diff --git a/lib/native/linux-64/libglobalshortcut.so b/lib/native/linux-64/libglobalshortcut.so index fc01cb1cb..648db1f47 100755 Binary files a/lib/native/linux-64/libglobalshortcut.so and b/lib/native/linux-64/libglobalshortcut.so differ diff --git a/lib/native/linux/libglobalshortcut.so b/lib/native/linux/libglobalshortcut.so index a3253051d..428f50e69 100755 Binary files a/lib/native/linux/libglobalshortcut.so and b/lib/native/linux/libglobalshortcut.so differ diff --git a/lib/native/mac/libglobalshortcut.jnilib b/lib/native/mac/libglobalshortcut.jnilib index 261f936c4..28d6bf9d9 100755 Binary files a/lib/native/mac/libglobalshortcut.jnilib and b/lib/native/mac/libglobalshortcut.jnilib differ diff --git a/lib/native/windows-64/globalshortcut.dll b/lib/native/windows-64/globalshortcut.dll index 7f03750c0..a5b0820e7 100644 Binary files a/lib/native/windows-64/globalshortcut.dll and b/lib/native/windows-64/globalshortcut.dll differ diff --git a/lib/native/windows/globalshortcut.dll b/lib/native/windows/globalshortcut.dll index 3b6e4d8e0..15de04079 100644 Binary files a/lib/native/windows/globalshortcut.dll and b/lib/native/windows/globalshortcut.dll differ diff --git a/resources/languages/resources.properties b/resources/languages/resources.properties index b07324794..a0ff1228a 100644 --- a/resources/languages/resources.properties +++ b/resources/languages/resources.properties @@ -1122,6 +1122,9 @@ plugin.keybindings.globalchooser.MUTE_CALLS=Mute calls plugin.keybindings.globalchooser.SHORTCUT_NAME=Name plugin.keybindings.globalchooser.SHORTCUT_PRIMARY=Primary shortcut plugin.keybindings.globalchooser.SHORTCUT_SECOND=Second shortcut +plugin.keybindings.globalchooser.PRESS_BTN=Press to set shortcut +plugin.keybindings.globalchooser.PRESS_BTN_DOWN=Waiting +plugin.keybindings.globalchooser.ENABLE_SPECIAL=Enable special key detection plugin.keybindings.PLUGIN_NAME=Keybindings plugin.keybindings.CHAT=Chat plugin.keybindings.MAIN=Main diff --git a/src/native/globalshortcut/net_java_sip_communicator_impl_globalshortcut_NativeKeyboardHook.cc b/src/native/globalshortcut/net_java_sip_communicator_impl_globalshortcut_NativeKeyboardHook.cc index 350144807..4d8c41f6b 100644 --- a/src/native/globalshortcut/net_java_sip_communicator_impl_globalshortcut_NativeKeyboardHook.cc +++ b/src/native/globalshortcut/net_java_sip_communicator_impl_globalshortcut_NativeKeyboardHook.cc @@ -922,3 +922,31 @@ JNIEXPORT void JNICALL Java_net_java_sip_communicator_impl_globalshortcut_Native } } +JNIEXPORT jboolean JNICALL Java_net_java_sip_communicator_impl_globalshortcut_NativeKeyboardHook_registerSpecial + (JNIEnv* jniEnv, jclass clazz, jlong ptr, jint keycode) +{ + (void)jniEnv; + (void)clazz; + (void)ptr; + (void)keycode; + return JNI_FALSE; +} + +JNIEXPORT void JNICALL Java_net_java_sip_communicator_impl_globalshortcut_NativeKeyboardHook_unregisterSpecial + (JNIEnv* jniEnv, jclass clazz, jlong ptr, jint keycode) +{ + (void)jniEnv; + (void)clazz; + (void)ptr; + (void)keycode; +} + +JNIEXPORT void JNICALL Java_net_java_sip_communicator_impl_globalshortcut_NativeKeyboardHook_detectSpecialKeyPress + (JNIEnv* jniEnv, jclass clazz, jlong ptr, jboolean enable) +{ + (void)jniEnv; + (void)clazz; + (void)ptr; + (void)enable; +} + diff --git a/src/native/globalshortcut/net_java_sip_communicator_impl_globalshortcut_NativeKeyboardHook.cpp b/src/native/globalshortcut/net_java_sip_communicator_impl_globalshortcut_NativeKeyboardHook.cpp index 119b51cc6..2ab0db464 100644 --- a/src/native/globalshortcut/net_java_sip_communicator_impl_globalshortcut_NativeKeyboardHook.cpp +++ b/src/native/globalshortcut/net_java_sip_communicator_impl_globalshortcut_NativeKeyboardHook.cpp @@ -61,6 +61,8 @@ class keyboard_hook HWND hwnd; /**< Handle of the window that will receive event. */ int hotkey_next_id; /**< Next ID to use for a new hotkey. */ std::list keystrokes; /**< List of keystrokes registered. */ + std::list specials; /**< List of special keystrokes registered. */ + bool detect; /**< If we are in detection mode. */ }; /** @@ -653,13 +655,13 @@ static int convertWindowsKeycodeToJava(int keycode) else if(keycode == VK_PERIOD) ret = JVK_PERIOD; else if(keycode == VK_DOLLAR) - ret = JVK_DOLLAR; - else if(keycode == VK_LESS) - ret = JVK_LESS; - else if(keycode == VK_GREATER) - ret = JVK_GREATER; - else if(keycode == VK_SLASH) - ret = JVK_SLASH; + ret = JVK_DOLLAR; + else if(keycode == VK_LESS) + ret = JVK_LESS; + else if(keycode == VK_GREATER) + ret = JVK_GREATER; + else if(keycode == VK_SLASH) + ret = JVK_SLASH; } return ret; } @@ -872,6 +874,39 @@ static void RegisterWindowClassW(HINSTANCE hInstance) } } +/** + * \brief Callback handler for low-level keyboard press/release. + * \param nCode code + * \param wParam wParam + * \param lParam lParam + * \return CallNextHookEx return value + */ +LRESULT CALLBACK keyHandler(int nCode, WPARAM wParam, LPARAM lParam) +{ + KBDLLHOOKSTRUCT* kbd = (KBDLLHOOKSTRUCT*)lParam; + int pressed = wParam == WM_KEYDOWN; + + if(pressed && kbd->vkCode > 160) + { + if(g_keyboard_hook.detect) + { + notify(kbd->vkCode, 16367); + } + else + { + for(std::list::const_iterator it = g_keyboard_hook.specials.begin() ; it != g_keyboard_hook.specials.end() ; ++it) + { + if((*it)== kbd->vkCode) + { + notify((*it), 16367); + break; + } + } + } + } + return CallNextHookEx(g_keyboard_hook.hook, nCode, wParam, lParam); +} + /** * \brief Thread that create window and that monitor event related to it. * \param pThreadParam thread parameter @@ -887,6 +922,7 @@ static unsigned WINAPI CreateWndThreadW(LPVOID pThreadParam) HWND hWnd = CreateWindowW(WINDOW_SHORTCUT_NAME, NULL, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); + keyboard->hook = SetWindowsHookEx(WH_KEYBOARD_LL, keyHandler, NULL, 0); if(hWnd == NULL) { @@ -926,6 +962,7 @@ JNIEXPORT void JNICALL Java_net_java_sip_communicator_impl_globalshortcut_Native HANDLE hThread = NULL; UINT uThreadId = 0; + keyboard->detect = false; hThread = (HANDLE)_beginthreadex(NULL, 0, &CreateWndThreadW, keyboard, 0, &uThreadId); if(!hThread) { @@ -940,6 +977,7 @@ JNIEXPORT void JNICALL Java_net_java_sip_communicator_impl_globalshortcut_Native { keyboard_hook* keyboard = (keyboard_hook*)ptr; + UnhookWindowsHookEx(keyboard->hook); keyboard->hook = 0; keyboard->running = 0; } @@ -1017,3 +1055,50 @@ JNIEXPORT void JNICALL Java_net_java_sip_communicator_impl_globalshortcut_Native } } +JNIEXPORT jboolean JNICALL Java_net_java_sip_communicator_impl_globalshortcut_NativeKeyboardHook_registerSpecial + (JNIEnv* jniEnv, jclass clazz, jlong ptr, jint keycode) +{ + struct keyboard_hook* keyboard = (struct keyboard_hook*)ptr; + + if(keyboard && keyboard->hook != NULL) + { + keyboard->specials.push_back((int)keycode); + return JNI_TRUE; + } + return JNI_FALSE; +} + +JNIEXPORT void JNICALL Java_net_java_sip_communicator_impl_globalshortcut_NativeKeyboardHook_unregisterSpecial + (JNIEnv* jniEnv, jclass clazz, jlong ptr, jint keycode) +{ + struct keyboard_hook* keyboard = (struct keyboard_hook*)ptr; + + if(keyboard) + { + for(std::list::iterator it = keyboard->specials.begin() ; + it != keyboard->specials.end() ; ++it) + { + if((*it) == keycode) + { + keyboard->specials.erase(it); + return; + } + } + } +} + +/* + * Class: net_java_sip_communicator_impl_globalshortcut_NativeKeyboardHook + * Method: detectSpecialKeyPress + * Signature: (JZ)V + */ +JNIEXPORT void JNICALL Java_net_java_sip_communicator_impl_globalshortcut_NativeKeyboardHook_detectSpecialKeyPress + (JNIEnv* jniEnv, jclass clazz, jlong ptr, jboolean enable) +{ + struct keyboard_hook* keyboard = (struct keyboard_hook*)ptr; + + if(keyboard) + { + keyboard->detect = (enable == JNI_TRUE) ? true : false; + } +} diff --git a/src/native/globalshortcut/net_java_sip_communicator_impl_globalshortcut_NativeKeyboardHook.h b/src/native/globalshortcut/net_java_sip_communicator_impl_globalshortcut_NativeKeyboardHook.h index 75025f931..2ca41310a 100644 --- a/src/native/globalshortcut/net_java_sip_communicator_impl_globalshortcut_NativeKeyboardHook.h +++ b/src/native/globalshortcut/net_java_sip_communicator_impl_globalshortcut_NativeKeyboardHook.h @@ -55,6 +55,30 @@ JNIEXPORT jboolean JNICALL Java_net_java_sip_communicator_impl_globalshortcut_Na JNIEXPORT void JNICALL Java_net_java_sip_communicator_impl_globalshortcut_NativeKeyboardHook_unregisterShortcut (JNIEnv *, jclass, jlong, jint, jint); +/* + * Class: net_java_sip_communicator_impl_globalshortcut_NativeKeyboardHook + * Method: registerSpecial + * Signature: (JI)Z + */ +JNIEXPORT jboolean JNICALL Java_net_java_sip_communicator_impl_globalshortcut_NativeKeyboardHook_registerSpecial + (JNIEnv *, jclass, jlong, jint); + +/* + * Class: net_java_sip_communicator_impl_globalshortcut_NativeKeyboardHook + * Method: unregisterSpecial + * Signature: (JI)V + */ +JNIEXPORT void JNICALL Java_net_java_sip_communicator_impl_globalshortcut_NativeKeyboardHook_unregisterSpecial + (JNIEnv *, jclass, jlong, jint); + +/* + * Class: net_java_sip_communicator_impl_globalshortcut_NativeKeyboardHook + * Method: detectSpecialKeyPress + * Signature: (JZ)V + */ +JNIEXPORT void JNICALL Java_net_java_sip_communicator_impl_globalshortcut_NativeKeyboardHook_detectSpecialKeyPress + (JNIEnv *, jclass, jlong, jboolean); + #ifdef __cplusplus } #endif diff --git a/src/native/globalshortcut/net_java_sip_communicator_impl_globalshortcut_NativeKeyboardHook.m b/src/native/globalshortcut/net_java_sip_communicator_impl_globalshortcut_NativeKeyboardHook.m index f46e3f9b7..2e78b2978 100644 --- a/src/native/globalshortcut/net_java_sip_communicator_impl_globalshortcut_NativeKeyboardHook.m +++ b/src/native/globalshortcut/net_java_sip_communicator_impl_globalshortcut_NativeKeyboardHook.m @@ -938,3 +938,31 @@ JNIEXPORT void JNICALL Java_net_java_sip_communicator_impl_globalshortcut_Native } } +JNIEXPORT jboolean JNICALL Java_net_java_sip_communicator_impl_globalshortcut_NativeKeyboardHook_registerSpecial + (JNIEnv* jniEnv, jclass clazz, jlong ptr, jint keycode) +{ + (void)jniEnv; + (void)clazz; + (void)ptr; + (void)keycode; + return JNI_FALSE; +} + +JNIEXPORT void JNICALL Java_net_java_sip_communicator_impl_globalshortcut_NativeKeyboardHook_unregisterSpecial + (JNIEnv* jniEnv, jclass clazz, jlong ptr, jint keycode) +{ + (void)jniEnv; + (void)clazz; + (void)ptr; + (void)keycode; +} + +JNIEXPORT void JNICALL Java_net_java_sip_communicator_impl_globalshortcut_NativeKeyboardHook_detectSpecialKeyPress + (JNIEnv* jniEnv, jclass clazz, jlong ptr, jboolean enable) +{ + (void)jniEnv; + (void)clazz; + (void)ptr; + (void)enable; +} + diff --git a/src/net/java/sip/communicator/impl/globalshortcut/GlobalShortcutServiceImpl.java b/src/net/java/sip/communicator/impl/globalshortcut/GlobalShortcutServiceImpl.java index d1cda5624..9268dc3e5 100644 --- a/src/net/java/sip/communicator/impl/globalshortcut/GlobalShortcutServiceImpl.java +++ b/src/net/java/sip/communicator/impl/globalshortcut/GlobalShortcutServiceImpl.java @@ -38,6 +38,11 @@ public class GlobalShortcutServiceImpl private final Map> mapActions = new HashMap>(); + /** + * List of notifiers when special key detection is enabled. + */ + private final List specialKeyNotifiers = + new ArrayList(); /** * If the service is running or not. */ @@ -58,6 +63,16 @@ public class GlobalShortcutServiceImpl */ private final UIShortcut uiShortcut = new UIShortcut(); + /** + * Last special key detected. + */ + private AWTKeyStroke specialKeyDetected = null; + + /** + * Object to synchronize special key detection. + */ + private final Object specialKeySyncRoot = new Object(); + /** * Initializes the GlobalShortcutServiceImpl. */ @@ -90,6 +105,7 @@ public void registerShortcut(GlobalShortcutListener listener, synchronized(mapActions) { List keystrokes = mapActions.get(listener); + boolean ok = false; if(keyStroke == null) { @@ -98,21 +114,38 @@ public void registerShortcut(GlobalShortcutListener listener, if(keystrokes != null) { - if(keyboardHook.registerShortcut(keyStroke.getKeyCode(), - getModifiers(keyStroke))) + if(keyStroke.getModifiers() != SPECIAL_KEY_MODIFIERS) + { + ok = keyboardHook.registerShortcut(keyStroke.getKeyCode(), + getModifiers(keyStroke)); + } + else { - if(add) - keystrokes.add(keyStroke); + ok = keyboardHook.registerSpecial(keyStroke.getKeyCode()); + } + + if(ok && add) + { + keystrokes.add(keyStroke); } } else { keystrokes = new ArrayList(); - if(keyboardHook.registerShortcut(keyStroke.getKeyCode(), - getModifiers(keyStroke))) + + if(keyStroke.getModifiers() != SPECIAL_KEY_MODIFIERS) { - if(add) - keystrokes.add(keyStroke); + ok = keyboardHook.registerShortcut(keyStroke.getKeyCode(), + getModifiers(keyStroke)); + } + else + { + ok = keyboardHook.registerSpecial(keyStroke.getKeyCode()); + } + + if(ok && add) + { + keystrokes.add(keyStroke); } } @@ -160,8 +193,15 @@ public void unregisterShortcut(GlobalShortcutListener listener, ks = l; } - keyboardHook.unregisterShortcut(keyStroke.getKeyCode(), - getModifiers(keyStroke)); + if(modifiers != SPECIAL_KEY_MODIFIERS) + { + keyboardHook.unregisterShortcut(keyStroke.getKeyCode(), + getModifiers(keyStroke)); + } + else + { + keyboardHook.unregisterSpecial(keyStroke.getKeyCode()); + } if(remove) { @@ -228,6 +268,30 @@ public void stop() */ public synchronized void receiveKey(int keycode, int modifiers) { + if(keyboardHook.isSpecialKeyDetection()) + { + specialKeyDetected = AWTKeyStroke.getAWTKeyStroke(keycode, + modifiers); + + synchronized(specialKeySyncRoot) + { + specialKeySyncRoot.notify(); + } + + GlobalShortcutEvent evt = new GlobalShortcutEvent( + specialKeyDetected); + List copyListeners = + new ArrayList(specialKeyNotifiers); + + for(GlobalShortcutListener l : copyListeners) + { + l.shortcutReceived(evt); + } + + // if special key detection is enabled, disable all other shortcuts + return; + } + synchronized(mapActions) { // compare keycode/modifiers to keystroke @@ -239,7 +303,9 @@ public synchronized void receiveKey(int keycode, int modifiers) for(AWTKeyStroke l : lst) { if(l.getKeyCode() == keycode && - getModifiers(l) == modifiers) + (getModifiers(l) == modifiers || + (modifiers == SPECIAL_KEY_MODIFIERS && + l.getModifiers() == modifiers))) { // notify corresponding listeners GlobalShortcutEvent evt = new GlobalShortcutEvent(l); @@ -381,6 +447,67 @@ public void setEnable(boolean enable) } } + /** + * Enable or disable special key detection. + * + * @param enable enable or not special key detection. + * @param callback object to be notified + */ + public synchronized void setSpecialKeyDetection(boolean enable, + GlobalShortcutListener callback) + { + keyboardHook.detectSpecialKeyPress(enable); + + if(specialKeyNotifiers.contains(callback) == enable) + { + return; + } + + if(enable) + { + specialKeyNotifiers.add(callback); + } + else + { + specialKeyNotifiers.remove(callback); + } + } + + /** + * Get special keystroke or null if not supported or user cancels. If no + * special key is detected for 5 seconds, it returns null + * + * @return special keystroke or null if not supported or user cancels + */ + public AWTKeyStroke getSpecialKey() + { + AWTKeyStroke ret = null; + + specialKeyDetected = null; + keyboardHook.detectSpecialKeyPress(true); + + // Windows only for the moment + if(OSUtils.IS_WINDOWS) + { + synchronized(specialKeySyncRoot) + { + try + { + specialKeySyncRoot.wait(5000); + } + catch(InterruptedException e) + { + } + } + + ret = specialKeyDetected; + specialKeyDetected = null; + } + + keyboardHook.detectSpecialKeyPress(false); + return ret; + } + /** * Simple test. */ diff --git a/src/net/java/sip/communicator/impl/globalshortcut/NativeKeyboardHook.java b/src/net/java/sip/communicator/impl/globalshortcut/NativeKeyboardHook.java index e2101c4e9..b61971b55 100644 --- a/src/net/java/sip/communicator/impl/globalshortcut/NativeKeyboardHook.java +++ b/src/net/java/sip/communicator/impl/globalshortcut/NativeKeyboardHook.java @@ -34,6 +34,11 @@ public class NativeKeyboardHook */ private static long ptr = 0; + /** + * If the special key detection is enable. + */ + private boolean specialKeydetection = false; + /** * Constructor. */ @@ -105,6 +110,57 @@ public synchronized void unregisterShortcut(int keycode, unregisterShortcut(ptr, keycode, modifiers); } + /** + * Register a special key shortcut (for example key coming from headset). + * + * @param keycode keycode of the shortcut + * @param modifiers modifiers (CTRL, ALT, ...) + * @return true if success, false otherwise + */ + public synchronized boolean registerSpecial(int keycode) + { + if(ptr != 0) + return registerSpecial(ptr, keycode); + + return false; + } + + /** + * Unregister a special key shortcut (for example key coming from headset). + * + * @param keycode keycode of the shortcut + * @param modifiers modifiers (CTRL, ALT, ...) + */ + public synchronized void unregisterSpecial(int keycode) + { + if(ptr != 0) + unregisterSpecial(ptr, keycode); + } + + /** + * Detect special key press. + * + * @param enable enable or not the special key press detection. + */ + public synchronized void detectSpecialKeyPress(boolean enable) + { + if(ptr != 0) + { + detectSpecialKeyPress(ptr, enable); + this.specialKeydetection = enable; + } + } + + /** + * Returns whether or not special key detection is enabled. + * + * @return true if special key detection is enabled, false otherwise + */ + public boolean isSpecialKeyDetection() + { + return specialKeydetection; + } + /** * Native method to initialize NativeKeyboardHook. * @@ -141,6 +197,7 @@ private static native void setDelegate(long ptr, * @param ptr native pointer * @param keycode keycode of the shortcut * @param modifiers modifiers (CTRL, ALT, ...) + * @return true if registration is successful, false otherwise */ private static native boolean registerShortcut(long ptr, int keycode, int modifiers); @@ -155,6 +212,33 @@ private static native boolean registerShortcut(long ptr, int keycode, private static native void unregisterShortcut(long ptr, int keycode, int modifiers); + /** + * Native method to register a special key shortcut (for example key coming + * from a headset). + * + * @param ptr native pointer + * @param keycode keycode of the shortcut + * @return true if registration is successful, false otherwise + */ + private static native boolean registerSpecial(long ptr, int keycode); + + /** + * Native method to unregister a special key shortcut (for example key + * coming from a headset). + * + * @param ptr native pointer + * @param keycode keycode of the shortcut + */ + private static native void unregisterSpecial(long ptr, int keycode); + + /** + * Native method to ook for special key press. + * + * @param ptr native pointer + * @param enable enable or not the special key press detection. + */ + private static native void detectSpecialKeyPress(long ptr, boolean enable); + static { try diff --git a/src/net/java/sip/communicator/impl/keybindings/KeybindingsServiceImpl.java b/src/net/java/sip/communicator/impl/keybindings/KeybindingsServiceImpl.java index a7a7a4fca..108905116 100644 --- a/src/net/java/sip/communicator/impl/keybindings/KeybindingsServiceImpl.java +++ b/src/net/java/sip/communicator/impl/keybindings/KeybindingsServiceImpl.java @@ -183,16 +183,16 @@ synchronized void start(BundleContext bc) Map customTmp = new LinkedHashMap(customBindings); - for (Map.Entry shortcut2action : defaultBindings - .entrySet()) + for (Map.Entry shortcut2action : + defaultBindings.entrySet()) { String action = shortcut2action.getValue(); if (customTmp.containsValue(action)) { KeyStroke custom = null; - for (Map.Entry customShortcut2action : customTmp - .entrySet()) + for(Map.Entry customShortcut2action : + customTmp.entrySet()) { if (customShortcut2action.getValue().equals(action)) { @@ -383,9 +383,15 @@ public Map> getGlobalShortcutFromConfiguration() { kss.add(AWTKeyStroke.getAWTKeyStroke(shortcut)); } + + // second shortcut is always "special" if(shortcut2 != null) { - kss.add(AWTKeyStroke.getAWTKeyStroke(shortcut2)); + //16367 is the combination of all possible modifiers + //(shift ctrl meta alt altGraph button1 button2 button3 pressed) + //it is used to distinguish special key (headset) and others + int nb = Integer.parseInt(shortcut2); + kss.add(AWTKeyStroke.getAWTKeyStroke(nb, 16367)); } gBindings.put(name, kss); } @@ -417,8 +423,10 @@ public void saveGlobalShortcutFromConfiguration() configService.setProperty(shortcut, kss.size() > 0 ? kss.get(0) : null); - configService.setProperty(shortcut2, kss.size() > 1 ? - kss.get(1) : null); + // second shortcut is special + configService.setProperty(shortcut2, + (kss.size() > 1 && kss.get(1) != null) ? + kss.get(1).getKeyCode() : null); } } diff --git a/src/net/java/sip/communicator/plugin/keybindingchooser/globalchooser/GlobalShortcutConfigForm.java b/src/net/java/sip/communicator/plugin/keybindingchooser/globalchooser/GlobalShortcutConfigForm.java index 65e955d28..034d6d8c4 100644 --- a/src/net/java/sip/communicator/plugin/keybindingchooser/globalchooser/GlobalShortcutConfigForm.java +++ b/src/net/java/sip/communicator/plugin/keybindingchooser/globalchooser/GlobalShortcutConfigForm.java @@ -61,16 +61,6 @@ public class GlobalShortcutConfigForm private GlobalShortcutTableModel tableModel = new GlobalShortcutTableModel(); - /** - * Current selected row. - */ - private int currentRow = -1; - - /** - * Current selected row. - */ - private int currentColumn = -1; - /** * Constructor */ @@ -97,152 +87,76 @@ private void initComponents() @Override public void mouseClicked(MouseEvent e) { - if(e.getClickCount() >= 1) + if(e.getClickCount() >= 2) { int row = GlobalShortcutConfigForm.this.shortcutsTable. getSelectedRow(); - int column = GlobalShortcutConfigForm.this.shortcutsTable. - getSelectedColumn(); - - if(currentRow != -1 && currentColumn != -1) - return; - - if(row >= 0 && column >= 1) - { - currentRow = row; - currentColumn = column; - - if(column == 1) - GlobalShortcutConfigForm.this.tableModel.getEntryAt( - row).setEditShortcut1(true); - else if(column == 2) - GlobalShortcutConfigForm.this.tableModel.getEntryAt( - row).setEditShortcut2(true); - else - return; - - KeybindingChooserActivator.getGlobalShortcutService(). - setEnable(false); - refresh(); - shortcutsTable.setRowSelectionInterval(row, row); - } - } - } - }); - - shortcutsTable.addKeyListener(new KeyAdapter() - { - private KeyEvent buffer = null; - - @Override - public void keyPressed(KeyEvent event) - { - if(currentRow == -1 || currentColumn == -1) - return; - - // delete shortcut - if(event.getKeyCode() == KeyEvent.VK_BACK_SPACE) - { GlobalShortcutEntry en = - GlobalShortcutConfigForm.this.tableModel.getEntryAt( - currentRow); - List kss = new ArrayList(); - if(currentColumn == 1) - { - kss.add(null); - kss.add(en.getShortcut2()); - } - else if(currentColumn == 2) - { - kss.add(en.getShortcut()); - kss.add(null); - } - - currentRow = -1; - currentColumn = -1; - en.setShortcuts(kss); - en.setEditShortcut1(false); - en.setEditShortcut2(false); - GlobalShortcutConfigForm.this.saveConfig(); - GlobalShortcutConfigForm.this.refresh(); - } - else - { - // Reports KEY_PRESSED events on release to support - // modifiers - this.buffer = event; - } - } + GlobalShortcutConfigForm.this.tableModel. + getEntryAt(row); + List kss = new ArrayList(); - @Override - public void keyReleased(KeyEvent event) - { - if (buffer != null) - { - AWTKeyStroke input = KeyStroke.getKeyStrokeForEvent(buffer); - buffer = null; - - if(currentRow != -1) - { - GlobalShortcutEntry en = - GlobalShortcutConfigForm.this.tableModel.getEntryAt( - currentRow); - List kss = new ArrayList(); + GlobalShortcutDialog dialog = + new GlobalShortcutDialog((Dialog) + GlobalShortcutConfigForm.this.getTopLevelAncestor(), + en); - if(currentColumn == 1) // shortcut 1 - { - kss.add(input); - kss.add(en.getShortcut2()); - } - else if(currentColumn == 2) // shortcut 2 - { - kss.add(en.getShortcut()); - kss.add(input); - } - else - { - return; - } + kss.add(en.getShortcut()); + kss.add(en.getShortcut2()); - en.setShortcuts(kss); - en.setEditShortcut1(false); - en.setEditShortcut2(false); + KeybindingChooserActivator.getGlobalShortcutService(). + setEnable(false); + int ret = dialog.showDialog(); + if(ret == 1) + { + // ok button clicked kss = new ArrayList(); - List lst = tableModel.getEntries(); + List lst = + tableModel.getEntries(); - for(GlobalShortcutEntry e : lst) + for(GlobalShortcutEntry ee : lst) { - boolean isEntry = (e == en); - AWTKeyStroke s1 = isEntry && - currentColumn == 1 ? null : e.getShortcut(); - AWTKeyStroke s2 = isEntry && - currentColumn == 2 ? null : e.getShortcut2(); - - if(s1 != null && - s1.getKeyCode() == input.getKeyCode() && - s1.getModifiers() == input.getModifiers()) + boolean isEntry = (ee == en); + AWTKeyStroke s1 = isEntry ? null : + ee.getShortcut(); + AWTKeyStroke s2 = isEntry ? null : + ee.getShortcut2(); + + if(s1 != null && en.getShortcut() != null && + s1.getKeyCode() == en.getShortcut(). + getKeyCode() && + s1.getModifiers() == en.getShortcut(). + getModifiers()) { kss.add(null); - kss.add(e.getShortcut2()); - e.setShortcuts(kss); + kss.add(ee.getShortcut2()); + ee.setShortcuts(kss); break; } - else if(s2 != null && - s2.getKeyCode() == input.getKeyCode() && - s2.getModifiers() == input.getModifiers()) + else if(s2 != null && en.getShortcut2() != null && + s2.getKeyCode() == en.getShortcut2(). + getKeyCode() && + s2.getModifiers() == en.getShortcut2(). + getModifiers()) { - kss.add(e.getShortcut()); + kss.add(ee.getShortcut()); kss.add(null); - e.setShortcuts(kss); + ee.setShortcuts(kss); break; } } - currentRow = -1; - currentColumn = -1; + KeybindingChooserActivator.getGlobalShortcutService(). + setEnable(true); GlobalShortcutConfigForm.this.saveConfig(); GlobalShortcutConfigForm.this.refresh(); + KeybindingChooserActivator.getGlobalShortcutService(). + setEnable(true); + } + else + { + en.setShortcuts(kss); } } } diff --git a/src/net/java/sip/communicator/plugin/keybindingchooser/globalchooser/GlobalShortcutDialog.java b/src/net/java/sip/communicator/plugin/keybindingchooser/globalchooser/GlobalShortcutDialog.java new file mode 100644 index 000000000..12a8727f0 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/keybindingchooser/globalchooser/GlobalShortcutDialog.java @@ -0,0 +1,475 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.plugin.keybindingchooser.globalchooser; + +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import java.util.List; // disambiguation + +import javax.swing.*; + +import net.java.sip.communicator.plugin.keybindingchooser.*; +import net.java.sip.communicator.service.globalshortcut.*; +import net.java.sip.communicator.util.*; +import net.java.sip.communicator.util.skin.*; +import net.java.sip.communicator.util.swing.*; +import net.java.sip.communicator.util.swing.plaf.*; + +/** + * Dialog to choose the shortcut. + * + * @author Sebastien Vincent + */ +public class GlobalShortcutDialog + extends SIPCommDialog + implements ActionListener, + GlobalShortcutListener +{ + /** + * Serial version UID. + */ + private static final long serialVersionUID = 0L; + + /** + * Text displayed when no shortcut is configured. + */ + private static final String PRESS_TO_SETUP_SHORTCUT = + Resources.getString("plugin.keybindings.globalchooser.PRESS_BTN"); + + /** + * The global shortcut entry. + */ + private final GlobalShortcutEntry entry; + + /** + * OK button. + */ + private final JButton btnOK = new JButton( + Resources.getString("service.gui.OK")); + + /** + * Cancel button. + */ + private final JButton btnCancel = new JButton( + Resources.getString("service.gui.CANCEL")); + + /** + * Enable or not special key for shortcut. + */ + private final JCheckBox specialBox = new SIPCommCheckBox( + Resources.getString("plugin.keybindings.globalchooser.ENABLE_SPECIAL")); + + /** + * First shortcut field. + */ + private final ShortcutField fldShortcut = new ShortcutField( + PRESS_TO_SETUP_SHORTCUT); + + /** + * Secondary shortcut field. + */ + private final ShortcutField fldShortcut2 = new ShortcutField( + PRESS_TO_SETUP_SHORTCUT); + + /** + * Return code. + */ + private int retCode = 0; + + /** + * Constructor. + * + * @param dialog root dialog + * @param entry the global shortcut entry + */ + public GlobalShortcutDialog(Dialog dialog, GlobalShortcutEntry entry) + { + super(dialog); + + setModal(true); + setTitle("Global shortcut: " + entry.getAction()); + this.entry = entry; + init(); + } + + /** + * Initialize components. + */ + private void init() + { + TransparentPanel mainPanel = new TransparentPanel(new BorderLayout()); + JPanel btnPanel = new TransparentPanel( + new FlowLayout(FlowLayout.RIGHT)); + JPanel shortcutPanel = new TransparentPanel( + new GridLayout(0, 2, 0, 10)); + + btnOK.addActionListener(this); + btnCancel.addActionListener(this); + + KeyAdapter keyAdapter = new KeyAdapter() + { + private KeyEvent buffer = null; + + @Override + public void keyPressed(KeyEvent event) + { + if(event.getKeyCode() == KeyEvent.VK_ESCAPE) + { + SIPCommTextField field = + (SIPCommTextField)event.getSource(); + + AWTKeyStroke ks = null; + + if(field == fldShortcut) + { + ks = entry.getShortcut(); + } + else if(field == fldShortcut2) + { + ks = entry.getShortcut2(); + } + + if(ks == null) + field.setText(PRESS_TO_SETUP_SHORTCUT); + else + { + + if(ks.getModifiers() == + GlobalShortcutService.SPECIAL_KEY_MODIFIERS) + { + field.setText("Special"); + } + else + { + field.setText(GlobalShortcutEntry.getShortcutText( + entry.getShortcut())); + } + } + btnOK.requestFocusInWindow(); + return; + } + + if(event.getKeyCode() == 0) + return; + + // Reports KEY_PRESSED events on release to support modifiers + this.buffer = event; + } + + @Override + public void keyReleased(KeyEvent event) + { + if (buffer != null) + { + SIPCommTextField field = + (SIPCommTextField)event.getSource(); + AWTKeyStroke input = KeyStroke.getKeyStrokeForEvent(buffer); + buffer = null; + + GlobalShortcutEntry en = entry; + List kss = new ArrayList(); + + kss.add(input); + kss.add(en.getShortcut2()); + + en.setShortcuts(kss); + en.setEditShortcut1(false); + en.setEditShortcut2(false); + field.setText(GlobalShortcutEntry.getShortcutText( + input)); + btnOK.requestFocus(); + } + } + }; + + AWTKeyStroke ks = entry.getShortcut(); + AWTKeyStroke ks2 = entry.getShortcut2(); + + if(ks != null) + { + if(ks.getModifiers() != GlobalShortcutService.SPECIAL_KEY_MODIFIERS) + { + fldShortcut.setText(GlobalShortcutEntry.getShortcutText(ks)); + } + else + { + fldShortcut.setText("Special"); + } + } + + if(ks2 != null) + { + + if(ks2.getModifiers() != GlobalShortcutService.SPECIAL_KEY_MODIFIERS) + { + fldShortcut2.setText(GlobalShortcutEntry.getShortcutText(ks2)); + } + else + { + fldShortcut2.setText("Special"); + } + } + + fldShortcut.addKeyListener(keyAdapter); + fldShortcut2.addKeyListener(keyAdapter); + + specialBox.addItemListener(new ItemListener() + { + public void itemStateChanged(ItemEvent evt) + { + KeybindingChooserActivator.getGlobalShortcutService(). + setSpecialKeyDetection( + (evt.getStateChange() == ItemEvent.SELECTED), + GlobalShortcutDialog.this); + } + }); + + shortcutPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, + 10)); + shortcutPanel.add(new JLabel("Primary shortcut")); + shortcutPanel.add(fldShortcut); + shortcutPanel.add(new JLabel("Secondary shortcut")); + shortcutPanel.add(fldShortcut2); + + if(OSUtils.IS_WINDOWS) + { + shortcutPanel.add(new TransparentPanel()); + shortcutPanel.add(specialBox); + } + + mainPanel.add(shortcutPanel, BorderLayout.CENTER); + + btnPanel.add(btnOK); + btnPanel.add(btnCancel); + mainPanel.add(btnPanel, BorderLayout.SOUTH); + + btnOK.requestFocus(); + + getContentPane().add(mainPanel); + pack(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void close(boolean isEscaped) + { + super.close(isEscaped); + KeybindingChooserActivator.getGlobalShortcutService(). + setSpecialKeyDetection(false, this); + } + + /** + * Show the dialog and returns if the user has modified something (create + * or modify entry). + * + * @return true if the user has modified something (create + * or modify entry), false otherwise. + */ + public int showDialog() + { + setVisible(true); + + // as the dialog is modal, wait for OK/Cancel button retCode + setVisible(false); + return retCode; + } + + /** + * {@inheritDoc} + */ + public void actionPerformed(ActionEvent evt) + { + Object obj = evt.getSource(); + + if(obj == btnOK) + { + retCode = 1; + dispose(); + } + else if(obj == btnCancel) + { + retCode = 0; + dispose(); + } + } + + /** + * {@inheritDoc} + */ + public void shortcutReceived(GlobalShortcutEvent evt) + { + AWTKeyStroke ksr = evt.getKeyStroke(); + + if(ksr.getModifiers() != GlobalShortcutService.SPECIAL_KEY_MODIFIERS) + { + return; + } + + if(!fldShortcut.isFocusOwner() && !fldShortcut2.isFocusOwner()) + return; + + List kss = new ArrayList(); + + if(fldShortcut.isFocusOwner()) + { + kss.add(ksr); + kss.add(entry.getShortcut2()); + fldShortcut.setText("Special"); + } + else if(fldShortcut2.isFocusOwner()) + { + kss.add(entry.getShortcut()); + kss.add(ksr); + fldShortcut2.setText("Special"); + } + entry.setShortcuts(kss); + KeybindingChooserActivator.getGlobalShortcutService(). + setSpecialKeyDetection(false, this); + } + + /** + * Clear the text field. + * + * @param ui TextFieldUI to clear + */ + public void clearTextField(SIPCommTextFieldUI ui) + { + List kss = new ArrayList(); + + if(ui == fldShortcut.getUI()) + { + kss.add(null); + kss.add(entry.getShortcut2()); + entry.setShortcuts(kss); + btnOK.requestFocusInWindow(); + } + else if(ui == fldShortcut2.getUI()) + { + kss.add(entry.getShortcut()); + kss.add(null); + entry.setShortcuts(kss); + btnOK.requestFocusInWindow(); + } + } + + /** + * A custom call field. + */ + private class ShortcutField + extends SIPCommTextField + implements Skinnable + { + /** + * Serial version UID. + */ + private static final long serialVersionUID = 0L; + + /** + * The text field ui. + */ + private ShortcutFieldUI textFieldUI; + + /** + * Creates an instance of the CallField. + * + * @param text + */ + public ShortcutField(String text) + { + super(text); + + textFieldUI = new ShortcutFieldUI(); + textFieldUI.setDeleteButtonEnabled(true); + + this.setPreferredSize(new Dimension(200, 23)); + this.setUI(textFieldUI); + this.setBorder(null); + this.setOpaque(false); + + this.setDragEnabled(true); + + loadSkin(); + } + + /** + * Reloads text field UI defs. + */ + public void loadSkin() + { + textFieldUI.loadSkin(); + } + } + + public class ShortcutFieldUI + extends SIPCommTextFieldUI + implements Skinnable + { + /** + * Creates a SIPCommTextFieldUI. + */ + public ShortcutFieldUI() + { + loadSkin(); + } + + /** + * Adds the custom mouse listeners defined in this class to the installed + * listeners. + */ + protected void installListeners() + { + super.installListeners(); + } + + /** + * {@inheritDoc} + */ + protected void updateDeleteIcon(MouseEvent evt) + { + super.updateDeleteIcon(evt); + + Rectangle deleteRect = getDeleteButtonRect(); + if(deleteRect.contains(evt.getX(), evt.getY()) && + evt.getID() == MouseEvent.MOUSE_CLICKED) + { + clearTextField(this); + } + } + + /** + * Implements parent paintSafely method and enables antialiasing. + * @param g the Graphics object that notified us + */ + protected void paintSafely(Graphics g) + { + customPaintBackground(g); + super.paintSafely(g); + } + + /** + * Paints the background of the associated component. + * @param g the Graphics object used for painting + */ + protected void customPaintBackground(Graphics g) + { + Graphics2D g2 = (Graphics2D) g.create(); + + try + { + AntialiasingManager.activateAntialiasing(g2); + super.customPaintBackground(g2); + } + finally + { + g2.dispose(); + } + } + } +} diff --git a/src/net/java/sip/communicator/plugin/keybindingchooser/globalchooser/GlobalShortcutEntry.java b/src/net/java/sip/communicator/plugin/keybindingchooser/globalchooser/GlobalShortcutEntry.java index f68c31f4a..bcb85104a 100644 --- a/src/net/java/sip/communicator/plugin/keybindingchooser/globalchooser/GlobalShortcutEntry.java +++ b/src/net/java/sip/communicator/plugin/keybindingchooser/globalchooser/GlobalShortcutEntry.java @@ -10,6 +10,8 @@ import java.awt.*; import java.awt.event.*; +import net.java.sip.communicator.service.globalshortcut.*; + /** * Entry for a global shortcut. * @@ -104,6 +106,11 @@ public static String getShortcutText(AWTKeyStroke shortcut) int keycode = shortcut.getKeyCode(); int modifiers = shortcut.getModifiers(); + if(modifiers == GlobalShortcutService.SPECIAL_KEY_MODIFIERS) + { + return "Special"; + } + // Indicates modifiers of the keystroke boolean shiftMask = (modifiers & InputEvent.SHIFT_MASK) != 0; boolean ctrlMask = (modifiers & InputEvent.CTRL_MASK) != 0; diff --git a/src/net/java/sip/communicator/plugin/keybindingchooser/keybindingChooser.manifest.mf b/src/net/java/sip/communicator/plugin/keybindingchooser/keybindingChooser.manifest.mf index 1bb8e9043..85d2bcbd2 100644 --- a/src/net/java/sip/communicator/plugin/keybindingchooser/keybindingChooser.manifest.mf +++ b/src/net/java/sip/communicator/plugin/keybindingchooser/keybindingChooser.manifest.mf @@ -12,6 +12,9 @@ Import-Package: org.osgi.framework, net.java.sip.communicator.service.globalshortcut, net.java.sip.communicator.util, net.java.sip.communicator.util.swing, + net.java.sip.communicator.util.swing.plaf, + net.java.sip.communicator.util.skin, javax.swing, + javax.swing.plaf, javax.swing.event, javax.swing.table diff --git a/src/net/java/sip/communicator/service/globalshortcut/GlobalShortcutService.java b/src/net/java/sip/communicator/service/globalshortcut/GlobalShortcutService.java index 2753c92f2..f9f2cf373 100644 --- a/src/net/java/sip/communicator/service/globalshortcut/GlobalShortcutService.java +++ b/src/net/java/sip/communicator/service/globalshortcut/GlobalShortcutService.java @@ -16,6 +16,11 @@ */ public interface GlobalShortcutService { + /** + * Value for AWTKeyStroke's modifiers that specify a "special" key shortcut. + */ + public static final int SPECIAL_KEY_MODIFIERS = 16367; + /** * Registers an action to execute when the keystroke is typed. * @@ -34,6 +39,22 @@ public void registerShortcut(GlobalShortcutListener l, public void unregisterShortcut(GlobalShortcutListener l, AWTKeyStroke keyStroke); + /** + * Enable or disable special key detection. + * + * @param enable enable or not special key detection. + * @param callback object to be notified + */ + public void setSpecialKeyDetection(boolean enable, + GlobalShortcutListener callback); + + /** + * Get special keystroke or null if not supported or user cancels. + * + * @return special keystroke or null if not supported or user cancels + */ + public AWTKeyStroke getSpecialKey(); + /** * Reload global shortcuts. */ diff --git a/src/net/java/sip/communicator/util/swing/SIPCommDialog.java b/src/net/java/sip/communicator/util/swing/SIPCommDialog.java index 511df08f5..e1b01282c 100644 --- a/src/net/java/sip/communicator/util/swing/SIPCommDialog.java +++ b/src/net/java/sip/communicator/util/swing/SIPCommDialog.java @@ -144,7 +144,7 @@ private void initInputMap() amap.put("close", new CloseAction()); imap = this.getRootPane().getInputMap( - JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + JComponent.WHEN_IN_FOCUSED_WINDOW); imap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "close"); @@ -176,7 +176,7 @@ public void actionPerformed(ActionEvent e) /** * Adds a key - action pair for this frame. - * + * * @param keyStroke the key combination * @param action the action which will be executed when user presses the * given key combination @@ -184,9 +184,9 @@ public void actionPerformed(ActionEvent e) protected void addKeyBinding(KeyStroke keyStroke, Action action) { String actionID = action.getClass().getName(); - + amap.put(actionID, action); - + imap.put(keyStroke, actionID); } @@ -268,8 +268,8 @@ private void setCenterLocation() } /** - * Checks whether the current component will - * exceeds the screen size and if it do will set a default size + * Checks whether the current component will + * exceeds the screen size and if it do will set a default size */ private void ensureOnScreenLocationAndSize() { @@ -429,6 +429,6 @@ public void dispose() */ protected void close(boolean isEscaped) { - + } } diff --git a/src/net/java/sip/communicator/util/swing/SIPCommTextField.java b/src/net/java/sip/communicator/util/swing/SIPCommTextField.java index 5ce51d05a..151dc7c4c 100644 --- a/src/net/java/sip/communicator/util/swing/SIPCommTextField.java +++ b/src/net/java/sip/communicator/util/swing/SIPCommTextField.java @@ -28,6 +28,11 @@ public class SIPCommTextField KeyListener, DocumentListener { + /** + * Serial version UID. + */ + private static final long serialVersionUID = 0L; + /** * The default text. */