@ -7,18 +7,18 @@
package net.java.sip.communicator.impl.keybindings ;
import java.io.* ;
import java.text. ParseException ;
import java.text. * ;
import java.util.* ;
import javax.swing. KeyStroke ;
import javax.swing. * ;
import org.osgi.framework.* ;
import chooser. Persistence ;
import chooser. * ;
import net.java.sip.communicator.service.fileaccess. FileAccessService ;
import net.java.sip.communicator.service.fileaccess. * ;
import net.java.sip.communicator.service.keybindings.* ;
import net.java.sip.communicator.util. Logger ;
import net.java.sip.communicator.util. * ;
/ * *
* Service that concerns keybinding mappings used by various parts of the UI .
@ -31,83 +31,86 @@
* < / 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 ) ;
Logger . getLogger ( KeybindingsServiceImpl . class ) ;
// Name of the relative directory that holds default bindings
private static final String DEFAULT_KEYBINDING_DIR =
"/resources/config/defaultkeybindings" ;
"/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 > ( ) ;
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 ;
if ( this . isRunning )
return ;
for ( KeybindingSet . Category category : KeybindingSet . Category . values ( ) )
{
// Retrieves default bindings
Persistence format = category . getFormat ( ) ;
LinkedHashMap < KeyStroke , String > defaultBindings ;
LinkedHashMap < KeyStroke , String > defaultBindings ;
try
{
String defaultPath =
DEFAULT_KEYBINDING_DIR + "/" + category . getResource ( ) ;
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 ) ;
+ category . getResource ( ) , exc ) ;
this . bindings . put ( category , null ) ;
continue ;
}
catch ( ParseException exc )
{
logger . error ( "unable to parse default bindings set: "
+ category . getResource ( ) , exc ) ;
+ category . getResource ( ) , exc ) ;
this . bindings . put ( category , null ) ;
continue ;
}
// Attempts to retrieve custom bindings
String customPath =
CUSTOM_KEYBINDING_DIR + "/" + category . getResource ( ) ;
CUSTOM_KEYBINDING_DIR + "/" + category . getResource ( ) ;
File customFile ;
try
{
ServiceReference faServiceReference =
bc . getServiceReference ( FileAccessService . class
. getName ( ) ) ;
bc . getServiceReference ( FileAccessService . class . getName ( ) ) ;
FileAccessService faService =
( FileAccessService ) bc . getService ( faServiceReference ) ;
( 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 ( ) ;
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 ) ;
@ -115,18 +118,17 @@ synchronized void start(BundleContext bc)
catch ( Exception exc )
{
String msg =
"unable to secure file for custom bindings ("
+ customPath
+ "), using defaults but won't be able to save changes" ;
"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 ) ;
new KeybindingSetImpl ( defaultBindings , category , null ) ;
this . bindings . put ( category , newSet ) ;
newSet . addObserver ( this ) ;
continue ;
}
LinkedHashMap < KeyStroke , String > customBindings = null ;
LinkedHashMap < KeyStroke , String > customBindings = null ;
if ( customFile . exists ( ) )
{
try
@ -141,38 +143,40 @@ synchronized void start(BundleContext bc)
// loading custom bindings
}
}
// Merges custom bindings
LinkedHashMap < KeyStroke , String > merged =
new LinkedHashMap < KeyStroke , String > ( ) ;
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 ( ) )
Map < KeyStroke , String > customTmp =
new LinkedHashMap < KeyStroke , String > ( customBindings ) ;
for ( Map . Entry < KeyStroke , String > shortcut2action : defaultBindings
. entrySet ( ) )
{
String action = defaultBindings. get ( shortcut ) ;
String action = shortcut2action. getValue ( ) ;
if ( customTmp . containsValue ( action ) )
{
KeyStroke custom = null ;
for ( KeyStroke customShortcut : customTmp . keySet ( ) )
for ( Map . Entry < KeyStroke , String > customShortcut2action : customTmp
. entrySet ( ) )
{
if ( custom Tmp. get ( customShortcut ) . equals ( action ) )
if ( custom Shortcut2action. getValue ( ) . equals ( action ) )
{
custom = customShortcut ;
custom = customShortcut 2action. getKey ( ) ;
break ;
}
}
assert custom ! = null ;
customTmp . remove ( custom ) ;
merged . put ( custom , action ) ;
}
else
{
merged . put ( shortcut , action ) ;
merged . put ( shortcut 2action. getKey ( ) , action ) ;
}
}
}
@ -180,7 +184,7 @@ synchronized void start(BundleContext bc)
{
merged = defaultBindings ;
}
// Writes merged result
if ( ! merged . equals ( customBindings ) )
{
@ -193,25 +197,26 @@ synchronized void start(BundleContext bc)
catch ( IOException exc )
{
logger . error ( "unable to write to: "
+ customFile . getAbsolutePath ( ) , exc ) ;
+ customFile . getAbsolutePath ( ) , exc ) ;
}
}
KeybindingSetImpl newSet =
new KeybindingSetImpl ( merged , category , customFile ) ;
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 ;
if ( ! this . isRunning )
return ;
for ( KeybindingSetImpl bindingSet : this . bindings . values ( ) )
{
bindingSet . invalidate ( ) ;
@ -219,32 +224,34 @@ synchronized void stop()
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 )
KeybindingSet . Category category )
{
if ( ! this . isRunning ) throw new UnsupportedOperationException ( ) ;
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 ( ) )
@ -255,24 +262,24 @@ public void update(Observable obs, Object arg)
{
// Writes new bindings to custom file
File customFile = changedBindings . getCustomFile ( ) ;
try
{
FileOutputStream out =
new FileOutputStream ( customFile ) ;
new FileOutputStream ( customFile ) ;
Persistence format =
changedBindings . getCategory ( ) . getFormat ( ) ;
changedBindings . getCategory ( ) . getFormat ( ) ;
format . save ( out , changedBindings . getBindings ( ) ) ;
out . close ( ) ;
}
catch ( IOException exc )
{
logger . error ( "unable to write to: "
+ customFile . getAbsolutePath ( ) , exc ) ;
+ customFile . getAbsolutePath ( ) , exc ) ;
}
}
}
}
}
}
}
}