aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYana Stamcheva <yana@jitsi.org>2008-07-17 23:08:57 +0000
committerYana Stamcheva <yana@jitsi.org>2008-07-17 23:08:57 +0000
commit4460f1b63687e00d2dd408febc8dc8230e878d2b (patch)
tree170635bfbdb611cbf0c80b61287225cf9cca9cf4
parent89ea2776f0e3ba0e87fe90371a68d51a3a83e443 (diff)
downloadjitsi-4460f1b63687e00d2dd408febc8dc8230e878d2b.zip
jitsi-4460f1b63687e00d2dd408febc8dc8230e878d2b.tar.gz
jitsi-4460f1b63687e00d2dd408febc8dc8230e878d2b.tar.bz2
KeyBinding plugin contributed by Damian Johnson.
-rw-r--r--build.xml40
-rw-r--r--lib/felix.client.run.properties2
-rw-r--r--lib/installer-exclude/KeybindingUtil.jarbin0 -> 164002 bytes
-rw-r--r--resources/config/defaultkeybindings/keybindings-chatbin0 -> 567 bytes
-rw-r--r--resources/config/defaultkeybindings/keybindings-mainbin0 -> 349 bytes
-rw-r--r--resources/images/plugin/keybindingchooser/credits.txt5
-rw-r--r--resources/images/plugin/keybindingchooser/pluginIcon.pngbin0 -> 2078 bytes
-rw-r--r--resources/languages/resources.properties16
-rw-r--r--src/net/java/sip/communicator/impl/gui/GuiActivator.java28
-rw-r--r--src/net/java/sip/communicator/impl/gui/customcontrols/SIPCommFrame.java98
-rwxr-xr-xsrc/net/java/sip/communicator/impl/gui/main/MainFrame.java11
-rwxr-xr-xsrc/net/java/sip/communicator/impl/gui/main/chat/ChatWindow.java29
-rw-r--r--src/net/java/sip/communicator/impl/gui/swing.ui.manifest.mf6
-rw-r--r--src/net/java/sip/communicator/impl/keybindings/KeybindingSetImpl.java106
-rw-r--r--src/net/java/sip/communicator/impl/keybindings/KeybindingsActivator.java56
-rw-r--r--src/net/java/sip/communicator/impl/keybindings/KeybindingsServiceImpl.java278
-rw-r--r--src/net/java/sip/communicator/impl/keybindings/keybindings.manifest.mf11
-rw-r--r--src/net/java/sip/communicator/plugin/keybindingchooser/KeybindingChooserActivator.java78
-rw-r--r--src/net/java/sip/communicator/plugin/keybindingchooser/KeybindingsConfigForm.java203
-rw-r--r--src/net/java/sip/communicator/plugin/keybindingchooser/Resources.java63
-rw-r--r--src/net/java/sip/communicator/plugin/keybindingchooser/keybindingChooser.manifest.mf12
-rw-r--r--src/net/java/sip/communicator/plugin/keybindingchooser/resources.properties1
-rw-r--r--src/net/java/sip/communicator/service/keybindings/KeybindingSet.java77
-rw-r--r--src/net/java/sip/communicator/service/keybindings/KeybindingsService.java24
24 files changed, 1081 insertions, 63 deletions
diff --git a/build.xml b/build.xml
index 3b006ab..212a4f4 100644
--- a/build.xml
+++ b/build.xml
@@ -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
new file mode 100644
index 0000000..2fa0800
--- /dev/null
+++ b/lib/installer-exclude/KeybindingUtil.jar
Binary files differ
diff --git a/resources/config/defaultkeybindings/keybindings-chat b/resources/config/defaultkeybindings/keybindings-chat
new file mode 100644
index 0000000..f166b1f
--- /dev/null
+++ b/resources/config/defaultkeybindings/keybindings-chat
Binary files differ
diff --git a/resources/config/defaultkeybindings/keybindings-main b/resources/config/defaultkeybindings/keybindings-main
new file mode 100644
index 0000000..e870803
--- /dev/null
+++ b/resources/config/defaultkeybindings/keybindings-main
Binary files differ
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
new file mode 100644
index 0000000..90edde6
--- /dev/null
+++ b/resources/images/plugin/keybindingchooser/pluginIcon.png
Binary files differ
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