View Javadoc

1   /*
2    * Copyright (c) 2007 Creative Sphere Limited.
3    * All rights reserved. This program and the accompanying materials
4    * are made available under the terms of the Eclipse Public License v1.0
5    * which accompanies this distribution, and is available at
6    * http://www.eclipse.org/legal/epl-v10.html
7    *
8    * Contributors:
9    *
10   *   Creative Sphere - initial API and implementation
11   *
12   */
13  package org.abstracthorizon.aequo.gui;
14  
15  import java.awt.Adjustable;
16  import java.awt.BorderLayout;
17  import java.awt.Color;
18  import java.awt.Component;
19  import java.awt.Container;
20  import java.awt.Dimension;
21  import java.awt.Point;
22  import java.awt.event.AdjustmentEvent;
23  import java.awt.event.AdjustmentListener;
24  import java.awt.event.FocusEvent;
25  import java.awt.event.FocusListener;
26  import java.beans.PropertyChangeEvent;
27  import java.beans.PropertyChangeListener;
28  import java.util.HashMap;
29  import java.util.Map;
30  
31  import javax.swing.AbstractButton;
32  import javax.swing.Action;
33  import javax.swing.JComponent;
34  import javax.swing.JFrame;
35  import javax.swing.JMenu;
36  import javax.swing.JMenuBar;
37  import javax.swing.JMenuItem;
38  import javax.swing.JPanel;
39  import javax.swing.JPopupMenu;
40  import javax.swing.JScrollBar;
41  import javax.swing.JScrollPane;
42  import javax.swing.JViewport;
43  import javax.swing.KeyStroke;
44  import javax.swing.event.ChangeEvent;
45  import javax.swing.event.ChangeListener;
46  import javax.swing.event.ListDataEvent;
47  import javax.swing.event.ListDataListener;
48  
49  import org.abstracthorizon.aequo.CompareEntry;
50  import org.abstracthorizon.aequo.CompareModel;
51  import org.abstracthorizon.aequo.DefaultCompareModel;
52  import org.abstracthorizon.aequo.GlobalContext;
53  import org.abstracthorizon.aequo.action.BaseAction;
54  import org.abstracthorizon.aequo.text.TextCompareEntry;
55  
56  /**
57   * Abstract compare panel. It is a base for side by side compare panels.
58   *  
59   * @param <T> type
60   * @param <CompareEntryType> compare entry type of type T.
61   *
62   * @author Daniel Sendula
63   */
64  public abstract class AbstractComparePanel<T, CompareEntryType extends CompareEntry<T>> extends JPanel {
65  
66      /** Global context */
67      protected GlobalContext context;
68      
69      /** Left view component */
70      protected JComponent leftList;
71      
72      /** Right view component */
73      protected JComponent rightList;
74  
75      /** Left panel */
76      protected JPanel leftPanel;
77      
78      /** Right panel */
79      protected JPanel rightPanel;
80      
81      /** Bottom left panel */
82      protected BottomPanel bottomLeftPanel;
83      
84      /** Bottom rigth panel */
85      protected BottomPanel bottomRightPanel;
86      
87      /** Left scroll pane */
88      protected JScrollPane leftScrollPane;
89      
90      /** Right scroll pane */
91      protected JScrollPane rightScrollPane;
92  
93      /** Horizontal bar */
94      protected JScrollBar horizontalBar;
95      
96      /** Foreground colours */
97      protected Map<Byte, Color> foreGrounds = new HashMap<Byte, Color>();
98  
99      /** Background colours */
100     protected Map<Byte, Color> backGrounds = new HashMap<Byte, Color>();
101 
102     /** Model */
103     protected CompareModel<T, CompareEntryType> compareModel = new DefaultCompareModel<T, CompareEntryType>(2);
104 
105     /** Cache of actions */
106     protected Map<String, BaseAction> actionsCache = new HashMap<String, BaseAction>();
107         
108     /** Left popup menu */
109     protected JPopupMenu leftPopupMenu;
110     
111     /** Right popup menu */
112     protected JPopupMenu rightPopupMenu;
113 
114     /**
115      * Constructor
116      * @param context global context
117      */
118     public AbstractComparePanel(GlobalContext context) {
119         this(context, new DefaultCompareModel<T, CompareEntryType>(2));
120     }
121 
122     /**
123      * Constructor
124      * @param context global context
125      * @param compareModel model
126      */
127     public AbstractComparePanel(GlobalContext context, CompareModel<T, CompareEntryType> compareModel) {
128         super(new BorderLayout());
129         this.compareModel = compareModel;
130         this.context = context;
131     }
132 
133     /**
134      * Returns global context
135      * @return global context
136      */
137     public GlobalContext getGlobalContext() {
138         return context;
139     }
140     
141     /**
142      * Returns left list
143      * @return left list
144      */
145     public Component getLeftList() {
146         return leftList;
147     }
148     
149     /**
150      * Returns right list
151      * @return right list
152      */
153     public Component getRightList() {
154         return rightList;
155     }
156     
157     /**
158      * <p>Initialise panel.</p>
159      * <p>This method calls following methods in this order</p>
160      * <ul>
161      *   <li>setUpColors()</li>
162      *   <li>createPanels()</li>
163      *   <li>createLists()</li>
164      *   <li>createScrollPanes()</li>
165      * </ul>
166      * 
167      */
168     public void initialise() {
169 
170         createActions();
171 
172         setUpColors();
173 
174         createPanels();
175         
176         createLists();
177         
178         createScrollPanes();
179         
180         // addHorizontalBar();
181 
182 
183     }
184     
185     /**
186      * Returns the model
187      * @return the model
188      */
189     public CompareModel<T, CompareEntryType> getCompareModel() {
190         return compareModel;
191     }
192 
193     /**
194      * Sets up default colours
195      */
196     protected void setUpColors() {
197         foreGrounds.put(CompareEntry.EMPTY, Color.BLACK);
198         backGrounds.put(CompareEntry.EMPTY, Color.LIGHT_GRAY);
199 
200         foreGrounds.put(CompareEntry.EQUAL, Color.BLACK);
201         backGrounds.put(CompareEntry.EQUAL, Color.WHITE);
202         
203         foreGrounds.put(CompareEntry.LESS, Color.BLACK);
204         backGrounds.put(CompareEntry.LESS, new Color(255, 180, 180));
205 
206         foreGrounds.put(CompareEntry.GREATER, Color.BLACK);
207         backGrounds.put(CompareEntry.GREATER, new Color(255, 180, 180));
208 
209         foreGrounds.put(CompareEntry.DIFFERENT, Color.BLACK);
210         backGrounds.put(CompareEntry.DIFFERENT, new Color(255, 180, 180));
211 
212         foreGrounds.put(TextCompareEntry.SIMILAR, Color.BLACK);
213         backGrounds.put(TextCompareEntry.SIMILAR, new Color(255, 208, 208));
214 
215         foreGrounds.put(TextCompareEntry.CHANGED, Color.BLACK);
216         backGrounds.put(TextCompareEntry.CHANGED, new Color(255, 255, 180));
217     }
218     
219     /**
220      * Creates panels
221      */
222     protected void createPanels() {
223         leftPanel = new JPanel(new BorderLayout());
224         rightPanel = new JPanel(new BorderLayout());
225         
226         bottomLeftPanel = new BottomPanel();
227         bottomRightPanel = new BottomPanel();
228         
229         bottomLeftPanel.setOtherPanel(bottomRightPanel);
230         bottomRightPanel.setOtherPanel(bottomLeftPanel);
231         
232         leftPanel.add(bottomLeftPanel, BorderLayout.SOUTH);
233         rightPanel.add(bottomRightPanel, BorderLayout.SOUTH);
234     }
235     
236     /**
237      * Creates lists (views)
238      */
239     protected void createLists() {
240         leftList = createLeftComponent();
241         rightList = createRightComponent();
242 
243         leftPopupMenu = createActionMenu(0);
244         rightPopupMenu = createActionMenu(1);
245         
246         leftList.addFocusListener(new ListFocusListener(0));
247         rightList.addFocusListener(new ListFocusListener(1));
248     }
249     
250     /**
251      * This method is to create left view component
252      * @return left view component
253      */
254     protected abstract JComponent createLeftComponent();
255 
256     /**
257      * This method is to create right view component
258      * @return right view component
259      */
260     protected abstract JComponent createRightComponent();
261     
262     /**
263      * Creates scroll panes
264      */
265     protected void createScrollPanes() {
266         leftScrollPane = new JScrollPane(leftList);
267         rightScrollPane = new JScrollPane(rightList);
268         
269         leftScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
270         rightScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
271         leftScrollPane.getViewport().setScrollMode(JViewport.BACKINGSTORE_SCROLL_MODE);
272         rightScrollPane.getViewport().setScrollMode(JViewport.BACKINGSTORE_SCROLL_MODE);
273         
274         
275         leftPanel.add(leftScrollPane, BorderLayout.CENTER);
276         rightPanel.add(rightScrollPane, BorderLayout.CENTER);
277         
278         leftScrollPane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
279             public void adjustmentValueChanged(AdjustmentEvent e) {
280                 rightScrollPane.getVerticalScrollBar().setValue(e.getAdjustable().getValue());
281             }
282         });
283         
284         rightScrollPane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
285             public void adjustmentValueChanged(AdjustmentEvent e) {
286                 leftScrollPane.getVerticalScrollBar().setValue(e.getAdjustable().getValue());
287             }
288         });        
289     }
290     
291     /**
292      * This method is called to create popup menu
293      * @param column column it is invoked for
294      * @return popup menu
295      */
296     protected JPopupMenu createActionMenu(int column) {
297         JPopupMenu menu = new JPopupMenu();
298         menu.addPropertyChangeListener("visible", new PropertyChangeListener() {
299             public void propertyChange(PropertyChangeEvent e) {
300                 JPopupMenu menu = (JPopupMenu)e.getSource();
301                 updateActions(menu);
302             }
303             
304             public void updateActions(Container container) {
305                 for (Component component : container.getComponents()) {
306                     if (component instanceof AbstractButton) {
307                         AbstractButton abstractButton = (AbstractButton)component;
308                         Action a = abstractButton.getAction();
309 
310                         boolean oldValue = component.isEnabled();
311                         boolean newValue = a.isEnabled();
312                         if (oldValue != newValue) {
313                             component.setEnabled(newValue);
314                         }
315                     } else if (component instanceof Container) {
316                         updateActions((Container)component);
317                     }
318                 }
319             }
320             
321         });
322         return menu;
323     }
324 
325     /**
326      * This method is called to populate menu bar
327      * @param menuBar menu bar
328      */
329     public void createMenu(JMenuBar menuBar) {
330     }
331 
332     /**
333      * Creates the actions for this component
334      */
335     protected void createActions() {
336     }
337 
338     /**
339      * Searches for a main menu with given name and if one is present it returns it.
340      * If not it creates one and returns it.
341      *  
342      * @param name name of menu
343      * @param menuBar menu bar to be searched
344      * @return existing or newly created menu
345      */
346     protected JMenu obtainMenu(String name, Container menuBar) {
347         int count = menuBar.getComponentCount();
348         for (int i = 0; i < count; i++) {
349             Component c = menuBar.getComponent(i);
350             if (c instanceof JMenu) {
351                 JMenu menu = (JMenu)c;
352                 String menuName = menu.getText();
353                 if (name.equals(menuName)) {
354                     return menu;
355                 }
356             }
357         }
358         JMenu menu = new JMenu(name);
359         menuBar.add(menu, 1);
360         return menu;
361     }
362     
363     /**
364      * This method adds horizontal bar that scrolls both panels
365      */
366     protected void addHorizontalBar() {
367         horizontalBar = new JScrollBar(JScrollBar.HORIZONTAL);
368 
369         horizontalBar.setMinimum(0);
370         horizontalBar.setValue(0);
371 
372         horizontalBar.addAdjustmentListener(new AdjustmentListener() {
373             public void adjustmentValueChanged(AdjustmentEvent e) {
374                 Adjustable a = e.getAdjustable();
375                 JViewport leftViewport = leftScrollPane.getViewport();
376                 JViewport rightViewport = rightScrollPane.getViewport();
377 
378                 Point leftPoint = leftViewport.getViewPosition();
379                 Point rightPoint = rightViewport.getViewPosition();
380                 
381                 leftPoint = new Point(a.getValue(), leftPoint.y);
382                 rightPoint = new Point(a.getValue(), rightPoint.y);
383                 
384                 leftViewport.setViewPosition(leftPoint);
385                 rightViewport.setViewPosition(rightPoint);
386                 
387             }
388         });
389         
390         compareModel.addListDataListener(new ListDataListener() {
391 
392             public void contentsChanged(ListDataEvent e) {
393                 updateScrollBar(horizontalBar, leftScrollPane, rightScrollPane);
394             }
395 
396             public void intervalAdded(ListDataEvent e) {
397                 updateScrollBar(horizontalBar, leftScrollPane, rightScrollPane);
398             }
399 
400             public void intervalRemoved(ListDataEvent e) {
401                 updateScrollBar(horizontalBar, leftScrollPane, rightScrollPane);
402             }
403             
404         });
405 
406         bottomRightPanel.add(horizontalBar, BorderLayout.CENTER);        
407 
408         updateScrollBar(horizontalBar, leftScrollPane, rightScrollPane);
409     }
410 
411     /**
412      * Creates an action and then stores it in a local cache
413      * @param name action name
414      * @return newly created action
415      */
416     protected BaseAction createCachedAction(String name) {
417         BaseAction action = (BaseAction)createAction(name);
418         if (action != null) {
419             actionsCache.put(name, action);
420         } else {
421             throw new NullPointerException(name);
422         }
423         return action;
424     }
425     
426     /**
427      * Creates an action and then stores it in a local cache
428      * @param name action name
429      */
430     protected void createCachedColumnActions(String name) {
431         BaseAction action = (BaseAction)createAction(name, 0);
432         if (action != null) {
433             actionsCache.put(name, action);
434         } else {
435             throw new NullPointerException(name);
436         }
437     }
438     
439     /**
440      * This is helper method that creates action based on given name
441      * @param name actio name
442      * @param column column
443      * @return newly created action or null if that fails
444      */
445     protected Action createAction(String name, int column) {
446         try {
447             Action a = instantiateAction(name);
448             updateAction(a, column);
449             return a;
450         } catch (Exception e) {
451             ErrorDialog.showError(null, "Trying to instantiate action for " + name, e);
452         }
453         return null;
454     }
455 
456     /**
457      * This method creates action of given name
458      * @param name action name
459      * @return action or <code>null</code> if it fails
460      */
461     protected Action createAction(String name) {
462         try {
463             Action a = instantiateAction(name);
464             updateAction(a);
465             return a;
466         } catch (Exception e) {
467             e.printStackTrace();
468             ErrorDialog.showError(null, "Trying to instantiate action for " + name, e);
469         }
470         return null;
471     }
472     
473     @SuppressWarnings("unchecked")
474     /**
475      * This method instantiates action
476      * @param name action name
477      */
478     protected Action instantiateAction(String name) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
479         Class c = null;
480         if (name.indexOf('.') > 0) {
481             // TODO Better handing of errors
482             try {
483                 c = Class.forName(name);
484             } catch (Exception ignore) {
485             }
486         }
487         if (c == null) {
488             try {
489                 String n = getClass().getPackage().getName() + ".action." + name;
490                 c = Class.forName(n);
491             } catch (Exception ignore) {
492             }
493         }
494         if (c == null) {
495             try {
496                 String n = "org.abstracthorizon.aequo.action." + name;
497                 c = Class.forName(n);
498             } catch (Exception ignore) {
499             }
500         }
501         if (c != null) {
502             Action a = (Action)c.newInstance();
503             return a;
504         } else {
505             throw new IllegalStateException("Cannot find class " + name);
506         }
507     }
508     
509     /**
510      * This method updates action with column
511      * @param a action
512      * @param column column
513      */
514     protected void updateAction(Action a, int column) {
515         updateAction(a);
516         if (a instanceof BaseAction) {
517             BaseAction baseAction = (BaseAction)a;
518             baseAction.setColumn(column);
519         }
520     }
521     
522     /**
523      * This method updates action with gobal context
524      * @param a action
525      */
526     protected void updateAction(Action a) {
527         if (a instanceof BaseAction) {
528             BaseAction baseAction = (BaseAction)a;
529             baseAction.setGlobalContext(getGlobalContext());
530         }
531     }
532     
533     /**
534      * Updates menu items with the values of action elements
535      * @param menu menu to be updated
536      */
537     protected void updateMenu(Component[] components) {
538         for (Component c : components) {
539             if (c instanceof JMenuItem) {
540                 JMenuItem item = (JMenuItem)c;
541                 if (item.getAction() instanceof BaseAction) {
542                     BaseAction action = (BaseAction)item.getAction();
543                     item.setEnabled(action.isEnabled());
544                     KeyStroke accelerator = (KeyStroke)action.getValue(Action.ACCELERATOR_KEY);
545                     item.setAccelerator(accelerator);
546                     Integer mnemonic = (Integer)action.getValue(Action.MNEMONIC_KEY);
547                     if (mnemonic != null) {
548                         int m = mnemonic.intValue();
549                         item.setMnemonic((char)m);
550                     }
551                 }
552             }
553         }
554     }
555 
556     /**
557      * Updates cached actions' column
558      * @param column
559      */
560     protected void updateCachedActionsColumn(int column) {
561         for (BaseAction action : actionsCache.values()) {
562             action.setColumn(column);
563         }
564     }
565 
566     /**
567      * Returns enclosing frame
568      * @return enclosing frame
569      */
570     protected JFrame getEnclosingFrame() {
571         Component c = this;
572         while ((c != null) && !(c instanceof JFrame)) {
573             c = c.getParent();
574         }
575         return (JFrame)c;
576     }
577     
578     /**
579      * This method updates scroll bar
580      * @param scrollBar scroll bar
581      * @param leftScrollPane left scroll pane
582      * @param rightScrollPane right scroll pane
583      */
584     protected static void updateScrollBar(JScrollBar scrollBar, JScrollPane leftScrollPane, JScrollPane rightScrollPane) {
585         int max = 0;
586         
587         int leftWidth = leftScrollPane.getComponent(0).getPreferredSize().width;
588         int rightWidth = rightScrollPane.getComponent(0).getPreferredSize().width;
589         
590         if (leftWidth > max) {
591             max = leftWidth;
592         }
593         if (rightWidth > max) {
594             max = rightWidth;
595         }
596         
597         
598         if (scrollBar.getValue() > max) {
599             scrollBar.setValue(max);
600         }
601         if (scrollBar.getMaximum() != max) {
602             leftWidth = leftScrollPane.getViewport().getWidth();
603             rightWidth = rightScrollPane.getViewport().getWidth();
604             if (leftWidth < rightWidth) {
605                 leftWidth = rightWidth;
606             }
607             
608             scrollBar.setMaximum(max - leftWidth);
609         }
610     }
611 
612     /**
613      * Bottom panel
614      */
615     protected static class BottomPanel extends JPanel {
616         
617         protected BottomPanel other;
618         
619         public BottomPanel() {
620             super(new BorderLayout());
621         }
622         
623         public void setOtherPanel(BottomPanel other) {
624             this.other = other;
625         }
626         
627         public BottomPanel getOtherPanel() {
628             return other;
629         }
630         
631         protected int getInternalPreferredHeight() {
632             return super.getPreferredSize().height;
633         }
634         
635         public Dimension getPreferredSize() {
636             Dimension d = super.getPreferredSize();
637             int otherHeight = other.getInternalPreferredHeight();
638             if (otherHeight > d.height) {
639                 d = new Dimension(d.width, otherHeight);
640                 super.setPreferredSize(d);
641             }
642             
643             return d;
644         }
645     }
646 
647     /**
648      * Listener that updates the menu when it is shown
649      * 
650      * @author Daniel Sendula
651      */
652     public class MenuChangeListener implements ChangeListener {
653 
654         public void stateChanged(ChangeEvent e) {
655             JMenu menu = (JMenu)e.getSource();
656             if (menu.isSelected()) {
657                 updateMenu(menu.getMenuComponents());
658             }
659         }
660     }
661     
662     
663     /** 
664      * List focus listener. It updates action column.
665      * 
666      * @author Daniel Sendula
667      */
668     public class ListFocusListener implements FocusListener {
669 
670         protected int column;
671         
672         public ListFocusListener(int column) {
673             this.column = column;
674         }
675 
676         public void focusGained(FocusEvent e) {
677             if (!e.isTemporary()) {
678                 updateCachedActionsColumn(column);
679             }
680         }
681 
682         public void focusLost(FocusEvent e) {
683             if (!e.isTemporary()) {
684                 updateCachedActionsColumn(-1);
685             }
686         }
687     }
688 
689 }