1
2
3
4
5
6
7
8
9
10
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
75
76
77
78 public class TextComparePanel extends ListComparePanel<String, TextCompareEntry> {
79
80
81 protected JSplitPane splitPane;
82
83
84 protected JPanel leftBottomBar;
85
86
87 protected JPanel rightBottomBar;
88
89
90 protected JLabel leftLineNo;
91
92
93 protected JLabel rightLineNo;
94
95
96 protected JLabel leftMiddle;
97
98
99 protected JLabel rightMiddle;
100
101
102 protected JLabel leftFiller;
103
104
105 protected TextLine leftLine;
106
107
108 protected TextLine rightLine;
109
110
111
112
113
114
115 public TextComparePanel(GlobalContext context, TextModel compareModel) {
116 super(context, compareModel);
117 }
118
119
120
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
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
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
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
378
379
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
403
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
445
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
457
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
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
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
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
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
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
605 TextModel model = (TextModel)compareModel;
606 saveAllAction.setEnabled(model.isLeftFileDirty() || model.isRightFileDirty());
607
608 }
609 }
610
611
612
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 }