package leaf.ui; import static info.clearthought.layout.TableLayoutConstants.FILL; import static leaf.ui.LeafLogic.ACTION_CANCEL; import static leaf.ui.LeafLogic.ACTION_STARTUP; import info.clearthought.layout.TableLayout; import info.clearthought.layout.TableLayoutConstraints; import java.awt.Cursor; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.lang.annotation.Annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; import javax.swing.BorderFactory; import javax.swing.DefaultListSelectionModel; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JTree; import javax.swing.SwingUtilities; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.table.TableModel; import leaf.data.Pair; import leaf.ui.LeafLogic.Action; import leaf.ui.LeafLogic.LeafLogicActionBinding; import leaf.ui.LeafLogic.LeafUIActionBinding; import com.evolute.utils.tables.BaseTable; import com.evolute.utils.tables.ColumnizedMappable; import com.evolute.utils.tables.VectorTableModel; import com.evolute.utils.tracker.TrackableWindow; public class LeafWindow extends JFrame implements TrackableWindow, ListSelectionListener, TreeSelectionListener, ActionListener, PropertyChangeListener { private static final long serialVersionUID = 1L; private static final int DEFAULT_HEIGHT = 480; private static final int DEFAULT_WIDTH = 640; /** * Registers DataComponent in a list of actions * * @author tsimao * */ @Retention(RetentionPolicy.RUNTIME) public @interface ActionActivation { /** * Array of actions to execute when a select is listened in this * JComponent * * @return */ String[] onSelect(); /** * Array of actions to execute when a change is listened in this * JComponent * * @return */ String[] onChange(); } /** * Binds an Object to actions * * @author tsimao * */ @Retention(RetentionPolicy.RUNTIME) public @interface LeafObject { /** * Actions that use this field */ String[] useWith(); } /** * Declares a JPanel as a leaf * * @author tsimao * */ @Retention(RetentionPolicy.RUNTIME) public @interface LeafPanel { } /** * This window's logic controller */ private final LeafLogic logicController; private List subPanels = new ArrayList(); /** * Actions */ private Map mapActionByName = new HashMap(); /** * Fields */ private Map> mapWindowOnSelectFieldByActionName = new HashMap>(); private Map> mapWindowOnChangeFieldByActionName = new HashMap>(); private Map mapLeafObjectByActionName = new HashMap(); private Map mapInstanceByField = new HashMap(); /** * Methods */ private Map> mapWindowMethodsByActionName = new HashMap>(); private Map mapLogicMethodByActionName = new HashMap(); private Map mapInstanceByMethod = new HashMap(); /** * Meta-info */ private Map mapAnnotationByObject = new HashMap(); /** * Run later actions */ private Queue> listRunLater = new LinkedList>(); /** * Creates a new LeafWindow binded with given 'logicController' * * @param logicController * @throws IllegalArgumentException * @throws IllegalAccessException */ public LeafWindow(LeafLogic logicController) { super(); setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); this.logicController = logicController; if( logicController != null ) { logicController.addWindow( this ); } } @Override public void open() { setVisible( true ); } public void close() { SwingUtilities.invokeLater( new Runnable() { public void run() { setVisible( false ); dispose(); } } ); } @Override public boolean closeIfPossible() { close(); return true; } @Override public void refresh() { } /** * Aborts current action. Aborts pending actions as well if 'all' is true * * @param all */ public void abortAction( boolean all ) { runGivenAction( ACTION_CANCEL, null ); throw new LeafRuntimeException( all ); } protected boolean runAction( String actionName ) { return runAction( actionName, null ); } /** * Returns false if an error occurred * * @param actionName * @param argument * @return */ protected boolean runAction( String actionName, Object argument ) { boolean ok = true; if( argument == null ) { Field field = mapLeafObjectByActionName.get( actionName ); if(field != null) { Object instance = mapInstanceByField.get( field ); if( instance != null) { try { argument = field.get( instance ); } catch( IllegalArgumentException e ) { e.printStackTrace(System.out); } catch( IllegalAccessException e ) { e.printStackTrace( System.out); } } } } try { runGivenAction( actionName, argument ); } catch( LeafRuntimeException leafRuntimeException ) { ok = !leafRuntimeException.isAbort(); } if( ok ) { runPendingActions(); } else { listRunLater.clear(); } return ok; } public void runActionLater( String action ) { runActionLater( action, null ); } public void runActionLater( String action, Object argument ) { if( action != null && mapActionByName.containsKey( action ) ) { listRunLater.add( new Pair( action, argument ) ); } } /** * Fires ACTION_STARTUP */ public void completeSetup() { try { loadLeafs(); loadActions(); loadFields(); loadMethods(); runAction( ACTION_STARTUP, null ); setVisible( true ); } catch( Exception e ) { e.printStackTrace( System.out ); } } public static void setupTopBottomSimpleActionsPanel(JPanel where, JPanel top, JPanel bottom) { TableLayout layout = new TableLayout(new double[]{TableLayout.FILL}, new double[]{TableLayout.MINIMUM, TableLayout.FILL,TableLayout.MINIMUM}); where.setLayout( layout ); where.add( top, new TableLayoutConstraints(0,0) ); where.add( new JPanel(), new TableLayoutConstraints(0,1) ); where.add( bottom, new TableLayoutConstraints(0,2) ); } public static void setupSimpleDataPanel( JPanel where, String name, JComponent... field ) { double[] cols = new double[] { FILL }; double[] rows = new double[field.length]; for( int i = 0; i < field.length; rows[i++] = TableLayout.PREFERRED ) ; rows[rows.length - 1] = FILL; TableLayout layout = new TableLayout( cols, rows ); layout.setHGap( 5 ); layout.setVGap( 5 ); where.setLayout( layout ); if( name != null ) { where.setBorder( BorderFactory.createTitledBorder( BorderFactory.createEtchedBorder(), name ) ); } for( int i = 0; i < field.length; ++i ) { where.add( field[i], new TableLayoutConstraints( 0, i ) ); } } private void loadLeafs() throws IllegalArgumentException, IllegalAccessException { Field fields[] = this.getClass().getDeclaredFields(); if( fields != null ) { for( Field field : fields ) { if( field.getAnnotation( LeafPanel.class ) != null && field.get( this ) != null ) { subPanels.add( (JPanel) field.get( this ) ); } } } } private void loadActions() throws IllegalArgumentException, IllegalAccessException { Field[] allLogicFields = this.logicController.getClass().getFields(); for( Field field : allLogicFields ) { Action action = field.getAnnotation( Action.class ); if( action != null ) { String value = (String) field.get( this ); if( value != null ) { mapActionByName.put( value, action ); mapWindowMethodsByActionName.put( value, new ArrayList() ); mapWindowOnSelectFieldByActionName.put( value, new ArrayList() ); mapWindowOnChangeFieldByActionName.put( value, new ArrayList() ); } } } } private void loadFields( Field[] fields, Object instance ) { try { for( Field field : fields ) { ActionActivation componentBehaviour = field.getAnnotation( ActionActivation.class ); if( componentBehaviour != null ) { String[] allChanges = componentBehaviour.onChange(); if( allChanges != null ) { for( String onChange : allChanges ) { if( mapActionByName.containsKey( onChange ) ) { // valid action mapAnnotationByObject.put( field.get( instance ), componentBehaviour ); mapWindowOnChangeFieldByActionName.get( onChange ).add( field.get( instance ) ); if( !mapInstanceByField.containsKey( field ) ) { addListenerForField( componentBehaviour, field, instance ); mapInstanceByField.put( field, instance ); } } } } String[] allSelect = componentBehaviour.onSelect(); if( allSelect != null ) { for( String onSelect : allSelect ) { if( mapActionByName.containsKey( onSelect ) ) { // valid action mapAnnotationByObject.put( field.get( instance ), componentBehaviour ); mapWindowOnSelectFieldByActionName.get( onSelect ).add( field.get( instance ) ); if( !mapInstanceByField.containsKey( field ) ) { addListenerForField( componentBehaviour, field, instance ); mapInstanceByField.put( field, instance ); } } } } } LeafObject leafObject = field.getAnnotation( LeafObject.class ); if( leafObject != null ) { String[] useWith = leafObject.useWith(); if( useWith != null ) { for( String current : useWith ) { if( mapActionByName.containsKey( current ) ) { // valid action mapLeafObjectByActionName.put( current, field ); mapInstanceByField.put( field, instance ); } } } } } } catch( IllegalAccessException exception ) { exception.printStackTrace( System.out ); } } private void loadFields() { Field[] allFields = this.getClass().getDeclaredFields(); if( allFields != null ) { loadFields( allFields, this ); } allFields = logicController.getClass().getDeclaredFields(); if( allFields != null ) { loadFields( allFields, logicController ); } for( JPanel panel : subPanels ) { allFields = panel.getClass().getDeclaredFields(); if( allFields != null ) { loadFields( allFields, panel ); } } } private void loadWindowMethods( Method[] windowMethods, Object instance ) { for( Method method : windowMethods ) { LeafUIActionBinding actionBinding = method.getAnnotation( LeafUIActionBinding.class ); if( actionBinding != null ) { String[] actions = actionBinding.action(); for( String actionName : actions ) { if( mapActionByName.containsKey( actionName ) ) { // valid action mapWindowMethodsByActionName.get( actionName ).add( method ); mapAnnotationByObject.put( method, actionBinding ); mapInstanceByMethod.put( method, instance ); } } } } } private void loadLogicMethods() { Method[] allLogicMethods = this.logicController.getClass().getDeclaredMethods(); if( allLogicMethods != null ) { for( Method method : allLogicMethods ) { LeafLogicActionBinding actionBinding = method.getAnnotation( LeafLogicActionBinding.class ); if( actionBinding != null ) { String[] actions = actionBinding.actions(); if( actions != null ) { for( String actionName : actions ) { if( mapActionByName.containsKey( actionName ) ) { // valid action mapAnnotationByObject.put( method, actionBinding ); mapLogicMethodByActionName.put( actionName, method ); mapInstanceByMethod.put( method, logicController ); } } } } } } } private void loadMethods() { loadLogicMethods(); Method[] allWindowMethods = this.getClass().getDeclaredMethods(); if( allWindowMethods != null ) { loadWindowMethods( allWindowMethods, this ); } for( JPanel panel : subPanels ) { allWindowMethods = panel.getClass().getDeclaredMethods(); if( allWindowMethods != null ) { loadWindowMethods( allWindowMethods, panel ); } } } private Object getObjectForAction( String actionName ) { Object result = null; Field field = mapLeafObjectByActionName.get( actionName ); if( field != null ) { Object instance = mapInstanceByField.get( field ); if( instance != null ) { try { result = field.get( instance ); } catch( IllegalArgumentException e ) { e.printStackTrace(); } catch( IllegalAccessException e ) { e.printStackTrace(); } } } return result; } public void runPendingActions() { while( listRunLater.size() > 0 ) { Pair p = listRunLater.poll(); runAction( p.getCar(), p.getCdr() ); } } /** * Executes given action */ private void runGivenAction( String actionName, Object argument ) { System.out.println( "Running: " + actionName ); try { this.setCursor( Cursor.getPredefinedCursor( Cursor.WAIT_CURSOR ) ); if( actionName != null && mapActionByName.containsKey( actionName ) ) { Action action = mapActionByName.get( actionName ); if( action.isSave() ) { Object windowArgument = getObjectForAction( actionName ); if( windowArgument == null ) { windowArgument = argument; } Object logicArgument = windowArgument; for( Method currentWindowMethod : mapWindowMethodsByActionName.get( actionName ) ) { Object currentLogicArgument = runWindowMethod( currentWindowMethod, windowArgument != null ? windowArgument : argument ); logicArgument = logicArgument == null ? currentLogicArgument : logicArgument; } runLogicMethod( mapLogicMethodByActionName.get( actionName ), logicArgument ); } else { Object windowArgument = runLogicMethod( mapLogicMethodByActionName.get( actionName ), argument ); for( Method currentWindowMethod : mapWindowMethodsByActionName.get( actionName ) ) { runWindowMethod( currentWindowMethod, windowArgument != null ? windowArgument : argument ); } } } } finally { this.setCursor( Cursor.getDefaultCursor() ); } } private Object runLogicMethod( Method logicMethod, Object argument ) throws LeafRuntimeException { Object result = null; try { if( logicMethod != null ) { if( logicMethod.getParameterTypes().length > 0 ) { result = logicMethod.invoke( logicController, argument ); } else { result = logicMethod.invoke( logicController ); } } } catch( IllegalArgumentException e ) { System.out.println("Error in: " + logicMethod.getName() ); System.out.println("Got: " + argument + " expected: " + (logicMethod.getParameterTypes().length > 0 ? logicMethod.getParameterTypes()[0].getCanonicalName() : "(nothing)")); e.printStackTrace( System.out ); } catch( IllegalAccessException e ) { e.printStackTrace( System.out ); } catch( InvocationTargetException e ) { if( e.getCause() instanceof LeafRuntimeException ) { throw (LeafRuntimeException) e.getCause(); } else { e.printStackTrace( System.out ); } } return result; } private Object runWindowMethod( Method windowMethod, Object argument ) throws LeafRuntimeException { Object result = null; try { if( windowMethod != null ) { if( windowMethod.getParameterTypes().length > 0 ) { result = windowMethod.invoke( mapInstanceByMethod.get( windowMethod ), argument ); } else { result = windowMethod.invoke( mapInstanceByMethod.get( windowMethod ) ); } } } catch( IllegalArgumentException e ) { System.out.println("Error in: " + windowMethod.getName() ); System.out.println("Got: " + argument + " expected: " + (windowMethod.getParameterTypes().length > 0 ? windowMethod.getParameterTypes()[0].getCanonicalName() : "(nothing)")); e.printStackTrace( System.out ); } catch( IllegalAccessException e ) { e.printStackTrace( System.out ); } catch( InvocationTargetException e ) { if( e.getCause() instanceof LeafRuntimeException ) { throw (LeafRuntimeException) e.getCause(); } else { e.printStackTrace( System.out ); } } return result; } private void addListenerForField( ActionActivation annotation, Field field, Object instance ) { if( instance instanceof JFrame || instance instanceof JPanel ) { try { Object value = field.get( instance ); if( value instanceof BaseTable ) { ((BaseTable) value).getSelectionModel().addListSelectionListener( this ); } else if( value instanceof JTree ) { ((JTree) value).addTreeSelectionListener( this ); } else if( value instanceof JButton ) { ((JButton) value).addActionListener( this ); } else if( value instanceof LeafInputField ) { ((LeafInputField) value).addPropertyChangeListener( this ); } } catch( IllegalAccessException e ) { e.printStackTrace( System.out ); } catch( NullPointerException e ) { e.printStackTrace( System.out ); } } } private Object getArgumentListSelectionEvent( String actionName, ListSelectionEvent event ) { Object source = event.getSource(); List allComponents = mapWindowOnSelectFieldByActionName.get( actionName ); for( Object component : allComponents ) { if( component instanceof BaseTable && ((BaseTable) component).getSelectionModel().equals( source ) ) { int [] indexes = ((BaseTable) component).getSelectedRows(); if( indexes != null && indexes.length > 0 ) { TableModel model = ((BaseTable) component).getModel(); if( model instanceof VectorTableModel ) { if(indexes.length == 1 && indexes[0] > -1) { return ((ColumnizedMappable) ((VectorTableModel) model).getRowAt( indexes[0] )).getID(); } else { List allSelected = new ArrayList(); for(int i = 0; i < indexes.length; ++i) { allSelected.add( ((ColumnizedMappable) ((VectorTableModel) model).getRowAt( indexes[0] )).getID() ); } return allSelected; } } else if( model instanceof LeafTableModel ) { if(indexes.length == 1 && indexes[0] > -1) { return ((LeafTableModel) model).getKey( indexes[0] ); } else { List allSelected = new ArrayList(); for(int i = 0; i < indexes.length; ++i) { allSelected.add( ((LeafTableModel) model).getKey( indexes[0] )); } return allSelected; } } } } } return null; } private List getActionListSelectionEvent( ListSelectionEvent event ) { List result = new ArrayList(); if( event.getSource() instanceof DefaultListSelectionModel ) { DefaultListSelectionModel model = (DefaultListSelectionModel) event.getSource(); BaseTable table = null; for( List allComponents : mapWindowOnSelectFieldByActionName.values() ) { // for each registered table for( Object component : allComponents ) { if( component instanceof BaseTable && ((BaseTable) component).getSelectionModel().equals( model ) ) { table = (BaseTable) component; } if( table != null ) { break; } } if( table != null ) { break; } } Annotation an = mapAnnotationByObject.get( table ); if( an != null && an instanceof ActionActivation ) { String[] actions = ((ActionActivation) an).onSelect(); for( String actionName : actions ) { result.add( actionName ); } } } return result; } // returns selected node private Object getArgumentTreeSelectionEvent( String actionName, TreeSelectionEvent event ) { List components = mapWindowOnSelectFieldByActionName.get( actionName ); for( Object component : components ) { if( component instanceof JTree && event.getPath() != null ) { Object[] nodes = event.getPath().getPath(); if( nodes != null && nodes.length > 0 ) { return nodes[nodes.length - 1]; } } } return null; } private List getActionTreeSelectionEvent( TreeSelectionEvent event ) { List result = new ArrayList(); Annotation an = mapAnnotationByObject.get( event.getSource() ); if( an != null && an instanceof ActionActivation ) { String[] actions = ((ActionActivation) an).onSelect(); for( String actionName : actions ) { result.add( actionName ); } } return result; } private List getActionActionEvent( ActionEvent event ) { List result = new ArrayList(); Annotation an = mapAnnotationByObject.get( event.getSource() ); if( an != null && an instanceof ActionActivation ) { String[] actions = ((ActionActivation) an).onSelect(); for( String actionName : actions ) { result.add( actionName ); } } return result; } private List getActionsForPropertyChangeEvent( PropertyChangeEvent evt ) { List result = new ArrayList(); Annotation an = mapAnnotationByObject.get( evt.getSource() ); if( an != null ) { if( an instanceof ActionActivation ) { if( evt.getSource() instanceof LeafInputField ) { if( LeafInputField.PROPERTY_CHANGED_CONSTANT.equals( evt.getPropertyName() ) ) { String[] actions = ((ActionActivation) an).onChange(); for( String actionName : actions ) { result.add( actionName ); } } } } } return result; } @Override public void valueChanged( TreeSelectionEvent event ) { List actions = getActionTreeSelectionEvent( event ); for( String action : actions ) { Object argument = getArgumentTreeSelectionEvent( action, event ); if( !runAction( action, argument ) ) { break; } } } /** * Listens to ListSelectionEvents */ @Override public void valueChanged( ListSelectionEvent event ) { if( !event.getValueIsAdjusting() ) { List actionNames = getActionListSelectionEvent( event ); for( String action : actionNames ) { Object argument = getArgumentListSelectionEvent( action, event ); if( !runAction( action, argument ) ) { break; } } } } @Override public void actionPerformed( ActionEvent event ) { List actionNames = getActionActionEvent( event ); if( actionNames.size() > 0 ) { for( int i = 1; i < actionNames.size(); ++i ) { runActionLater( actionNames.get( i ) ); } runAction( actionNames.get( 0 ) ); } } @Override public void propertyChange( PropertyChangeEvent evt ) { List actionNames = getActionsForPropertyChangeEvent( evt ); if( actionNames.size() > 0 ) { for( int i = 1; i < actionNames.size(); ++i ) { runActionLater( actionNames.get( i ) ); } runAction( actionNames.get( 0 ) ); } } }