The menus do not support accelerators: one instance of a CMenu
(or any CAction
) can be shown in many places, but the CAction
object does not have a reference to its (many) representations as UI element.
Or in other words: a CMenu
does not know where it is shown, and thus cannot access parts of the UI. If it would know the UI, it would still not know which menu exactly to open.
Other elements like a „button“ do not have this limitation, because such actions can be triggered without giving the user any visual feedback.
I was playing around a bit and added a KeyListener
that will open a menu. This solution should work decently as long as you don’t have more than one JComponent
that is linked to the same CMenu
. Check out the example below, and let me know if it is of any help to you.
You should not run into these kinds of troubles, assuming you don’t write heavy modifications (e.g. like adding additional custom DockTitles
).
package test;
import java.awt.Color;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.KeyStroke;
import bibliothek.gui.DockController;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.action.ActionType;
import bibliothek.gui.dock.action.DockAction;
import bibliothek.gui.dock.action.MenuDockAction;
import bibliothek.gui.dock.action.view.ActionViewConverter;
import bibliothek.gui.dock.action.view.ViewGenerator;
import bibliothek.gui.dock.action.view.ViewTarget;
import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.action.CAction;
import bibliothek.gui.dock.common.action.CButton;
import bibliothek.gui.dock.common.action.CMenu;
import bibliothek.gui.dock.common.action.core.CommonDockAction;
import bibliothek.gui.dock.common.event.CKeyboardListener;
import bibliothek.gui.dock.common.intern.CDockController;
import bibliothek.gui.dock.common.intern.CDockable;
import bibliothek.gui.dock.common.intern.CommonDockable;
import bibliothek.gui.dock.common.intern.EfficientControlFactory;
import bibliothek.gui.dock.control.ControllerSetupCollection;
import bibliothek.gui.dock.control.DefaultDockControllerFactory;
import bibliothek.gui.dock.themes.basic.action.BasicMenuHandler;
import bibliothek.gui.dock.themes.basic.action.BasicTitleViewItem;
import bibliothek.gui.dock.title.DockTitle.Orientation;
public class AcceleratorTest {
public static void main( String[] args ) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.setBounds( 50, 50, 1000, 1000 );
// note the custom factory we are creating
CControl control = new CControl( frame, new CustomCControlFactory() );
// control.setTheme( ThemeMap.KEY_ECLIPSE_THEME );
frame.add( control.getContentArea() );
DefaultSingleCDockable dockable = new DefaultSingleCDockable( "id", "Title" );
dockable.add( new JButton( "Focus me" ) );
setup( dockable );
control.addDockable( dockable );
dockable.setVisible( true );
frame.setVisible( true );
}
/*
* Adding the menu
*/
private static void setup( DefaultSingleCDockable parentDockable ) {
CMenu testMenu = new CMenu();
testMenu.setText( "L\u0332ist" );
testMenu.setShowTextOnButtons( true );
testMenu.setTooltip( "Show the List [ALT-L]" );
testMenu.setAccelerator( KeyStroke.getKeyStroke( KeyEvent.VK_L, InputEvent.ALT_DOWN_MASK ) );
// testMenu.setAcceleratorIsGlobal( true );
testMenu.add( new CButton( "ACTION 1", null ) );
testMenu.add( new CButton( "ACTION 2", null ) );
parentDockable.addAction( testMenu );
}
/*
* This factory creates a special CustomControllerFactory ...
*/
private static class CustomCControlFactory extends EfficientControlFactory {
@Override
public DockController createController( CControl owner ) {
return new CDockController( owner, new CustomControllerFactory() );
}
}
/*
* ... the CustomControllerFactory creates a special CustomActionViewConverter ...
*/
private static class CustomControllerFactory extends DefaultDockControllerFactory {
@Override
public ActionViewConverter createActionViewConverter( DockController controller, ControllerSetupCollection setup ) {
return new CustomActionViewConverter();
}
}
/*
* ... and the CustomActionViewConverter is responsible for creating JComponents for buttons, menus, etc.
*/
private static class CustomActionViewConverter extends ActionViewConverter {
@Override
public <A, D extends DockAction> void putClient( ActionType<D> action, ViewTarget<A> target, ViewGenerator<D, A> generator ) {
generator = replace( action, target, generator );
super.putClient( action, target, generator );
}
@Override
public <A, D extends DockAction> void putTheme( ActionType<D> action, ViewTarget<A> target, ViewGenerator<D, A> generator ) {
generator = replace( action, target, generator );
super.putTheme( action, target, generator );
}
@Override
public <A, D extends DockAction> void putDefault( ActionType<D> action, ViewTarget<A> target, ViewGenerator<D, A> generator ) {
generator = replace( action, target, generator );
super.putDefault( action, target, generator );
}
@SuppressWarnings("unchecked")
private <A, D extends DockAction> ViewGenerator<D, A> replace( ActionType<D> action, ViewTarget<A> target, ViewGenerator<D, A> generator ) {
if( action == ActionType.MENU && target == ViewTarget.TITLE ) {
/*
* If where are showing a menu in a title: wrap the "handler" in a special CustomMenuViewGenerator ...
*/
return (ViewGenerator<D, A>) replace( (ViewGenerator<MenuDockAction, BasicTitleViewItem<JComponent>>) generator );
} else {
return generator;
}
}
private ViewGenerator<MenuDockAction, BasicTitleViewItem<JComponent>> replace( ViewGenerator<MenuDockAction, BasicTitleViewItem<JComponent>> generator ) {
return new CustomMenuViewGenerator( generator );
}
}
/*
* ... the CustomMenuViewGenerator creates a special CustomBasicTitleViewItem when we are dealing with a CMenu on a
* CDockable ...
*/
private static class CustomMenuViewGenerator implements ViewGenerator<MenuDockAction, BasicTitleViewItem<JComponent>> {
private final ViewGenerator<MenuDockAction, BasicTitleViewItem<JComponent>> generator;
public CustomMenuViewGenerator( ViewGenerator<MenuDockAction, BasicTitleViewItem<JComponent>> generator ) {
this.generator = generator;
}
@Override
public BasicTitleViewItem<JComponent> create( ActionViewConverter converter, MenuDockAction action, Dockable dockable ) {
BasicTitleViewItem<JComponent> delegate = generator.create( converter, action, dockable );
CMenu menu = getMenu( action );
if( dockable instanceof CommonDockable && delegate instanceof BasicMenuHandler && menu != null ) {
return new CustomBasicTitleViewItem( menu, ((CommonDockable) dockable).getDockable(), (BasicMenuHandler) delegate );
} else {
return delegate;
}
}
private CMenu getMenu( MenuDockAction action ) {
if( action instanceof CommonDockAction ) {
CAction caction = ((CommonDockAction) action).getAction();
if( caction instanceof CMenu ) {
return (CMenu) caction;
}
}
return null;
}
}
/*
* ... finally the CustomBasicTitleViewItem. Here we add a KeyListener to the CDockable and open the menu when the
* accelerator of the menu is hit.
*/
private static class CustomBasicTitleViewItem implements BasicTitleViewItem<JComponent> {
private final BasicTitleViewItem<JComponent> delegate;
private final CDockable dockable;
private final MenuOpener menuOpener;
public CustomBasicTitleViewItem( CMenu menu, CDockable dockable, BasicMenuHandler delegate ) {
this.delegate = delegate;
this.dockable = dockable;
this.menuOpener = new MenuOpener( menu, delegate );
}
@Override
public void bind() {
delegate.bind();
/*
* Register the KeyListener
*/
dockable.addKeyboardListener( menuOpener );
}
@Override
public void unbind() {
/*
* Clean up the KeyListener
*/
dockable.removeKeyboardListener( menuOpener );
delegate.unbind();
}
@Override
public JComponent getItem() {
return delegate.getItem();
}
@Override
public DockAction getAction() {
return delegate.getAction();
}
@Override
public void setOrientation( Orientation orientation ) {
delegate.setOrientation( orientation );
}
@Override
public void setForeground( Color foreground ) {
delegate.setForeground( foreground );
}
@Override
public void setBackground( Color background ) {
delegate.setBackground( background );
}
}
private static class MenuOpener implements CKeyboardListener {
private final CMenu menu;
private final BasicMenuHandler delegate;
public MenuOpener( CMenu menu, BasicMenuHandler delegate ) {
this.menu = menu;
this.delegate = delegate;
}
private void open() {
delegate.triggered();
}
@Override
public boolean keyTyped( CDockable source, KeyEvent event ) {
return false;
}
@Override
public boolean keyReleased( CDockable source, KeyEvent event ) {
System.out.println( event );
/*
* open the menu if its accelerator is hit
*/
KeyStroke accelerator = menu.getAccelerator();
if( accelerator != null && accelerator.equals( KeyStroke.getKeyStrokeForEvent( event ) ) ) {
open();
return true;
}
return false;
}
@Override
public boolean keyPressed( CDockable source, KeyEvent event ) {
System.out.println( event );
/*
* open the menu if its accelerator is hit
*/
KeyStroke accelerator = menu.getAccelerator();
if( accelerator != null && accelerator.equals( KeyStroke.getKeyStrokeForEvent( event ) ) ) {
open();
return true;
}
return false;
}
}
}