/* Miranda IM: the free IM client for Microsoft* Windows* Copyright 2000-2009 Miranda ICQ/IM project, all portions of this codebase are copyrighted to the people listed in contributors.txt. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "..\..\core\commonheaders.h" #include "keybindings.h" #include "m_keybindings.h" static HANDLE hKeyBindings_Register; static HANDLE hKeyBindings_Get; static WNDPROC OldEditProc; static TCHAR *keySeparator = _T(" + "); static TreeItem *currentTreeItem = NULL; static DWORD tempModifiers; static DWORD modifiers; static DWORD virtualKey; static int addKeyBinding(KEYBINDINGDESC *desc) { int i, len; TCHAR *sectionName; TCHAR *actionName; KeyBindingItem *item = (KeyBindingItem *)mir_alloc(sizeof(KeyBindingItem)); ZeroMemory(item, sizeof(KeyBindingItem)); if (desc->flags & KBDF_UNICODE) { #ifdef _UNICODE sectionName = mir_tstrdup(desc->ptszSection); actionName = mir_tstrdup(desc->ptszActionName); #else sectionName = u2a(desc->pwszSection); actionName = u2a(desc->pwszActionName); #endif } else { #ifdef _UNICODE sectionName = a2u(desc->pszSection); actionName = a2u(desc->pszActionName); #else sectionName = mir_tstrdup(desc->ptszSection); actionName = mir_tstrdup(desc->ptszActionName); #endif } len = _tcslen(sectionName) + _tcslen(actionName) + 2; item->fullActionName = (TCHAR *)mir_alloc(len * sizeof(TCHAR)); _tcscpy(item->fullActionName, sectionName); _tcscat(item->fullActionName, _T("/")); _tcscat(item->fullActionName, actionName); item->actionName = actionName; mir_free(sectionName); item->actionGroupName = mir_strdup(desc->pszActionGroup); item->action = desc->action; for (i = 0; i < 5; i++) { item->defaultKey[i] = desc->key[i]; item->key[i] = desc->key[i]; } item->next = keyBindingList; keyBindingList = item; if (item->next != NULL) item->next->prev = item; { DBVARIANT dbv; char *paramName = mir_t2a(item->fullActionName); if ( !DBGetContactSettingString(NULL, "KeyBindings", paramName, &dbv )) { for (i = 0; i < 5; i++) item->key[i] = 0; sscanf(dbv.pszVal, "%X,%X,%X,%X,%X", &item->key[0],&item->key[1],&item->key[2],&item->key[3],&item->key[4]); DBFreeVariant(&dbv); } mir_free(paramName); } return 0; } static KeyBindingItem* findKeyBinding(char *actionGroup, DWORD key) { int i; KeyBindingItem *ptr = NULL; if (key != 0) { for (ptr = keyBindingList; ptr != NULL; ptr = ptr->next) if (strcmp(ptr->actionGroupName, actionGroup) == 0) for (i = 0; i < 5; i++) if (ptr->key[i] == key) return ptr; } return ptr; } static KeyBindingItem* findTempKeyBinding(char *actionGroup, DWORD key) { int i; KeyBindingItem *ptr = NULL; if (key != 0) { for (ptr = keyBindingList; ptr != NULL; ptr = ptr->next) if (strcmp(ptr->actionGroupName, actionGroup) == 0) for (i = 0; i < 5; i++) if (ptr->tempKey[i] == key) return ptr; } return ptr; } static void removeTempKeyBinding(char *actionGroup, DWORD key) { KeyBindingItem *ptr = NULL; int i, j; if (key != 0) { for (ptr = keyBindingList; ptr != NULL; ptr = ptr->next) if (strcmp(ptr->actionGroupName, actionGroup) == 0) for (i = 0; i < 5; i++) if (ptr->tempKey[i] == key) { for (j = i+1; j < 5; j++) ptr->tempKey[j-1] = ptr->tempKey[j]; ptr->tempKey[4] = 0; } } } static void loadTempKeyBinding(KeyBindingItem *item) { int i; for (i = 0; i < 5; i ++) item->tempKey[i] = item->key[i]; } static void loadTempKeyBindings() { KeyBindingItem *ptr; for (ptr = keyBindingList; ptr != NULL; ptr = ptr->next) loadTempKeyBinding(ptr); } static void saveKeyBinding(KeyBindingItem *item) { BOOL save = FALSE; int i; char buff[128]; for (i = 0; i < 5; i ++) { if (item->key[i] != item->tempKey[i]) { item->key[i] = item->tempKey[i]; save = TRUE; } } if (save) { char *paramName = mir_t2a(item->fullActionName); mir_snprintf(buff, sizeof(buff), "%X,%X,%X,%X,%X", item->key[0],item->key[1],item->key[2],item->key[3],item->key[4]); DBWriteContactSettingString(NULL, "KeyBindings", paramName, buff); mir_free(paramName); } } static void saveKeyBindings() { KeyBindingItem *ptr; for (ptr = keyBindingList; ptr != NULL; ptr = ptr->next) saveKeyBinding(ptr); } static HTREEITEM findNamedTreeItemAt(HWND hwndTree, HTREEITEM hItem, const TCHAR *name) { TVITEM tvi = {0}; TCHAR str[MAX_PATH]; if (hItem) tvi.hItem = TreeView_GetChild(hwndTree, hItem); else tvi.hItem = TreeView_GetRoot(hwndTree); if (!name) return tvi.hItem; tvi.mask = TVIF_TEXT; tvi.pszText = str; tvi.cchTextMax = MAX_PATH; while (tvi.hItem) { TreeView_GetItem(hwndTree, &tvi); if (!lstrcmp(tvi.pszText, name)) return tvi.hItem; tvi.hItem = TreeView_GetNextSibling(hwndTree, tvi.hItem); } return NULL; } static void createSettingsTreeNode(HWND hwndTree, KeyBindingItem *keyBindingItem) { TCHAR itemName[1024]; TCHAR* sectionName; int sectionLevel = 0; HTREEITEM hSection = NULL; lstrcpy(itemName, keyBindingItem->fullActionName); sectionName = itemName; while (sectionName) { HTREEITEM hItem; TCHAR* pTranslatedItemName; TCHAR* pItemName = sectionName; if (sectionName = _tcschr(sectionName, '/')) { *sectionName = 0; } pTranslatedItemName = TranslateTS( pItemName ); hItem = findNamedTreeItemAt(hwndTree, hSection, pTranslatedItemName); if (!sectionName || !hItem) { if (!hItem) { TVINSERTSTRUCT tvis = {0}; TreeItem *treeItem = (TreeItem *)mir_alloc(sizeof(TreeItem)); treeItem->keyBinding = !sectionName ? keyBindingItem : NULL; treeItem->paramName = mir_t2a(itemName); tvis.hParent = hSection; tvis.hInsertAfter = TVI_SORT; //!sectionName ? TVI_LAST : TVI_SORT; tvis.item.mask = TVIF_TEXT|TVIF_PARAM|TVIF_STATE; tvis.item.pszText = pTranslatedItemName; tvis.item.lParam = (LPARAM)treeItem; tvis.item.state = tvis.item.stateMask = DBGetContactSettingByte(NULL, "KeyBindingsUI", treeItem->paramName, TVIS_EXPANDED ); hItem = TreeView_InsertItem(hwndTree, &tvis); } } if (sectionName) { *sectionName = '/'; sectionName++; } sectionLevel++; hSection = hItem; } } static void saveCollapseState( HWND hwndTree ) { HTREEITEM hti; TVITEM tvi; hti = TreeView_GetRoot( hwndTree ); while( hti != NULL ) { HTREEITEM ht; tvi.mask = TVIF_STATE | TVIF_HANDLE | TVIF_CHILDREN | TVIF_PARAM; tvi.hItem = hti; tvi.stateMask = (DWORD)-1; TreeView_GetItem( hwndTree, &tvi ); if( tvi.cChildren > 0 ) { TreeItem *treeItem = (TreeItem *)tvi.lParam; if ( tvi.state & TVIS_EXPANDED ) DBWriteContactSettingByte(NULL, "KeyBindingsUI", treeItem->paramName, TVIS_EXPANDED ); else DBWriteContactSettingByte(NULL, "KeyBindingsUI", treeItem->paramName, 0 ); } ht = TreeView_GetChild( hwndTree, hti ); if( ht == NULL ) { ht = TreeView_GetNextSibling( hwndTree, hti ); while( ht == NULL ) { hti = TreeView_GetParent( hwndTree, hti ); if( hti == NULL ) break; ht = TreeView_GetNextSibling( hwndTree, hti ); } } hti = ht; } } static const TCHAR* getKeyName(DWORD key) { static TCHAR keyName[64]; int nameLen = 0; ZeroMemory(keyName, sizeof(keyName)); if (key & KB_CTRL_FLAG) { GetKeyNameText(MAKELPARAM(0, MapVirtualKey(VK_CONTROL, 0)), keyName, 64); _tcscat(keyName, keySeparator); nameLen = _tcslen(keyName); } if (key & KB_SHIFT_FLAG) { GetKeyNameText(MAKELPARAM(0, MapVirtualKey(VK_SHIFT, 0)), &keyName[nameLen], 64 - nameLen); _tcscat(keyName, keySeparator); nameLen = _tcslen(keyName); } if (key & KB_ALT_FLAG) { GetKeyNameText(MAKELPARAM(0, MapVirtualKey(VK_MENU, 0)), &keyName[nameLen], 64 - nameLen); _tcscat(keyName, keySeparator); nameLen = _tcslen(keyName); } if ((key & 0xFFFF) != 0) { DWORD scanCode = MapVirtualKey(key & 0xFFFF, 0); switch(key & 0xFFFF) { case VK_INSERT: case VK_DELETE: case VK_HOME: case VK_END: case VK_NEXT: case VK_PRIOR: case VK_LEFT: case VK_RIGHT: case VK_UP: case VK_DOWN: scanCode |= 0x100; // Add extended bit } GetKeyNameText(MAKELPARAM(0, scanCode), &keyName[nameLen], 64 - nameLen); nameLen = _tcslen(keyName); } return keyName; } static refreshPreview(HWND hwnd) { TCHAR warning[1024]; ZeroMemory(warning, sizeof(warning)); SetWindowText(hwnd, getKeyName(virtualKey | modifiers)); if (currentTreeItem != NULL && currentTreeItem->keyBinding != NULL) { KeyBindingItem *item = findTempKeyBinding(currentTreeItem->keyBinding->actionGroupName, virtualKey | modifiers); if (item != NULL) mir_sntprintf(warning, 1024, TranslateT("Shortcut already assigned to \"%s\" action.\nIf you click \"Add\" the shortcut will be reassigned."), item->actionName); } SetDlgItemText(GetParent(hwnd), IDC_MESSAGE, warning); EnableWindow(GetDlgItem(GetParent(hwnd), IDC_ADD), virtualKey != 0); } static LRESULT CALLBACK KeyBindingsEditProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_CREATE: virtualKey = 0; break; case WM_SYSKEYDOWN: case WM_KEYDOWN: if (virtualKey != 0) { virtualKey = 0; } switch (wParam) { case VK_SHIFT: tempModifiers |= KB_SHIFT_FLAG; break; case VK_CONTROL: tempModifiers |= KB_CTRL_FLAG; break; case VK_MENU: tempModifiers |= KB_ALT_FLAG; break; default: virtualKey = wParam; break; } modifiers = tempModifiers; refreshPreview(hwnd); return 0; case WM_KEYUP: case WM_SYSKEYUP: switch (wParam) { case VK_SHIFT: tempModifiers &= ~KB_SHIFT_FLAG; break; case VK_CONTROL: tempModifiers &= ~KB_CTRL_FLAG; break; case VK_MENU: tempModifiers &= ~KB_ALT_FLAG; break; default: break; } if (virtualKey == 0) { modifiers = tempModifiers; refreshPreview(hwnd); } case WM_CHAR: case WM_PASTE: return 0; case WM_SETFOCUS: modifiers = 0; tempModifiers = 0; virtualKey = 0; refreshPreview(hwnd); break; case WM_GETDLGCODE: return DLGC_WANTARROWS|DLGC_WANTALLKEYS| DLGC_WANTTAB; } return CallWindowProc(OldEditProc, hwnd, msg, wParam, lParam); } static void buildTree(HWND hwnd) { KeyBindingItem *ptr; for (ptr = keyBindingList; ptr != NULL; ptr=ptr->next) { createSettingsTreeNode(hwnd, ptr); } } static void refreshListBox(HWND hwnd) { int count = 0; BOOL nonDefault = FALSE; BOOL canUndo = FALSE; SendDlgItemMessage(hwnd, IDC_LIST, LB_RESETCONTENT, 0, 0); if (currentTreeItem->keyBinding != NULL) { int i; for (i=0; i<5; i++) { if (currentTreeItem->keyBinding->tempKey[i] != currentTreeItem->keyBinding->defaultKey[i]) nonDefault = TRUE; if (currentTreeItem->keyBinding->tempKey[i] != currentTreeItem->keyBinding->key[i]) canUndo = TRUE; if (currentTreeItem->keyBinding->tempKey[i] != 0) { SendDlgItemMessage(hwnd, IDC_LIST, LB_ADDSTRING, 0, (LPARAM)getKeyName(currentTreeItem->keyBinding->tempKey[i])); count++; } } } EnableWindow(GetDlgItem(hwnd, IDC_PREVIEW), currentTreeItem->keyBinding != NULL && count < 5); SetDlgItemText(hwnd, IDC_PREVIEW, _T("")); SetDlgItemText(hwnd, IDC_MESSAGE, _T("")); EnableWindow(GetDlgItem(hwnd, IDC_ADD), FALSE); EnableWindow(GetDlgItem(hwnd, IDC_DELETE), FALSE); EnableWindow(GetDlgItem(hwnd, IDC_BTN_RESET), nonDefault); EnableWindow(GetDlgItem(hwnd, IDC_BTN_UNDO), canUndo); } BOOL CALLBACK DlgProcKeyBindingsOpts(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_INITDIALOG: { TranslateDialogDefault(hwndDlg); OldEditProc = (WNDPROC) SetWindowLongPtr(GetDlgItem(hwndDlg, IDC_PREVIEW), GWLP_WNDPROC, (LONG_PTR) KeyBindingsEditProc); currentTreeItem = NULL; loadTempKeyBindings(); buildTree(GetDlgItem(hwndDlg, IDC_CATEGORYLIST)); EnableWindow(GetDlgItem(hwndDlg, IDC_ADD), FALSE); EnableWindow(GetDlgItem(hwndDlg, IDC_DELETE), FALSE); } return TRUE; case WM_KEYDOWN: case WM_SYSKEYDOWN: SendDlgItemMessage(hwndDlg, IDC_PREVIEW, msg, wParam, lParam); return TRUE; case WM_COMMAND: switch (LOWORD(wParam)) { case IDC_ADD: if (currentTreeItem->keyBinding != NULL) { int i; removeTempKeyBinding(currentTreeItem->keyBinding->actionGroupName, modifiers | virtualKey); for (i=0; i<5; i++) { if (currentTreeItem->keyBinding->tempKey[i] == 0) { currentTreeItem->keyBinding->tempKey[i] = modifiers | virtualKey; break; } } } refreshListBox(hwndDlg); SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); break; case IDC_DELETE: if (currentTreeItem->keyBinding != NULL) { int index = SendDlgItemMessage(hwndDlg, IDC_LIST, LB_GETCURSEL, 0, 0); if (index != LB_ERR && index <5) { if (currentTreeItem->keyBinding->tempKey[index] != 0) { int i; for (i = index + 1; i < 5; i++) { currentTreeItem->keyBinding->tempKey[i-1] = currentTreeItem->keyBinding->tempKey[i]; } currentTreeItem->keyBinding->tempKey[4] = 0; } } } refreshListBox(hwndDlg); SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); break; case IDC_BTN_RESET: if (currentTreeItem->keyBinding != NULL) { int i; for (i = 0; i < 5; i++) { removeTempKeyBinding(currentTreeItem->keyBinding->actionGroupName, currentTreeItem->keyBinding->defaultKey[i]); currentTreeItem->keyBinding->tempKey[i] = currentTreeItem->keyBinding->defaultKey[i]; } } refreshListBox(hwndDlg); SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); break; case IDC_BTN_UNDO: if (currentTreeItem->keyBinding != NULL) { int i; for (i = 0; i < 5; i++) { removeTempKeyBinding(currentTreeItem->keyBinding->actionGroupName, currentTreeItem->keyBinding->key[i]); currentTreeItem->keyBinding->tempKey[i] = currentTreeItem->keyBinding->key[i]; } } refreshListBox(hwndDlg); SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); break; case IDC_LIST: if (HIWORD(wParam) == LBN_SELCHANGE) { EnableWindow(GetDlgItem(hwndDlg, IDC_DELETE), TRUE); } break; } break; case WM_NOTIFY: if (((LPNMHDR) lParam)->idFrom == IDC_CATEGORYLIST) { switch(((NMHDR*)lParam)->code) { case TVN_SELCHANGEDA: case TVN_SELCHANGEDW: { TVITEM tvi = {0}; tvi.hItem = TreeView_GetSelection(GetDlgItem(hwndDlg, IDC_CATEGORYLIST)); tvi.mask = TVIF_HANDLE|TVIF_PARAM; TreeView_GetItem(GetDlgItem(hwndDlg, IDC_CATEGORYLIST), &tvi); currentTreeItem = (TreeItem *) tvi.lParam; refreshListBox(hwndDlg); break; } case TVN_DELETEITEMA: case TVN_DELETEITEMW: { TreeItem *treeItem = (TreeItem *)(((LPNMTREEVIEW)lParam)->itemOld.lParam); if (treeItem) { mir_free(treeItem->paramName); mir_free(treeItem); } break; } } } if (((LPNMHDR) lParam)->idFrom == 0 && ((LPNMHDR) lParam)->code == PSN_APPLY ) { saveKeyBindings(); } break; case WM_DESTROY: saveCollapseState(GetDlgItem(hwndDlg, IDC_CATEGORYLIST)); } return FALSE; } static INT_PTR KBRegister( WPARAM wParam, LPARAM lParam ) { return (int)addKeyBinding(( KEYBINDINGDESC* )lParam ); } static INT_PTR KBGet( WPARAM wParam, LPARAM lParam ) { KEYBINDINGDESC* desc = ( KEYBINDINGDESC* )lParam; KeyBindingItem* item = (KeyBindingItem*)findKeyBinding(desc->pszActionGroup, desc->key[0]); if (item != NULL) { desc->action = item->action; return 0; } return 1; } static void InitKeyBinding() { keyBindingList = NULL; hKeyBindings_Register = CreateServiceFunction(MS_KEYBINDINGS_REGISTER, KBRegister); hKeyBindings_Get = CreateServiceFunction(MS_KEYBINDINGS_GET, KBGet); } static void UninitKeyBinding() { KeyBindingItem *ptr, *ptr2; ptr = keyBindingList; keyBindingList = NULL; for (; ptr != NULL; ptr = ptr2) { ptr2 = ptr->next; mir_free(ptr->actionName); mir_free(ptr->fullActionName); mir_free(ptr->actionGroupName); mir_free(ptr); } DestroyServiceFunction(hKeyBindings_Register); DestroyServiceFunction(hKeyBindings_Get); } static int KeyBindingsOptionsInit(WPARAM wParam,LPARAM lParam) { OPTIONSDIALOGPAGE odp = {0}; odp.cbSize = sizeof(odp); odp.hInstance = GetModuleHandle(NULL); odp.flags = ODPF_BOLDGROUPS | ODPF_TCHAR; odp.position = -180000000; odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_KEYBINDINGS); odp.ptszTitle = TranslateT("Key Bindings"); odp.ptszGroup = TranslateT("Customize"); odp.pfnDlgProc = DlgProcKeyBindingsOpts; CallService(MS_OPT_ADDPAGE, wParam, (LPARAM)&odp); return 0; } static int KeyBindingsSystemModulesLoaded(WPARAM wParam,LPARAM lParam) { HookEvent(ME_OPT_INITIALISE, KeyBindingsOptionsInit); return 0; } static int OnPreShutdown(WPARAM wParam, LPARAM lParam) { UninitKeyBinding(); return 0; } int LoadKeyBindingsModule( void ) { HookEvent(ME_SYSTEM_MODULESLOADED, KeyBindingsSystemModulesLoaded); HookEvent(ME_SYSTEM_PRESHUTDOWN, OnPreShutdown); InitKeyBinding(); return 0; }