KeyBinding plugin contributed by Damian Johnson.

cusax-fix
Yana Stamcheva 18 years ago
parent 89ea2776f0
commit 4460f1b636

@ -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>

@ -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

@ -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'

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

@ -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

@ -15,6 +15,7 @@
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 static SystrayService getSystrayService()
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 static ResourceManagementService getResources()
return resourcesService;
}
/**
* Returns the <tt>NotificationService</tt> obtained from the bundle context.
*

@ -15,15 +15,19 @@
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 SIPCommFrame()
imap = this.getRootPane().getInputMap(
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
imap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "close");
}
/**
@ -58,21 +60,45 @@ public void actionPerformed(ActionEvent e)
}
/**
* Adds a key - action pair for this frame.
*
* @param keyStroke the key combination
* @param action the action which will be executed when user presses the
* given key combination
* 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 @@ private void saveSizeAndLocation()
{
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 @@ private void ensureOnScreenLocationAndSize()
*/
public void setVisible(boolean isVisible)
{
if(isVisible)
if (isVisible)
{
this.pack();
this.setSizeAndLocation();
@ -278,7 +306,29 @@ public void dispose()
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.

@ -37,6 +37,7 @@
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 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);

@ -24,6 +24,7 @@
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 void stateChanged(ChangeEvent e)
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());
}

@ -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

@ -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;
}
}

@ -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;
}
}
}

@ -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);
}
}
}
}
}
}
}

@ -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

@ -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;
}
}

@ -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;
}
}

@ -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;
}
}

@ -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

@ -0,0 +1 @@
pluginIcon=resources/images/plugin/keybindingchooser/pluginIcon.png

@ -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;
}
}
}

@ -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);
}
Loading…
Cancel
Save