diff options
author | Yana Stamcheva <yana@jitsi.org> | 2008-07-17 23:08:57 +0000 |
---|---|---|
committer | Yana Stamcheva <yana@jitsi.org> | 2008-07-17 23:08:57 +0000 |
commit | 4460f1b63687e00d2dd408febc8dc8230e878d2b (patch) | |
tree | 170635bfbdb611cbf0c80b61287225cf9cca9cf4 | |
parent | 89ea2776f0e3ba0e87fe90371a68d51a3a83e443 (diff) | |
download | jitsi-4460f1b63687e00d2dd408febc8dc8230e878d2b.zip jitsi-4460f1b63687e00d2dd408febc8dc8230e878d2b.tar.gz jitsi-4460f1b63687e00d2dd408febc8dc8230e878d2b.tar.bz2 |
KeyBinding plugin contributed by Damian Johnson.
24 files changed, 1081 insertions, 63 deletions
@@ -622,7 +622,9 @@ bundle-ssh,bundle-plugin-sshaccregwizz, bundle-contacteventhandler,bundle-plugin-contactinfo, bundle-plugin-accountinfo,bundle-plugin-chatalerter, - bundle-plugin-statusupdate,bundle-jfontchooserlib, + bundle-plugin-statusupdate, bundle-keybindings, + bundle-plugin-keybindingChooser, + bundle-jfontchooserlib, bundle-updatecheckplugin, bundle-dict,bundle-plugin-dictaccregwizz, bundle-plugin-simpleaccreg,bundle-plugin-generalconfig, @@ -1561,7 +1563,33 @@ javax.swing.event, javax.swing.border"/> <zipfileset src="${lib.noinst}/izpack-shortcut-link.jar" prefix=""/> </jar> </target> - + + <!--BUNDLE-Keybindings--> + <target name="bundle-keybindings"> + <jar compress="false" destfile="${bundles.dest}/keybindings.jar" + manifest="${src}/net/java/sip/communicator/impl/keybindings/keybindings.manifest.mf"> + <zipfileset dir="${dest}/net/java/sip/communicator/service/keybindings" + prefix="net/java/sip/communicator/service/keybindings"/> + <zipfileset dir="${dest}/net/java/sip/communicator/impl/keybindings" + prefix="net/java/sip/communicator/impl/keybindings"/> + <zipfileset dir="${resources}/config/defaultKeybindings" + prefix="resources/config/defaultKeybindings"/> + <zipfileset src="${lib.noinst}/KeybindingUtil.jar" prefix=""/> + </jar> + </target> + + <!--BUNDLE-PLUGIN-KeybindingChooser--> + <target name="bundle-plugin-keybindingChooser"> + <jar compress="false" destfile="${bundles.dest}/keybindingChooser.jar" + manifest="${src}/net/java/sip/communicator/plugin/keybindingchooser/keybindingChooser.manifest.mf"> + <zipfileset dir="${dest}/net/java/sip/communicator/plugin/keybindingchooser" + prefix="net/java/sip/communicator/plugin/keybindingchooser"/> + <zipfileset dir="${resources}/images/plugin/keybindingchooser" + prefix="resources/images/plugin/keybindingchooser"/> + <zipfileset src="${lib.noinst}/KeybindingUtil.jar" prefix=""/> + </jar> + </target> + <!--BUNDLE-DEFAULT-RESOURCES--> <target name="bundle-resources-defaultpack"> <!-- Creates a bundle for the default resource pack."--> @@ -1570,7 +1598,7 @@ javax.swing.event, javax.swing.border"/> manifest="${src}/net/java/sip/communicator/plugin/defaultresourcepack/defaultresourcepack.manifest.mf"> <zipfileset dir="${dest}/net/java/sip/communicator/plugin/defaultresourcepack" prefix="net/java/sip/communicator/plugin/defaultresourcepack"/> - + <zipfileset dir="${resources}/colors" prefix="resources/colors"/> <zipfileset dir="${resources}/config" @@ -1585,7 +1613,7 @@ javax.swing.event, javax.swing.border"/> prefix="resources/styles"/> </jar> </target> - + <!--BUNDLE-RESOURCE-MANAGER--> <target name="bundle-resource-manager"> <!-- Creates a bundle for the Resource Management Service Impl."--> @@ -1607,14 +1635,14 @@ javax.swing.event, javax.swing.border"/> <zipfileset dir="${dest}/net/java/sip/communicator/plugin/notificationconfiguration" prefix="net/java/sip/communicator/plugin/notificationconfiguration"/> </jar> - </target> + </target> <target name="bundle-jfontchooserlib"> <!-- Creates a bundle containing the jfontchooser lib.--> <jar compress="false" destfile="${bundles.dest}/jfontchooserlib.jar" manifest="${lib.noinst}/jfontchooser.manifest.mf"> - <zipfileset src="${lib.noinst}/jfontchooser-1.0.5.jar" prefix=""/> + <zipfileset src="${lib.noinst}/jfontchooser-1.0.5.jar" prefix=""/> </jar> </target> </project> diff --git a/lib/felix.client.run.properties b/lib/felix.client.run.properties index ec1fe6f..b617027 100644 --- a/lib/felix.client.run.properties +++ b/lib/felix.client.run.properties @@ -95,6 +95,7 @@ felix.auto.start.60= \ reference:file:sc-bundles/msghistory.jar \ reference:file:sc-bundles/callhistory.jar \ reference:file:sc-bundles/audionotifier.jar \ + reference:file:sc-bundles/keybindings.jar \ reference:file:sc-bundles/notification.jar felix.auto.start.66= \ @@ -126,6 +127,7 @@ felix.auto.start.60= \ reference:file:sc-bundles/chatalerter.jar \ reference:file:sc-bundles/shutdown.jar \ reference:file:sc-bundles/statusupdate.jar \ + reference:file:sc-bundles/keybindingChooser.jar \ reference:file:sc-bundles/generalconfig.jar \ reference:file:sc-bundles/dictaccregwizz.jar diff --git a/lib/installer-exclude/KeybindingUtil.jar b/lib/installer-exclude/KeybindingUtil.jar Binary files differnew file mode 100644 index 0000000..2fa0800 --- /dev/null +++ b/lib/installer-exclude/KeybindingUtil.jar diff --git a/resources/config/defaultkeybindings/keybindings-chat b/resources/config/defaultkeybindings/keybindings-chat Binary files differnew file mode 100644 index 0000000..f166b1f --- /dev/null +++ b/resources/config/defaultkeybindings/keybindings-chat diff --git a/resources/config/defaultkeybindings/keybindings-main b/resources/config/defaultkeybindings/keybindings-main Binary files differnew file mode 100644 index 0000000..e870803 --- /dev/null +++ b/resources/config/defaultkeybindings/keybindings-main diff --git a/resources/images/plugin/keybindingchooser/credits.txt b/resources/images/plugin/keybindingchooser/credits.txt new file mode 100644 index 0000000..3f23bd4 --- /dev/null +++ b/resources/images/plugin/keybindingchooser/credits.txt @@ -0,0 +1,5 @@ +Image Sources: +pluginIcon.png - +NuoveXT by Alexandre Moore (http://nuovext.pwsp.net/) +Available under the GPLv2 +Originally 'gnome-settings-keybindings.png'
\ No newline at end of file diff --git a/resources/images/plugin/keybindingchooser/pluginIcon.png b/resources/images/plugin/keybindingchooser/pluginIcon.png Binary files differnew file mode 100644 index 0000000..90edde6 --- /dev/null +++ b/resources/images/plugin/keybindingchooser/pluginIcon.png diff --git a/resources/languages/resources.properties b/resources/languages/resources.properties index c08d5bf..74b4c95 100644 --- a/resources/languages/resources.properties +++ b/resources/languages/resources.properties @@ -522,7 +522,7 @@ msnUinAndPassword=ID and Password # plugin manager activate=Activate -desactivate=Desactivate +deactivate=Deactivate install=Install uninstall=Uninstall update=Update @@ -660,3 +660,17 @@ protocolName=GOOGLE TALK protocolDescription=The Google Talk protocol gtalkUsername=Google Talk username gtalkRegisterNewAccountText=In case you don't have a Google Talk account, click on this button to create a new one. + +# key binding chooser +keybindings=Keybindings +chat-nextTab=Next Tab +chat-previousTab=Previous Tab +chat-copy=Copy +chat-paste=Paste +chat-openSmilies=Open Smilies +chat-openHistory=Open History +chat-close=Close +chat-cut=Cut +main-rename=Rename +main-nextTab=Next Tab +main-previousTab=Previous Tab
\ No newline at end of file diff --git a/src/net/java/sip/communicator/impl/gui/GuiActivator.java b/src/net/java/sip/communicator/impl/gui/GuiActivator.java index eb4ac67..fa5c469 100644 --- a/src/net/java/sip/communicator/impl/gui/GuiActivator.java +++ b/src/net/java/sip/communicator/impl/gui/GuiActivator.java @@ -15,6 +15,7 @@ import net.java.sip.communicator.service.callhistory.*; import net.java.sip.communicator.service.configuration.*; import net.java.sip.communicator.service.contactlist.*; import net.java.sip.communicator.service.gui.*; +import net.java.sip.communicator.service.keybindings.*; import net.java.sip.communicator.service.msghistory.*; import net.java.sip.communicator.service.notification.*; import net.java.sip.communicator.service.protocol.*; @@ -51,11 +52,13 @@ public class GuiActivator implements BundleActivator private static BrowserLauncherService browserLauncherService; private static NotificationService notificationService; - + private static SystrayService systrayService; private static ResourceManagementService resourcesService; + private static KeybindingsService keybindingsService; + private static Map providerFactoriesMap = new Hashtable(); /** @@ -307,7 +310,26 @@ public class GuiActivator implements BundleActivator return systrayService; } - + + /** + * Returns the <tt>KeybindingsService</tt> obtained from the bundle context. + * + * @return the <tt>KeybindingsService</tt> obtained from the bundle context + */ + public static KeybindingsService getKeybindingsService() + { + if (keybindingsService == null) + { + ServiceReference serviceReference = bundleContext + .getServiceReference(KeybindingsService.class.getName()); + + keybindingsService = (KeybindingsService) bundleContext + .getService(serviceReference); + } + + return keybindingsService; + } + public static ResourceManagementService getResources() { if (resourcesService == null) @@ -324,7 +346,7 @@ public class GuiActivator implements BundleActivator return resourcesService; } - + /** * Returns the <tt>NotificationService</tt> obtained from the bundle context. * diff --git a/src/net/java/sip/communicator/impl/gui/customcontrols/SIPCommFrame.java b/src/net/java/sip/communicator/impl/gui/customcontrols/SIPCommFrame.java index 9b002c5..cde9640 100644 --- a/src/net/java/sip/communicator/impl/gui/customcontrols/SIPCommFrame.java +++ b/src/net/java/sip/communicator/impl/gui/customcontrols/SIPCommFrame.java @@ -15,15 +15,19 @@ import net.java.sip.communicator.impl.gui.*; import net.java.sip.communicator.impl.gui.utils.*; import net.java.sip.communicator.service.configuration.*; import net.java.sip.communicator.util.*; +import net.java.sip.communicator.service.keybindings.*; +import java.util.*; public abstract class SIPCommFrame extends JFrame + implements Observer { private Logger logger = Logger.getLogger(SIPCommFrame.class); - + ActionMap amap; InputMap imap; - + KeybindingSet bindings = null; + public SIPCommFrame() { this.setIconImage( @@ -41,8 +45,6 @@ public abstract class SIPCommFrame imap = this.getRootPane().getInputMap( JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); - - imap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "close"); } /** @@ -58,22 +60,46 @@ public abstract class SIPCommFrame } /** - * 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 + * Sets the input map to utilize a given category of keybindings. The frame + * is updated to reflect the new bindings when they change. This replaces + * any previous bindings that have been added. + * @param category set of keybindings to be utilized */ - protected void addKeyBinding(KeyStroke keyStroke, Action action) + protected void setKeybindingInput(KeybindingSet.Category category) { - String actionID = action.getClass().getName(); - - amap.put(actionID, action); - - imap.put(keyStroke, actionID); + // Removes old binding set + if (this.bindings != null) + { + this.bindings.deleteObserver(this); + resetInputMap(); + } + + // Adds new bindings to input map + KeybindingsService service = GuiActivator.getKeybindingsService(); + this.bindings = service.getBindings(category); + for (KeyStroke key : this.bindings.getBindings().keySet()) + { + String action = this.bindings.getBindings().get(key); + imap.put(key, action); + } + + this.bindings.addObserver(this); + } /** + * Bindings the string representation for a keybinding to the action that + * will be executed. + * @param binding string representation of action used by input map + * @param action the action which will be executed when user presses the + * given key combination + */ + protected void addKeybindingAction(String binding, Action action) + { + amap.put(binding, action); + } + + /** * Before closing the application window saves the current size and position * through the <tt>ConfigurationService</tt>. */ @@ -95,27 +121,29 @@ public abstract class SIPCommFrame { ConfigurationService configService = GuiActivator.getConfigurationService(); - + String className = this.getClass().getName(); - - try { + + try + { configService.setProperty( className + ".width", new Integer(getWidth())); - + configService.setProperty( className + ".height", new Integer(getHeight())); - + configService.setProperty( className + ".x", new Integer(getX())); - + configService.setProperty( className + ".y", new Integer(getY())); } - catch (PropertyVetoException e1) { + catch (PropertyVetoException e1) + { logger.error("The proposed property change " + "represents an unacceptable value"); } @@ -257,7 +285,7 @@ public abstract class SIPCommFrame */ public void setVisible(boolean isVisible) { - if(isVisible) + if (isVisible) { this.pack(); this.setSizeAndLocation(); @@ -278,7 +306,29 @@ public abstract class SIPCommFrame super.dispose(); } - + + private void resetInputMap() + { + imap.clear(); + imap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "close"); + } + + // Listens for changes in binding sets so they can be reflected in the input + // map + public void update(Observable obs, Object arg) + { + if (obs instanceof KeybindingSet) + { + KeybindingSet changedBindings = (KeybindingSet) obs; + resetInputMap(); + for (KeyStroke binding : changedBindings.getBindings().keySet()) + { + String action = changedBindings.getBindings().get(binding); + imap.put(binding, action); + } + } + } + /** * All functions implemented in this method will be invoked when user * presses the Escape key. diff --git a/src/net/java/sip/communicator/impl/gui/main/MainFrame.java b/src/net/java/sip/communicator/impl/gui/main/MainFrame.java index c03abe0..6ac6d51 100755 --- a/src/net/java/sip/communicator/impl/gui/main/MainFrame.java +++ b/src/net/java/sip/communicator/impl/gui/main/MainFrame.java @@ -37,6 +37,7 @@ import net.java.sip.communicator.service.gui.Container; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.util.*; +import net.java.sip.communicator.service.keybindings.*; import org.osgi.framework.*; @@ -136,12 +137,10 @@ public class MainFrame */ private void init() { - this.addKeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0), - new RenameAction()); - this.addKeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, - KeyEvent.ALT_DOWN_MASK), new ForwordTabAction()); - this.addKeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, - KeyEvent.ALT_DOWN_MASK), new BackwordTabAction()); + this.setKeybindingInput(KeybindingSet.Category.MAIN); + this.addKeybindingAction("main-rename", new RenameAction()); + this.addKeybindingAction("main-nextTab", new ForwordTabAction()); + this.addKeybindingAction("main-previousTab", new BackwordTabAction()); this.contactListPanel.add(tabbedPane, BorderLayout.CENTER); this.contactListPanel.add(callManager, BorderLayout.SOUTH); diff --git a/src/net/java/sip/communicator/impl/gui/main/chat/ChatWindow.java b/src/net/java/sip/communicator/impl/gui/main/chat/ChatWindow.java index a84b46a..f63f5e9 100755 --- a/src/net/java/sip/communicator/impl/gui/main/chat/ChatWindow.java +++ b/src/net/java/sip/communicator/impl/gui/main/chat/ChatWindow.java @@ -24,6 +24,7 @@ import net.java.sip.communicator.impl.gui.main.chat.toolBars.*; import net.java.sip.communicator.impl.gui.utils.*; import net.java.sip.communicator.service.gui.*; import net.java.sip.communicator.service.gui.Container; +import net.java.sip.communicator.service.keybindings.*; import net.java.sip.communicator.util.*; import org.osgi.framework.*; @@ -119,26 +120,14 @@ public class ChatWindow this.initPluginComponents(); - this.addKeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, - KeyEvent.ALT_DOWN_MASK), new ForwordTabAction()); - this.addKeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, - KeyEvent.ALT_DOWN_MASK), new BackwordTabAction()); - this.addKeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, - KeyEvent.CTRL_DOWN_MASK), new CopyAction()); - this.addKeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, - KeyEvent.SHIFT_DOWN_MASK), new PasteAction()); - this.addKeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_C, - KeyEvent.META_MASK), new CopyAction()); - this.addKeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_V, - KeyEvent.META_MASK), new PasteAction()); - this.addKeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_M, - KeyEvent.CTRL_DOWN_MASK), new OpenSmileyAction()); - this.addKeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_H, - KeyEvent.CTRL_DOWN_MASK), new OpenHistoryAction()); - this.addKeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_W, - KeyEvent.META_MASK), new CloseAction()); - this.addKeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_W, - KeyEvent.CTRL_DOWN_MASK), new CloseAction()); + this.setKeybindingInput(KeybindingSet.Category.CHAT); + this.addKeybindingAction("chat-nextTab", new ForwordTabAction()); + this.addKeybindingAction("chat-previousTab", new BackwordTabAction()); + this.addKeybindingAction("chat-copy", new CopyAction()); + this.addKeybindingAction("chat-paste", new PasteAction()); + this.addKeybindingAction("chat-openSmilies", new OpenSmileyAction()); + this.addKeybindingAction("chat-openHistory", new OpenHistoryAction()); + this.addKeybindingAction("chat-close", new CloseAction()); this.addWindowListener(new ChatWindowAdapter()); } diff --git a/src/net/java/sip/communicator/impl/gui/swing.ui.manifest.mf b/src/net/java/sip/communicator/impl/gui/swing.ui.manifest.mf index bdad5b5..3d07e66 100644 --- a/src/net/java/sip/communicator/impl/gui/swing.ui.manifest.mf +++ b/src/net/java/sip/communicator/impl/gui/swing.ui.manifest.mf @@ -7,7 +7,6 @@ Export-Package: net.java.sip.communicator.service.gui, net.java.sip.communicator.service.gui.event Import-Package: org.osgi.framework, net.java.sip.communicator.util, - net.java.sip.communicator.service.resources, net.java.sip.communicator.service.configuration, net.java.sip.communicator.service.configuration.event, net.java.sip.communicator.service.protocol, @@ -25,6 +24,8 @@ Import-Package: org.osgi.framework, net.java.sip.communicator.service.notification, net.java.sip.communicator.service.systray, net.java.sip.communicator.service.contacteventhandler, + net.java.sip.communicator.service.keybindings, + net.java.sip.communicator.service.resources, javax.swing, javax.swing.event, javax.swing.table, @@ -40,5 +41,4 @@ Import-Package: org.osgi.framework, javax.swing.undo, javax.swing.border, net.java.sip.communicator.service.audionotifier, - org.jdesktop.jdic.desktop, - say.swing + org.jdesktop.jdic.desktop diff --git a/src/net/java/sip/communicator/impl/keybindings/KeybindingSetImpl.java b/src/net/java/sip/communicator/impl/keybindings/KeybindingSetImpl.java new file mode 100644 index 0000000..070aab3 --- /dev/null +++ b/src/net/java/sip/communicator/impl/keybindings/KeybindingSetImpl.java @@ -0,0 +1,106 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.keybindings; + +import java.io.File; +import java.util.*; + +import javax.swing.KeyStroke; + +import net.java.sip.communicator.service.keybindings.KeybindingSet; + +/** + * Default implementation for the wrapper of keybinding sets. + * + * @author Damian Johnson + */ +class KeybindingSetImpl + extends KeybindingSet +{ + private LinkedHashMap<KeyStroke, String> bindings; + + private Category category; + + // Destination where custom bindings are saved, null if it couldn't be + // secured + private File customFile; + + // Flag indicating that the associated service has been stopped + private boolean isInvalidated = false; + + KeybindingSetImpl(Map<KeyStroke, String> initial, Category category, + File saveDst) + { + this.bindings = new LinkedHashMap<KeyStroke, String>(initial); + this.category = category; + this.customFile = saveDst; + } + + /** + * Provides current keybinding mappings. + * + * @return mapping of keystrokes to the string representation of the actions + * they perform + */ + public LinkedHashMap<KeyStroke, String> getBindings() + { + return new LinkedHashMap<KeyStroke, String>(this.bindings); + } + + /** + * Resets the bindings and notifies the observer's listeners if they've + * changed. If the bindings can be written then they will be. + * + * @param newBindings new keybindings to be held + */ + public void setBindings(Map<KeyStroke, String> newBindings) + { + if (!this.bindings.equals(newBindings)) + { + this.bindings = new LinkedHashMap<KeyStroke, String>(newBindings); + setChanged(); + notifyObservers(this); + } + } + + /** + * Provides the portion of the UI to which the bindings belong. + * + * @return binding category + */ + public Category getCategory() + { + return this.category; + } + + /** + * Provides if the keybindings can be written when changed or not. + * + * @return true if bindings can be written when changed, false otherwise + */ + boolean isWritable() + { + return !this.isInvalidated && this.customFile != null; + } + + /** + * Provides the file where custom bindings are to be saved. + * + * @return custom bindings save destination + */ + File getCustomFile() + { + return this.customFile; + } + + /** + * Invalidates reference to custom output, preventing further writes. + */ + void invalidate() + { + this.isInvalidated = true; + } +} diff --git a/src/net/java/sip/communicator/impl/keybindings/KeybindingsActivator.java b/src/net/java/sip/communicator/impl/keybindings/KeybindingsActivator.java new file mode 100644 index 0000000..4e6c621 --- /dev/null +++ b/src/net/java/sip/communicator/impl/keybindings/KeybindingsActivator.java @@ -0,0 +1,56 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.keybindings; + +import net.java.sip.communicator.service.keybindings.KeybindingsService; +import net.java.sip.communicator.util.Logger; + +import org.osgi.framework.*; + +/** + * Enabling and disabling osgi functionality for keybindings. + * @author Damian Johnson + */ +public class KeybindingsActivator + implements BundleActivator +{ + private static final Logger logger = + Logger.getLogger(KeybindingsActivator.class); + + private KeybindingsServiceImpl keybindingsService = null; + + /** + * Called when this bundle is started. + * @param context The execution context of the bundle being started. + */ + public void start(BundleContext context) + { + if (this.keybindingsService == null) + { + logger.debug("Service Impl: " + getClass().getName() + + " [ STARTED ]"); + this.keybindingsService = new KeybindingsServiceImpl(); + this.keybindingsService.start(context); + context.registerService(KeybindingsService.class.getName(), + this.keybindingsService, null); + } + } + + /** + * Called when this bundle is stopped so the Framework can perform the + * bundle-specific activities necessary to stop the bundle. + * @param context The execution context of the bundle being stopped. + */ + public void stop(BundleContext context) + { + if (this.keybindingsService != null) + { + this.keybindingsService.stop(); + this.keybindingsService = null; + } + } +}
\ No newline at end of file diff --git a/src/net/java/sip/communicator/impl/keybindings/KeybindingsServiceImpl.java b/src/net/java/sip/communicator/impl/keybindings/KeybindingsServiceImpl.java new file mode 100644 index 0000000..c8b67ed --- /dev/null +++ b/src/net/java/sip/communicator/impl/keybindings/KeybindingsServiceImpl.java @@ -0,0 +1,278 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.impl.keybindings; + +import java.io.*; +import java.text.ParseException; +import java.util.*; + +import javax.swing.KeyStroke; + +import org.osgi.framework.*; + +import chooser.Persistence; + +import net.java.sip.communicator.service.fileaccess.FileAccessService; +import net.java.sip.communicator.service.keybindings.*; +import net.java.sip.communicator.util.Logger; + +/** + * Service that concerns keybinding mappings used by various parts of the UI. + * Persistence is handled as follows when started: + * <ol> + * <li>Load default bindings from relative directory</li> + * <li>Attempt to load custom bindings and resolve any duplicates</li> + * <li>If merged bindings differ from the custom bindings then this attempts to + * save the merged version</li> + * </ol> + * Custom bindings attempt to be written again whenever they're changed if the + * service is running. Each category of keybindings are stored in its own file. + * @author Damian Johnson + */ +class KeybindingsServiceImpl + implements KeybindingsService, Observer +{ + private static final Logger logger = + Logger.getLogger(KeybindingsServiceImpl.class); + + // Name of the relative directory that holds default bindings + private static final String DEFAULT_KEYBINDING_DIR = + "/resources/config/defaultKeybindings"; + + // Name of the directory that holds custom bindings + private static final String CUSTOM_KEYBINDING_DIR = "keybindings"; + + // Flag indicating if service is running + private boolean isRunning = false; + + // Loaded keybinding mappings, maps to null if defaults failed to be loaded + private final HashMap <KeybindingSet.Category, KeybindingSetImpl> bindings = + new HashMap <KeybindingSet.Category, KeybindingSetImpl>(); + + /** + * Starts the KeybindingService, for each keybinding category retrieving the + * default bindings then overwriting them with any custom bindings that can + * be retrieved. This writes the merged copy back if it differs from the + * custom bindings. This is a no-op if the service has already been started. + * @param bc the currently valid OSGI bundle context. + */ + synchronized void start(BundleContext bc) + { + if (this.isRunning) return; + for (KeybindingSet.Category category : KeybindingSet.Category.values()) + { + // Retrieves default bindings + Persistence format = category.getFormat(); + LinkedHashMap <KeyStroke, String> defaultBindings; + try + { + String defaultPath = + DEFAULT_KEYBINDING_DIR + "/" + category.getResource(); + InputStream in = getClass().getResourceAsStream(defaultPath); + defaultBindings = format.load(in); + } + catch (IOException exc) + { + logger.error("default bindings set missing: " + + category.getResource(), exc); + this.bindings.put(category, null); + continue; + } + catch (ParseException exc) + { + logger.error("unable to parse default bindings set: " + + category.getResource(), exc); + this.bindings.put(category, null); + continue; + } + + // Attempts to retrieve custom bindings + String customPath = + CUSTOM_KEYBINDING_DIR + "/" + category.getResource(); + File customFile; + try + { + ServiceReference faServiceReference = + bc.getServiceReference(FileAccessService.class + .getName()); + FileAccessService faService = + (FileAccessService) bc.getService(faServiceReference); + + // Makes directory for custom bindings if it doesn't exist + File customDir = + faService + .getPrivatePersistentDirectory(CUSTOM_KEYBINDING_DIR); + if (!customDir.exists()) customDir.mkdir(); + + // Gets file access service to reference persistent storage + // of the user + customFile = faService.getPrivatePersistentFile(customPath); + } + catch (Exception exc) + { + String msg = + "unable to secure file for custom bindings (" + + customPath + + "), using defaults but won't be able to save changes"; + logger.error(msg, exc); + KeybindingSetImpl newSet = + new KeybindingSetImpl(defaultBindings, category, null); + this.bindings.put(category, newSet); + newSet.addObserver(this); + continue; + } + + LinkedHashMap <KeyStroke, String> customBindings = null; + if (customFile.exists()) + { + try + { + FileInputStream in = new FileInputStream(customFile); + customBindings = format.load(in); + in.close(); + } + catch (Exception exc) + { + // If either an IO or ParseException occur then we skip + // loading custom bindings + } + } + + // Merges custom bindings + LinkedHashMap <KeyStroke, String> merged = + new LinkedHashMap <KeyStroke, String>(); + if (customBindings != null) + { + LinkedHashMap <KeyStroke, String> customTmp = + new LinkedHashMap <KeyStroke, String>(customBindings); + + for (KeyStroke shortcut : defaultBindings.keySet()) + { + String action = defaultBindings.get(shortcut); + + if (customTmp.containsValue(action)) + { + KeyStroke custom = null; + for (KeyStroke customShortcut : customTmp.keySet()) + { + if (customTmp.get(customShortcut).equals(action)) + { + custom = customShortcut; + break; + } + } + + assert custom != null; + customTmp.remove(custom); + merged.put(custom, action); + } + else + { + merged.put(shortcut, action); + } + } + } + else + { + merged = defaultBindings; + } + + // Writes merged result + if (!merged.equals(customBindings)) + { + try + { + FileOutputStream out = new FileOutputStream(customFile); + format.save(out, merged); + out.close(); + } + catch (IOException exc) + { + logger.error("unable to write to: " + + customFile.getAbsolutePath(), exc); + } + } + + KeybindingSetImpl newSet = + new KeybindingSetImpl(merged, category, customFile); + this.bindings.put(category, newSet); + newSet.addObserver(this); + } + + this.isRunning = true; + } + + /** + * Invalidates references to custom bindings, preventing further writes. + */ + synchronized void stop() + { + if (!this.isRunning) return; + for (KeybindingSetImpl bindingSet : this.bindings.values()) + { + bindingSet.invalidate(); + } + this.bindings.clear(); + this.isRunning = false; + } + + /** + * Provides the bindings associated with a given category. This may be null + * if the default bindings failed to be loaded. + * @param category segment of the UI for which bindings should be retrieved + * @return mappings of keystrokes to the string representation of their + * actions + * @throws UnsupportedOperationException if the service isn't running + */ + public synchronized KeybindingSet getBindings( + KeybindingSet.Category category) + { + if (!this.isRunning) throw new UnsupportedOperationException(); + + // Started services should have all categories + assert this.bindings.containsKey(category); + return this.bindings.get(category); + } + + // Listens for changes in binding sets so changes can be written + public void update(Observable obs, Object arg) + { + if (obs instanceof KeybindingSetImpl) + { + KeybindingSetImpl changedBindings = (KeybindingSetImpl) obs; + + // Attempts to avoid lock if unwritable (this works since bindings + // can't become re-writable) + if (changedBindings.isWritable()) + { + synchronized (this) + { + if (changedBindings.isWritable()) + { + // Writes new bindings to custom file + File customFile = changedBindings.getCustomFile(); + + try + { + FileOutputStream out = + new FileOutputStream(customFile); + Persistence format = + changedBindings.getCategory().getFormat(); + format.save(out, changedBindings.getBindings()); + out.close(); + } + catch (IOException exc) + { + logger.error("unable to write to: " + + customFile.getAbsolutePath(), exc); + } + } + } + } + } + } +}
\ No newline at end of file diff --git a/src/net/java/sip/communicator/impl/keybindings/keybindings.manifest.mf b/src/net/java/sip/communicator/impl/keybindings/keybindings.manifest.mf new file mode 100644 index 0000000..3fb715f --- /dev/null +++ b/src/net/java/sip/communicator/impl/keybindings/keybindings.manifest.mf @@ -0,0 +1,11 @@ +Bundle-Activator: net.java.sip.communicator.impl.keybindings.KeybindingsActivator +Bundle-Name: Keybindings +Bundle-Description: Provides management and persistence of keyboard shortcuts. +Bundle-Vendor: sip-communicator.org +Bundle-Version: 0.0.1 +Import-Package: org.osgi.framework, + net.java.sip.communicator.util, + net.java.sip.communicator.service.fileaccess, + net.java.sip.communicator.service.gui, + javax.swing +Export-Package: net.java.sip.communicator.service.keybindings
\ No newline at end of file diff --git a/src/net/java/sip/communicator/plugin/keybindingchooser/KeybindingChooserActivator.java b/src/net/java/sip/communicator/plugin/keybindingchooser/KeybindingChooserActivator.java new file mode 100644 index 0000000..ea75d88 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/keybindingchooser/KeybindingChooserActivator.java @@ -0,0 +1,78 @@ +/* + * SIP Communicator, 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; + +import net.java.sip.communicator.service.gui.*; +import net.java.sip.communicator.service.keybindings.KeybindingsService; +import net.java.sip.communicator.service.resources.*; +import net.java.sip.communicator.util.Logger; + +import org.osgi.framework.*; + +/** + * Enabling and disabling osgi functionality for the keybinding chooser. + * + * @author Damian Johnson + */ +public class KeybindingChooserActivator + implements BundleActivator +{ + private static final Logger logger = + Logger.getLogger(KeybindingChooserActivator.class); + + private static BundleContext bundleContext; + + public static ResourceManagementService resourcesService; + + /** + * Called when this bundle is started. + * @param context The execution context of the bundle being started. + */ + public void start(BundleContext context) + { + bundleContext = context; + + logger.debug("Service Impl: " + getClass().getName() + " [ STARTED ]"); + + ServiceReference keybindingRef + = context.getServiceReference(KeybindingsService.class.getName()); + + KeybindingsService keybingingsService + = (KeybindingsService) context.getService(keybindingRef); + + KeybindingsConfigForm keybindingsManager = + new KeybindingsConfigForm(keybingingsService); + + context.registerService(ConfigurationForm.class.getName(), + keybindingsManager, + null); + } + + /** + * Stops this bundles. + */ + public void stop(BundleContext arg0) throws Exception + {} + + public static ResourceManagementService getResources() + { + if (resourcesService == null) + { + ServiceReference serviceReference = + bundleContext.getServiceReference( + ResourceManagementService.class.getName()); + + if(serviceReference == null) + return null; + + resourcesService = (ResourceManagementService) + bundleContext.getService(serviceReference); + } + + return resourcesService; + } +}
\ No newline at end of file diff --git a/src/net/java/sip/communicator/plugin/keybindingchooser/KeybindingsConfigForm.java b/src/net/java/sip/communicator/plugin/keybindingchooser/KeybindingsConfigForm.java new file mode 100644 index 0000000..81c20a4 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/keybindingchooser/KeybindingsConfigForm.java @@ -0,0 +1,203 @@ +/* + * SIP Communicator, 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; + +import java.awt.*; +import java.awt.event.*; +import java.util.*; + +import javax.swing.*; + +import chooser.*; + +import net.java.sip.communicator.service.gui.ConfigurationForm; +import net.java.sip.communicator.service.keybindings.*; + +/** + * The <tt>ConfigurationForm</tt> that would be added to the settings + * configuration to configure the application keybindings. + * + * @author Damian Johnson + */ +public class KeybindingsConfigForm + extends JPanel + implements ConfigurationForm +{ + private static final long serialVersionUID = 0; + private HashMap <KeybindingSet, SIPChooser> choosers = + new HashMap <KeybindingSet, SIPChooser>(); + + public KeybindingsConfigForm(KeybindingsService service) + { + super(new BorderLayout()); + + setFocusable(true); + JTabbedPane chooserPanes = new JTabbedPane(JTabbedPane.LEFT); + + // deselects entries awaiting input when focus is lost + this.addFocusListener(new FocusAdapter() + { + public void focusLost(FocusEvent event) + { + for (SIPChooser chooser : choosers.values()) + { + chooser.setSelected(null); + } + } + }); + + for (KeybindingSet.Category category : KeybindingSet.Category.values()) + { + KeybindingSet bindingSet = service.getBindings(category); + if (bindingSet == null) continue; // defaults failed to load + + SIPChooser newChooser = new SIPChooser(); + newChooser.putAllBindings(bindingSet.getBindings()); + + JPanel chooserWrapper = new JPanel(new BorderLayout()); + chooserWrapper.add(newChooser, BorderLayout.NORTH); + JScrollPane scroller = new JScrollPane(chooserWrapper); + + // adds listener that receives events to set bindings + this.addKeyListener(newChooser.makeAdaptor()); + + chooserPanes.addTab(getReadableConstant(category.toString()), + scroller); + this.choosers.put(bindingSet, newChooser); + } + + add(chooserPanes); + + JButton apply = new JButton("Apply"); + apply.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent event) + { + for (KeybindingSet set : choosers.keySet()) + set.setBindings(choosers.get(set).getBindingMap()); + } + }); + + JPanel bottomWrapper = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + bottomWrapper.add(apply); + add(bottomWrapper, BorderLayout.SOUTH); + } + + /** + * Implements the <tt>ConfigurationForm.getTitle()</tt> method. Returns + * the title of this configuration form. + */ + public String getTitle() + { + return Resources.getString("keybindings"); + } + + /** + * Implements the <tt>ConfigurationForm.getIcon()</tt> method. Returns the + * icon of this configuration form. + */ + public byte[] getIcon() + { + return Resources.getImageInBytes("keybindingPluginIcon"); + } + + /** + * Implements the <tt>ConfigurationForm.getForm()</tt> method. Returns the + * component corresponding to this configuration form. + */ + public Object getForm() + { + return this; + } + + /** + * Provides a more readable version of constant names. Spaces replace + * underscores and this changes the input to lowercase except the first + * letter of each word. For instance, "RARE_CARDS" would become "Rare + * Cards". + * @param input string to be converted + * @return reader friendly variant of constant name + */ + private static String getReadableConstant(String input) + { + char[] name = input.toCharArray(); + + boolean isStartOfWord = true; + for (int i = 0; i < name.length; ++i) + { + char chr = name[i]; + if (chr == '_') name[i] = ' '; + else if (isStartOfWord) name[i] = Character.toUpperCase(chr); + else name[i] = Character.toLowerCase(chr); + isStartOfWord = chr == '_'; + } + + return new String(name); + } + + /** + * Keybinding chooser with customized appearance and functionality for the + * SIP Communicator. + */ + private class SIPChooser + extends BindingChooser + { + private static final long serialVersionUID = 0; + + // Provides mapping of UI labels to internal action names + private HashMap <String, String> actionLabels = + new HashMap <String, String>(); + + // Calls focus to the form so keyboard events are received + protected void onClick(MouseEvent event, BindingEntry entry, + BindingEntry.Field field) + { + super.onClick(event, entry, field); + KeybindingsConfigForm.this.requestFocus(); + } + + public boolean putBinding(BindingEntry newEntry, int index) + { + // Converts to I18N strings for UI + String actionInternal = newEntry.getAction(); + String actionLabel = Resources.getString(actionInternal); + this.actionLabels.put(actionLabel, actionInternal); + newEntry.setAction(actionLabel); + + // Overwrites the default entry layout to stretch shortcut field + newEntry.removeAll(); + newEntry.setLayout(new BorderLayout()); + + JPanel left = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0)); + left.add(newEntry.getField(BindingEntry.Field.INDENT)); + left.add(newEntry.getField(BindingEntry.Field.ACTION)); + newEntry.add(left, BorderLayout.WEST); + newEntry.add(newEntry.getField(BindingEntry.Field.SHORTCUT)); + + return super.putBinding(newEntry, index); + } + + public LinkedHashMap <KeyStroke, String> getBindingMap() + { + // Translates I18N strings back to internal action labels + LinkedHashMap <KeyStroke, String> bindings = + new LinkedHashMap <KeyStroke, String>(); + for (BindingEntry entry : super.getBindings()) + { + bindings.put(entry.getShortcut(), this.actionLabels.get(entry + .getAction())); + } + + return bindings; + } + } + + public int getIndex() + { + return -1; + } +}
\ No newline at end of file diff --git a/src/net/java/sip/communicator/plugin/keybindingchooser/Resources.java b/src/net/java/sip/communicator/plugin/keybindingchooser/Resources.java new file mode 100644 index 0000000..eafcae5 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/keybindingchooser/Resources.java @@ -0,0 +1,63 @@ +/* + * SIP Communicator, 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; + +import java.io.*; + +import net.java.sip.communicator.util.*; + +/** + * The <tt>Resources</tt> class manages the access to the internationalization + * properties files and the image resources used in this plugin. + * + * @author Yana Stamcheva + */ +public class Resources +{ + private static Logger log = Logger.getLogger(Resources.class); + + /** + * Returns an internationalized string corresponding to the given key. + * + * @param key The key of the string. + * @return An internationalized string corresponding to the given key. + */ + public static String getString(String key) + { + return KeybindingChooserActivator.getResources().getI18NString(key); + } + + /** + * Loads an image from a given image identifier. + * + * @param imageID The identifier of the image. + * @return The image for the given identifier. + */ + public static byte[] getImageInBytes(String imageID) + { + InputStream in = KeybindingChooserActivator. + getResources().getImageInputStream(imageID); + + if(in == null) + return null; + + byte[] image = null; + + try + { + image = new byte[in.available()]; + in.read(image); + } + catch (IOException e) + { + log.error("Failed to load image:" + imageID, e); + } + + return image; + } +} diff --git a/src/net/java/sip/communicator/plugin/keybindingchooser/keybindingChooser.manifest.mf b/src/net/java/sip/communicator/plugin/keybindingchooser/keybindingChooser.manifest.mf new file mode 100644 index 0000000..b7233e9 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/keybindingchooser/keybindingChooser.manifest.mf @@ -0,0 +1,12 @@ +Bundle-Activator: net.java.sip.communicator.plugin.keybindingchooser.KeybindingChooserActivator +Bundle-Name: Keybinding Chooser +Bundle-Description: Provides configuring UI for keyboard shortcuts. +Bundle-Vendor: sip-communicator.org +Bundle-Version: 0.0.1 +Import-Package: org.osgi.framework, + net.java.sip.communicator.util, + net.java.sip.communicator.service.gui, + net.java.sip.communicator.service.keybindings, + net.java.sip.communicator.service.resources, + javax.swing, + javax.swing.event
\ No newline at end of file diff --git a/src/net/java/sip/communicator/plugin/keybindingchooser/resources.properties b/src/net/java/sip/communicator/plugin/keybindingchooser/resources.properties new file mode 100644 index 0000000..141a680 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/keybindingchooser/resources.properties @@ -0,0 +1 @@ +pluginIcon=resources/images/plugin/keybindingchooser/pluginIcon.png
\ No newline at end of file diff --git a/src/net/java/sip/communicator/service/keybindings/KeybindingSet.java b/src/net/java/sip/communicator/service/keybindings/KeybindingSet.java new file mode 100644 index 0000000..f961310 --- /dev/null +++ b/src/net/java/sip/communicator/service/keybindings/KeybindingSet.java @@ -0,0 +1,77 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.service.keybindings; + +import java.util.*; + +import javax.swing.KeyStroke; + +import chooser.Persistence; + +/** + * Wrapper for keybinding sets. Observers are notified when there's a change. + * @author Damian Johnson + */ +public abstract class KeybindingSet + extends Observable +{ + /** + * Provides current keybinding mappings. + * @return mapping of keystrokes to the string representation of the actions + * they perform + */ + public abstract LinkedHashMap <KeyStroke, String> getBindings(); + + /** + * Resets the bindings and notifies the observer's listeners if they've + * changed. + * @param newBindings new keybindings to be held + */ + public abstract void setBindings(Map <KeyStroke, String> newBindings); + + /** + * Provides the portion of the UI to which the bindings belong. + * @return binding category + */ + public abstract Category getCategory(); + + /** + * Keybinding sets available in the Sip Communicator. + */ + public enum Category + { + CHAT("keybindings-chat", Persistence.SERIAL_HASH), + MAIN("keybindings-main", Persistence.SERIAL_HASH); + + private final String resource; + private final Persistence persistenceFormat; + + Category(String resource, Persistence format) + { + this.resource = resource; + this.persistenceFormat = format; + } + + /** + * Provides the name keybindings are saved and loaded with. + * @return filename used for keybindings + */ + public String getResource() + { + return this.resource; + } + + /** + * Provides the format used to save and load keybinding resources. + * @return style of persistence used by keybindings + */ + public Persistence getFormat() + { + return this.persistenceFormat; + } + } +} diff --git a/src/net/java/sip/communicator/service/keybindings/KeybindingsService.java b/src/net/java/sip/communicator/service/keybindings/KeybindingsService.java new file mode 100644 index 0000000..186a69b --- /dev/null +++ b/src/net/java/sip/communicator/service/keybindings/KeybindingsService.java @@ -0,0 +1,24 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.service.keybindings; + +/** + * The <tt>KeybindingService</tt> handles the distribution of configurable and + * persistent keybinding sets. + * @author Damian Johnson + */ +public interface KeybindingsService +{ + /** + * Provides the bindings associated with a given category. This may be null + * if the default bindings failed to be loaded. + * @param category segment of the UI for which bindings should be retrieved + * @return mappings of keystrokes to the string representation of their + * actions + */ + KeybindingSet getBindings(KeybindingSet.Category category); +}
\ No newline at end of file |