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.text;
14  
15  import java.awt.BorderLayout;
16  import java.awt.Color;
17  import java.awt.Dimension;
18  import java.awt.GridLayout;
19  import java.awt.event.ActionEvent;
20  import java.awt.event.ActionListener;
21  import java.awt.event.ComponentEvent;
22  import java.awt.event.ComponentListener;
23  import java.beans.PropertyChangeEvent;
24  import java.beans.PropertyChangeListener;
25  import java.io.File;
26  import java.io.IOException;
27  import java.util.List;
28  
29  import javax.swing.Action;
30  import javax.swing.BorderFactory;
31  import javax.swing.BoundedRangeModel;
32  import javax.swing.JLabel;
33  import javax.swing.JList;
34  import javax.swing.JMenu;
35  import javax.swing.JMenuBar;
36  import javax.swing.JMenuItem;
37  import javax.swing.JPanel;
38  import javax.swing.JPopupMenu;
39  import javax.swing.JSeparator;
40  import javax.swing.JSplitPane;
41  import javax.swing.JTextField;
42  import javax.swing.JToolBar;
43  import javax.swing.border.BevelBorder;
44  import javax.swing.border.CompoundBorder;
45  import javax.swing.border.EmptyBorder;
46  import javax.swing.border.EtchedBorder;
47  import javax.swing.event.CaretEvent;
48  import javax.swing.event.CaretListener;
49  import javax.swing.event.ChangeEvent;
50  import javax.swing.event.ChangeListener;
51  import javax.swing.event.DocumentEvent;
52  import javax.swing.event.DocumentListener;
53  import javax.swing.event.ListSelectionEvent;
54  import javax.swing.event.ListSelectionListener;
55  import javax.swing.text.Document;
56  
57  import org.abstracthorizon.aequo.CompareEntry;
58  import org.abstracthorizon.aequo.GlobalContext;
59  import org.abstracthorizon.aequo.action.AboutAction;
60  import org.abstracthorizon.aequo.action.BaseAction;
61  import org.abstracthorizon.aequo.action.CloseWindowAction;
62  import org.abstracthorizon.aequo.gui.DropDownBoxComponent;
63  import org.abstracthorizon.aequo.gui.ErrorDialog;
64  import org.abstracthorizon.aequo.gui.LineBorder;
65  import org.abstracthorizon.aequo.gui.ListComparePanel;
66  import org.abstracthorizon.aequo.text.action.NextDifferenceAction;
67  import org.abstracthorizon.aequo.text.action.PreviousDifferenceAction;
68  import org.abstracthorizon.aequo.text.action.SaveAction;
69  import org.abstracthorizon.aequo.text.action.TextBaseAction;
70  import org.abstracthorizon.aequo.util.StringUtils;
71  import org.abstracthorizon.aequo.util.prefs.Recent;
72  
73  /**
74   * Text compare panel.
75   *
76   * @author Daniel Sendula
77   */
78  public class TextComparePanel extends ListComparePanel<String, TextCompareEntry> {
79  
80      /** Divider pane */
81      protected JSplitPane splitPane;
82  
83      /** Left bottom bar */
84      protected JPanel leftBottomBar;
85  
86      /** Right bottom bar */
87      protected JPanel rightBottomBar;
88  
89      /** Left line number label */
90      protected JLabel leftLineNo;
91  
92      /** Right line number label */
93      protected JLabel rightLineNo;
94  
95      /** Left middle status bar label */
96      protected JLabel leftMiddle;
97  
98      /** Right middle status bar label */
99      protected JLabel rightMiddle;
100 
101     /** Left filler bar */
102     protected JLabel leftFiller;
103 
104     /** Left line */
105     protected TextLine leftLine;
106     
107     /** Right line */
108     protected TextLine rightLine;
109     
110     /**
111      * Constructor
112      * @param context global context
113      * @param compareModel model
114      */
115     public TextComparePanel(GlobalContext context, TextModel compareModel) {
116         super(context, compareModel);
117     }
118 
119     /**
120      * Initialises panel
121      */
122     public void initialise() {
123         splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
124 
125         super.initialise();
126 
127         addHorizontalBar();
128 
129         leftBottomBar = new JPanel(new BorderLayout());
130         rightBottomBar = new JPanel(new BorderLayout());
131 
132         leftLineNo = new JLabel("1", JLabel.RIGHT);
133         rightLineNo = new JLabel("1", JLabel.RIGHT);
134 
135         leftLineNo.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
136         rightLineNo.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
137 
138         leftMiddle = new JLabel("Text", JLabel.LEFT);
139         rightMiddle = new JLabel("Text", JLabel.LEFT);
140 
141         leftMiddle.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
142         rightMiddle.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
143 
144         leftFiller = new JLabel("", JLabel.CENTER);
145         leftFiller.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
146 
147         compareModel.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
148             public void valueChanged(ListSelectionEvent e) {
149                 if (!e.getValueIsAdjusting()) {
150                     int i = compareModel.getSelectionModel().getLeadSelectionIndex();
151                     if ((i >= 0) && (i < compareModel.getSize())) {
152                         TextCompareEntry entry = compareModel.get(i);
153                         String left = entry.getData(0);
154                         String right = entry.getData(1);
155                         
156                         String leftLineNoStr = Integer.toString(entry.getLineNumber(0));
157                         String rightLineNoStr = Integer.toString(entry.getLineNumber(1));
158                         leftLineNo.setText(leftLineNoStr);
159                         rightLineNo.setText(rightLineNoStr);
160                         if ((left == right) || ((left != null) && left.equals(right))) {
161                             leftFiller.setText("Same");
162                         } else if ((left != null) && (right != null)) {
163                             left = StringUtils.collapseWhitespace(left);
164                             right = StringUtils.collapseWhitespace(right);
165                             if (left.equals(right)) {
166                                 leftFiller.setText("Similar");
167                             } else {
168                                 leftFiller.setText("Different");
169                             }
170                         } else {
171                             leftFiller.setText("Different");
172                         }
173                         
174                     }
175                 }
176             }
177         });
178 
179         leftLineNo.setPreferredSize(new Dimension(50, leftLineNo.getPreferredSize().height));
180         rightLineNo.setPreferredSize(new Dimension(50, rightLineNo.getPreferredSize().height));
181 
182         leftBottomBar.add(leftLineNo, BorderLayout.WEST);
183         leftBottomBar.add(leftMiddle, BorderLayout.CENTER);
184         bottomLeftPanel.add(leftFiller, BorderLayout.CENTER);
185 
186         rightBottomBar.add(rightLineNo, BorderLayout.WEST);
187         rightBottomBar.add(rightMiddle, BorderLayout.CENTER);
188 
189         bottomLeftPanel.add(leftBottomBar, BorderLayout.WEST);
190         bottomRightPanel.add(rightBottomBar, BorderLayout.WEST);
191 
192 
193         splitPane.setLeftComponent(leftPanel);
194         splitPane.setRightComponent(rightPanel);
195         splitPane.setResizeWeight(0.5d);
196         add(splitPane, BorderLayout.CENTER);
197         splitPane.setDividerLocation(0.5d);
198         
199         leftList.addComponentListener(new ComponentListener() {
200 
201             boolean first = true;
202             public void componentHidden(ComponentEvent e) { }
203             public void componentMoved(ComponentEvent e) { }
204             public void componentShown(ComponentEvent e) {}
205 
206             public void componentResized(ComponentEvent e) {
207                 if (first) {
208                     int firstDifference = findFirstDifference();
209                     if (firstDifference >= 0) {
210                         JList list = (JList)leftList;
211                         int rowCount = list.getLastVisibleIndex() - list.getFirstVisibleIndex();
212                         int index = firstDifference + (rowCount / 2) + 2;
213                         if (index < 0) {
214                             index = 0;
215                         }
216                         list.ensureIndexIsVisible(index);
217                     }
218                     first = false;
219                 }
220             }
221             
222         });
223         
224         leftLine = new TextLine();
225         leftLine.setBorder(new CompoundBorder(new LineBorder(0, 0, 1, 0), new EmptyBorder(1, 1, 1, 1)));
226         rightLine = new TextLine();
227         rightLine.setBorder(new EmptyBorder(1, 1, 1, 1));
228         
229         PositionSynchroniser positionSynchroniser = new PositionSynchroniser();
230         leftLine.addCaretListener(positionSynchroniser);
231         rightLine.addCaretListener(positionSynchroniser);
232         leftLine.getHorizontalVisibility().addChangeListener(positionSynchroniser);
233         rightLine.getHorizontalVisibility().addChangeListener(positionSynchroniser);
234         leftLine.getDocument().addDocumentListener(positionSynchroniser);
235         rightLine.getDocument().addDocumentListener(positionSynchroniser);
236         
237         
238         JPanel linesPanel = new JPanel(new GridLayout(2, 1));
239         linesPanel.add(leftLine);
240         linesPanel.add(rightLine);
241         linesPanel.setBorder(new EtchedBorder(EtchedBorder.LOWERED));
242         add(linesPanel, BorderLayout.SOUTH);
243         
244         compareModel.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
245 
246             public void valueChanged(ListSelectionEvent e) {
247                 int i = compareModel.getSelectionModel().getLeadSelectionIndex();
248                 if ((i >= 0) && (i < compareModel.getSize())) {
249                     String left = compareModel.get(i).getData(0);
250                     String right = compareModel.get(i).getData(1);
251                     leftLine.suspendEvents();
252                     leftLine.setText(left);
253                     leftLine.resumeEvents();
254                     rightLine.suspendEvents();
255                     rightLine.setText(right);
256                     rightLine.resumeEvents();
257                 }
258             }
259             
260         });
261         
262     }
263 
264     /**
265      * Sets up text comparison colours 
266      */
267     protected void setUpColors() {
268         foreGrounds.put(CompareEntry.EMPTY, Color.BLACK);
269         backGrounds.put(CompareEntry.EMPTY, Color.LIGHT_GRAY.brighter());
270 
271         foreGrounds.put(CompareEntry.EQUAL, Color.BLACK);
272         backGrounds.put(CompareEntry.EQUAL, Color.WHITE);
273 
274         foreGrounds.put(CompareEntry.LESS, Color.BLACK);
275         backGrounds.put(CompareEntry.LESS, new Color(255, 184, 184));
276 
277         foreGrounds.put(CompareEntry.GREATER, Color.BLACK);
278         backGrounds.put(CompareEntry.GREATER, new Color(255, 184, 184));
279 
280         foreGrounds.put(CompareEntry.DIFFERENT, Color.BLACK);
281         backGrounds.put(CompareEntry.DIFFERENT, new Color(255, 184, 184));
282 
283         foreGrounds.put(TextCompareEntry.SIMILAR, Color.BLACK);
284         backGrounds.put(TextCompareEntry.SIMILAR, new Color(255, 208, 208));
285 
286         foreGrounds.put(TextCompareEntry.CHANGED, Color.BLACK);
287         backGrounds.put(TextCompareEntry.CHANGED, new Color(255, 255, 180));
288     }
289 
290     /**
291      * Creates the actions for this component
292      */
293     protected void createActions() {
294         createCachedColumnActions("CopyToAction");
295         createCachedColumnActions("MoveToAction");
296 
297         createCachedColumnActions("CutAction");
298         createCachedColumnActions("CopyAction");
299         createCachedColumnActions("PasteOverAction");
300         createCachedColumnActions("DeleteAction");
301 
302         createCachedColumnActions("ReSynchroniseAction");
303         createCachedColumnActions("SplitAction");
304         createCachedColumnActions("InsertLineAction");
305         
306         BaseAction saveAllAction = createCachedAction("SaveAllAction");
307         saveAllAction.setEnabled(false);
308         
309         createCachedAction("RefreshAllAction");
310         createCachedAction("SelectAllAction");
311 
312         NextDifferenceAction nextDifferenceAction = (NextDifferenceAction)createAction("NextDifferenceAction");
313         nextDifferenceAction.setTextComparePanel(this);
314         actionsCache.put("NextDifferenceAction", nextDifferenceAction);
315         
316         PreviousDifferenceAction previousDifferenceAction = (PreviousDifferenceAction)createAction("PreviousDifferenceAction");
317         previousDifferenceAction.setTextComparePanel(this);
318         actionsCache.put("PreviousDifferenceAction", previousDifferenceAction);
319         
320     }
321 
322     /**
323      * Adds tool bar to the top of the panel 
324      */
325     protected void createPanels() {
326         super.createPanels();
327 
328         JToolBar topToolBar = new JToolBar();
329         topToolBar.add(createAction("org.abstracthorizon.aequo.action.NewSessionAction"));
330         topToolBar.addSeparator();
331         topToolBar.add(actionsCache.get("SaveAllAction"));
332         topToolBar.add(actionsCache.get("RefreshAllAction"));
333         topToolBar.addSeparator();
334         topToolBar.add(actionsCache.get("NextDifferenceAction"));
335         topToolBar.add(actionsCache.get("PreviousDifferenceAction"));
336         topToolBar.addSeparator();
337         topToolBar.add(new JSeparator());
338         AboutAction aboutAction = (AboutAction)createAction("AboutAction");
339         aboutAction.setName("<=>");
340         topToolBar.add(aboutAction);
341 
342         add(topToolBar, BorderLayout.NORTH);
343         
344         JToolBar leftToolBar = new JToolBar();
345         leftPanel.add(leftToolBar, BorderLayout.NORTH);
346 
347         SaveAction save0Action = (SaveAction)createAction("SaveAction", 0);
348         save0Action.setEnabled(false);
349         leftToolBar.add(save0Action);
350 
351         List<String> oldFiles = Recent.allRecent(getGlobalContext().getPreferences(), "compare.file");
352 
353         DropDownBoxComponent leftFileInput = new DropDownBoxComponent(((TextModel)compareModel).getLeftFile().getAbsolutePath(), oldFiles);
354         leftFileInput.addActionListener(new FileDropdownListener(getGlobalContext(), (TextModel)compareModel, 0));
355         leftToolBar.addSeparator();
356         leftToolBar.add(leftFileInput);
357         leftToolBar.addSeparator();
358         
359         JToolBar rightToolBar = new JToolBar();
360         rightPanel.add(rightToolBar, BorderLayout.NORTH);
361 
362         SaveAction save1Action = (SaveAction)createAction("SaveAction", 1);
363         save1Action.setEnabled(false);
364         rightToolBar.add(save1Action);
365 
366         DropDownBoxComponent rightFileInput = new DropDownBoxComponent(((TextModel)compareModel).getLeftFile().getAbsolutePath(), oldFiles);
367         rightFileInput.addActionListener(new FileDropdownListener(getGlobalContext(), (TextModel)compareModel, 1));
368         rightToolBar.addSeparator();
369         rightToolBar.add(rightFileInput);
370         rightToolBar.addSeparator();
371 
372         ((TextModel)compareModel).addPropertyChangeListener("leftFileDirty", new FileDirtyListener(save0Action));
373         ((TextModel)compareModel).addPropertyChangeListener("rightFileDirty", new FileDirtyListener(save1Action));
374     }
375 
376     /**
377      * Creates popup menu
378      * @param column column
379      * @return popup menu
380      */
381     protected JPopupMenu createActionMenu(int column) {
382         JPopupMenu menu = super.createActionMenu(column);
383         
384         menu.add(actionsCache.get("CopyToAction"));
385         menu.add(actionsCache.get("MoveToAction"));
386         menu.add(new JSeparator());
387         menu.add(actionsCache.get("ReSynchroniseAction"));
388         menu.add(actionsCache.get("SplitAction"));
389         menu.add(actionsCache.get("InsertLineAction"));
390         menu.add(new JSeparator());
391         menu.add(actionsCache.get("CutAction"));
392         menu.add(actionsCache.get("CopyAction"));
393         menu.add(actionsCache.get("PasteOverAction"));
394         menu.add(new JSeparator());
395         menu.add(actionsCache.get("DeleteAction"));
396 
397         updateCachedActionsColumn(column);
398         return menu;
399     }
400 
401     /**
402      * Adds elements to menu bar
403      * @param menuBar menu bar
404      */
405     public void createMenu(JMenuBar menuBar) {
406         super.createMenu(menuBar);
407 
408         JMenu fileMenu = obtainMenu("File", menuBar);
409         JMenuItem menuItem = new JMenuItem(actionsCache.get("SaveAllAction"));
410         fileMenu.add(menuItem, 2);
411         CloseWindowAction closeAction = new CloseWindowAction();
412         closeAction.setWindow(getEnclosingFrame());
413         JMenuItem closeWindowAction = new JMenuItem(closeAction);
414         fileMenu.add(closeWindowAction, 4);
415         
416         JMenu editMenu = obtainMenu("Edit", menuBar);
417         editMenu.add(actionsCache.get("CopyToAction"));
418         editMenu.add(actionsCache.get("MoveToAction"));
419         editMenu.addSeparator();
420         editMenu.add(actionsCache.get("ReSynchroniseAction"));
421         editMenu.add(actionsCache.get("SplitAction"));
422         editMenu.add(actionsCache.get("InsertLineAction"));
423         editMenu.addSeparator();
424         editMenu.add(actionsCache.get("CutAction"));
425         editMenu.add(actionsCache.get("CopyAction"));
426         editMenu.add(actionsCache.get("PasteOverAction"));
427         editMenu.addSeparator();
428         editMenu.add(actionsCache.get("SelectAllAction"));
429         editMenu.addSeparator();
430         editMenu.add(actionsCache.get("DeleteAction"));
431         editMenu.addChangeListener(new MenuChangeListener());
432         
433         JMenu viewMenu = obtainMenu("View", menuBar);
434         menuBar.add(viewMenu);
435         viewMenu.add(actionsCache.get("RefreshAllAction"));
436         viewMenu.addSeparator();
437         viewMenu.add(actionsCache.get("NextDifferenceAction"));
438         viewMenu.add(actionsCache.get("PreviousDifferenceAction"));
439         
440         viewMenu.addChangeListener(new MenuChangeListener());
441     }
442 
443     /**
444      * Updates action adding text compare model to them if they are of {@link TextBaseAction} type
445      * @param a action
446      */
447     protected void updateAction(Action a) {
448         super.updateAction(a);
449         if (a instanceof TextBaseAction) {
450             TextBaseAction textBaseAction = (TextBaseAction)a;
451             textBaseAction.setTextModel((TextModel)compareModel);
452         }
453     }
454     
455     /**
456      * Fins first difference in the panel
457      * @return index of first difference or -1
458      */
459     protected int findFirstDifference() {
460         for (int i = 0; i < compareModel.getSize(); i++) {
461             CompareEntry<?> entry = compareModel.get(i);
462             if (entry.getStatus(0) != CompareEntry.EQUAL) {
463                 return i;
464             }
465         }
466         return -1;
467     }
468 
469 
470     /**
471      * Action listener class that updates appropdiate file model column
472      */
473     protected static class FileDropdownListener implements ActionListener {
474         
475         protected int column;
476         
477         protected TextModel textModel;
478         
479         protected GlobalContext context;
480         
481         public FileDropdownListener(GlobalContext context, TextModel filesModel, int column) {
482             this.context = context;
483             this.column = column;
484             this.textModel = filesModel;
485         }
486         
487         public void actionPerformed(ActionEvent event) {
488             DropDownBoxComponent component = (DropDownBoxComponent)event.getSource();
489             File file = new File(component.getSelected());
490             if (file.exists()) {
491                 try {
492                     List<String> newData = TextModel.loadFile(file);
493                     List<String> oldData = textModel.getAsList(1 - column);
494                     if (column == 0) {
495                         textModel.refresh(newData, oldData);
496                     } else {
497                         textModel.refresh(oldData, newData);
498                     }
499                     Recent.writeNew(context.getPreferences(), "compare.file", file.getAbsolutePath());
500                 } catch (IOException e) {
501                     ErrorDialog.showError(null, "Problem loading file " + file.getAbsolutePath(), e);
502                 }
503             }
504         }
505     }
506 
507     /**
508      * Caret listener that synchronises two lines.
509      */
510     protected class PositionSynchroniser implements CaretListener, ChangeListener, DocumentListener {
511 
512         protected void synchronise(Document source) {
513             if (leftLine.getDocument() == source) {
514                 synchronise(leftLine);
515             } else {
516                 synchronise(rightLine);
517             }
518         }
519 
520         protected void synchronise(BoundedRangeModel source) {
521         }
522 
523         protected void synchronise(Object source) {
524             JTextField field = (JTextField)source;
525             JTextField other = leftLine;
526             if (other == field) {
527                 other = rightLine;
528             }
529 //            other.setCaretPosition(field.getCaretPosition());
530             other.setScrollOffset(field.getScrollOffset());
531             other.repaint();
532         }
533 
534         public void changedUpdate(DocumentEvent e) {
535             synchronise(e.getDocument());
536         }
537 
538         public void insertUpdate(DocumentEvent e) {
539             synchronise(e.getDocument());
540         }
541 
542         public void removeUpdate(DocumentEvent e) {
543             synchronise(e.getDocument());
544         }
545 
546         public void caretUpdate(CaretEvent e) {
547             synchronise(e.getSource());
548         }
549 
550         public void stateChanged(ChangeEvent e) {
551             TextLine line = rightLine;
552             if (leftLine.getHorizontalVisibility() == e.getSource()) {
553                 line = leftLine;
554             }
555             if (!line.isSuspendEvents()) {
556                 synchronise(line);
557                 int i = compareModel.getSelectionModel().getLeadSelectionIndex();
558                 if (i > 0) {
559                     TextCompareEntry entry = compareModel.get(i);
560                     String newValue = line.getText();
561                     String oldValue;
562                     if (line == leftLine) {
563                         oldValue = entry.getData(0);
564                     } else {
565                         oldValue = entry.getData(1);
566                     }
567                     boolean changed = false;
568                     if (!newValue.equals(oldValue)) {
569                         if ((oldValue != null) || (newValue.length() > 0)) {
570                             changed = true;
571                         }
572                     }
573                     if (changed) {
574                         // TODO UNDO
575                         int column = 0;
576                         if (line == leftLine) {
577                             column = 0;
578                         } else {
579                             column = 1;
580                         }
581                         entry.setData(column, line.getText());
582                         ((TextModel)compareModel).setFileDirty(column);
583                         ((TextModel)compareModel).notifyUpdate(i, i);
584                     }
585                 }
586             }
587         }
588     }
589     
590     /**
591      * Class that listens on file dirty events
592      */
593     public class FileDirtyListener implements PropertyChangeListener {
594         protected SaveAction action;
595 
596         public FileDirtyListener(SaveAction action) {
597             this.action = action;
598         }
599 
600         public void propertyChange(PropertyChangeEvent evt) {
601             action.setEnabled(((Boolean)evt.getNewValue()));
602             
603             BaseAction saveAllAction = (BaseAction)actionsCache.get("SaveAllAction");
604 //            BaseAction refreshAllAction = (BaseAction)actionsCache.get("SaveAllAction");
605             TextModel model = (TextModel)compareModel;
606             saveAllAction.setEnabled(model.isLeftFileDirty() || model.isRightFileDirty());
607 //            refreshAllAction.setEnabled(model.isLeftFileDirty() || model.isRightFileDirty());
608         }
609     }
610     
611     /**
612      * Class that adds susped events functionality
613      */
614     public static class TextLine extends JTextField {
615         
616         protected boolean ignoreEvents;
617         
618         public void suspendEvents() {
619             ignoreEvents = true;
620         }
621         
622         public void resumeEvents() {
623             ignoreEvents = false;
624         }
625         
626         public boolean isSuspendEvents() {
627             return ignoreEvents;
628         }
629     }
630 }