/* * Bibliothek - DockingFrames * Library built on Java/Swing, allows the user to "drag and drop" * panels containing any Swing-Component the developer likes to add. * * Copyright (C) 2007 Benjamin Sigg * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Benjamin Sigg * benjamin_sigg@gmx.ch * CH - Switzerland */ package bibliothek.gui.dock.facile.menu; import java.awt.EventQueue; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.text.Collator; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import javax.swing.Icon; import javax.swing.JCheckBoxMenuItem; import bibliothek.gui.DockFrontend; import bibliothek.gui.Dockable; import bibliothek.gui.dock.event.DockFrontendAdapter; import bibliothek.gui.dock.event.DockableListener; import bibliothek.gui.dock.support.menu.BaseMenuPiece; import bibliothek.gui.dock.title.DockTitle; /** * A piece of a menu allowing to close or reopen some {@link Dockable}s that * are registered in a {@link DockFrontend}. * @author Benjamin Sigg * */ public class CloseableDockableMenuPiece extends BaseMenuPiece{ /** the frontend that is currently observed */ private DockFrontend frontend; /** a list of all items shown in the menu */ private Map<Dockable, Item> items = new HashMap<Dockable, Item>(); /** a listener collecting all new {@link Dockable}s of the {@link #frontend} */ private DockableCollector collector = new DockableCollector(); /** the comparator deciding how to sort the items */ private Comparator<String> order = new Comparator<String>(){ private Collator collator = Collator.getInstance(); public int compare( String o1, String o2 ){ return collator.compare( o1, o2 ); } }; /** the identifier of the next {@link #order(int)} that will actually be executed */ private AtomicInteger orderCommand = new AtomicInteger(0); /** * Creates a new piece */ public CloseableDockableMenuPiece() { // do nothing } /** * Creates a new piece * @param frontend the list of {@link Dockable}s, can be <code>null</code> */ public CloseableDockableMenuPiece( DockFrontend frontend ) { setFrontend( frontend ); } /** * Sets the order of the menu items. The default behavior is to order the * items alphabetically. * @param order the order, or <code>null</code> */ public void setOrder( Comparator<String> order ){ this.order = order; reorder(); } /** * Gets the order of the menu items. * @return the order, can be <code>null</code> */ public Comparator<String> getOrder(){ return order; } /** * Asynchronously order the menu items */ protected void reorder(){ final int identifier = orderCommand.incrementAndGet(); if( EventQueue.isDispatchThread() ){ EventQueue.invokeLater( new Runnable(){ public void run(){ order( identifier ); } } ); } else{ order( identifier ); } } private void order( int identifier ){ if( identifier == orderCommand.get() && order != null ){ Item[] items = this.items.values().toArray( new Item[ this.items.size() ] ); Arrays.sort( items, new Comparator<Item>(){ public int compare( Item o1, Item o2 ){ return order.compare( o1.getText(), o2.getText() ); } }); removeAll(); for( Item item : items ){ add( item ); } } } /** * Gets the frontend which is observed by this piece. * @return the frontend, might be <code>null</code> * @see #setFrontend(DockFrontend) */ public DockFrontend getFrontend() { return frontend; } /** * Sets the frontend which will be observed by this piece. Every * {@link Dockable} that is registered at the <code>frontend</code> will * get an item in the menu of this piece. * @param frontend the list of {@link Dockable}s, can be <code>null</code> */ public void setFrontend( DockFrontend frontend ) { if( this.frontend != frontend ){ if( isBound() ){ if( this.frontend != null ){ this.frontend.removeFrontendListener( collector ); for( Item item : items.values() ){ item.destroy(); remove( item ); } items.clear(); } } this.frontend = frontend; if( isBound() ){ if( this.frontend != null ){ this.frontend.addFrontendListener( collector ); for( Dockable dockable : this.frontend.listDockables() ){ collector.added( frontend, dockable ); } } } } } @Override public void bind(){ if( !isBound() ){ super.bind(); if( frontend != null ){ frontend.addFrontendListener( collector ); for( Dockable dockable : this.frontend.listDockables() ){ collector.added( frontend, dockable ); } } } } @Override public void unbind(){ if( isBound() ){ super.unbind(); if( frontend != null ){ frontend.removeFrontendListener( collector ); for( Item item : items.values() ){ item.destroy(); remove( item ); } items.clear(); } } } /** * Creates a new item for the menu. * @param dockable the element which will be shown/hidden when the user * clicks onto the item. * @return the new item */ protected Item create( Dockable dockable ){ return new Item( dockable ); } /** * Adds an earlier created item into the menu, subclasses might override * this method to add the item at a different location. * @param item the item to insert * @see #setOrder(Comparator) */ protected void insert( Item item ){ add( item ); reorder(); } /** * Ensures that <code>dockable</code> is visible. * @param dockable the element to show */ protected void show( Dockable dockable ){ frontend.show( dockable ); } /** * Ensures that <code>dockable</code> is not visible. * @param dockable the element to hide */ protected void hide( Dockable dockable ){ frontend.hide( dockable ); } /** * Tells whether an item should be inserted into the menu for the given * <code>dockable</code> or not. * @param dockable the element to check * @return <code>true</code> if there should be an item added to the menu */ protected boolean include( Dockable dockable ){ return frontend.isHideable( dockable ); } /** * Ensures that <code>dockable</code> has an item if it is {@link #include(Dockable) included}, * and does not have otherwise. * @param dockable the element whose item is to be checked */ public void check( Dockable dockable ){ if( include( dockable ) ){ if( !items.containsKey( dockable )) collector.added( frontend, dockable ); } else{ if( items.containsKey( dockable )){ collector.removed( frontend, dockable ); } } } /** * A class that collects {@link Dockable}s and manages the states of the * {@link Item}s of the enclosing {@link CloseableDockableMenuPiece}. * @author Benjamin Sigg * */ private class DockableCollector extends DockFrontendAdapter{ @Override public void hidden( DockFrontend fronend, Dockable dockable ) { Item item = items.get( dockable ); if( item != null ){ item.setDockableState( false ); } } @Override public void shown( DockFrontend frontend, Dockable dockable ) { Item item = items.get( dockable ); if( item != null ){ item.setDockableState( true ); } } @Override public void added( DockFrontend frontend, Dockable dockable ) { if( include( dockable )){ Item item = create( dockable ); item.setDockableState( frontend.isShown( dockable ) ); items.put( dockable, item ); insert( item ); } } @Override public void removed( DockFrontend frontend, Dockable dockable ) { Item item = items.remove( dockable ); if( item != null ){ item.destroy(); remove( item ); } } @Override public void hideable( DockFrontend frontend, Dockable dockable, boolean hideable ) { check( dockable ); } } /** * An item showing the visibility state of one <code>Dockable</code>. * @author Benjamin Sigg */ protected class Item extends JCheckBoxMenuItem implements DockableListener, ActionListener{ /** the element that might be shown or hidden by this item */ private Dockable dockable; /** whether the properties of this item are currently changing */ private boolean onChange = false; /** * Creates a new item. * @param dockable the element that will be shown or hidden when the * user clicks onto this item */ public Item( Dockable dockable ){ this.dockable = dockable; dockable.addDockableListener( this ); addActionListener( this ); setIcon( dockable.getTitleIcon() ); setText( dockable.getTitleText() ); } /** * Sets whether the <code>Dockable</code> of this item is currently * visible or not. * @param visible the new state */ public void setDockableState( boolean visible ){ try{ onChange = true; setSelected( visible ); } finally{ onChange = false; } } /** * Removes all listeners, frees as many resources as possible */ public void destroy(){ dockable.removeDockableListener( this ); removeActionListener( this ); setIcon( null ); setText( "" ); } public void titleBound( Dockable dockable, DockTitle title ) { // ignore } public void titleIconChanged( Dockable dockable, Icon oldIcon, Icon newIcon ) { setIcon( newIcon ); } public void titleTextChanged( Dockable dockable, String oldTitle, String newTitle ) { setText( newTitle ); reorder(); } public void titleToolTipChanged( Dockable dockable, String oldToolTip, String newToolTip ) { // ignore } public void titleUnbound( Dockable dockable, DockTitle title ) { // ignore } public void titleExchanged( Dockable dockable, DockTitle title ) { // ignore } public void actionPerformed( ActionEvent e ) { if( !onChange ){ if( isSelected() ) CloseableDockableMenuPiece.this.show( dockable ); else CloseableDockableMenuPiece.this.hide( dockable ); } } } }