source: josm/trunk/src/org/openstreetmap/josm/gui/conflict/ListMerger.java@ 1926

Last change on this file since 1926 was 1655, checked in by stoecker, 15 years ago

better i18n of new conflict stuff

File size: 36.5 KB
Line 
1package org.openstreetmap.josm.gui.conflict;
2
3import static org.openstreetmap.josm.tools.I18n.tr;
4import static org.openstreetmap.josm.tools.I18n.trn;
5
6import java.awt.Adjustable;
7import java.awt.FlowLayout;
8import java.awt.GridBagConstraints;
9import java.awt.GridBagLayout;
10import java.awt.Insets;
11import java.awt.event.ActionEvent;
12import java.awt.event.AdjustmentEvent;
13import java.awt.event.AdjustmentListener;
14import java.awt.event.ItemEvent;
15import java.awt.event.ItemListener;
16import java.beans.PropertyChangeEvent;
17import java.beans.PropertyChangeListener;
18import java.util.ArrayList;
19import java.util.HashMap;
20import java.util.Observable;
21import java.util.Observer;
22import java.util.logging.Logger;
23
24import javax.swing.AbstractAction;
25import javax.swing.Action;
26import javax.swing.ImageIcon;
27import javax.swing.JButton;
28import javax.swing.JCheckBox;
29import javax.swing.JComboBox;
30import javax.swing.JLabel;
31import javax.swing.JPanel;
32import javax.swing.JScrollPane;
33import javax.swing.JTable;
34import javax.swing.JToggleButton;
35import javax.swing.event.ListSelectionEvent;
36import javax.swing.event.ListSelectionListener;
37
38import org.openstreetmap.josm.tools.ImageProvider;
39
40/**
41 * A UI component for resolving conflicts in two lists of entries of type T.
42 *
43 * @param T the type of the entries
44 * @see ListMergeModel
45 */
46public abstract class ListMerger<T> extends JPanel implements PropertyChangeListener, Observer {
47 private static final Logger logger = Logger.getLogger(ListMerger.class.getName());
48
49 protected JTable myEntriesTable;
50 protected JTable mergedEntriesTable;
51 protected JTable theirEntriesTable;
52
53 protected ListMergeModel<T> model;
54
55 private CopyStartLeftAction copyStartLeftAction;
56 private CopyBeforeCurrentLeftAction copyBeforeCurrentLeftAction;
57 private CopyAfterCurrentLeftAction copyAfterCurrentLeftAction;
58 private CopyEndLeftAction copyEndLeftAction;
59
60 private CopyStartRightAction copyStartRightAction;
61 private CopyBeforeCurrentRightAction copyBeforeCurrentRightAction;
62 private CopyAfterCurrentRightAction copyAfterCurrentRightAction;
63 private CopyEndRightAction copyEndRightAction;
64
65 private MoveUpMergedAction moveUpMergedAction;
66 private MoveDownMergedAction moveDownMergedAction;
67 private RemoveMergedAction removeMergedAction;
68 private FreezeAction freezeAction;
69
70 private AdjustmentSynchronizer adjustmentSynchronizer;
71
72 private JCheckBox cbLockMyScrolling;
73 private JCheckBox cbLockMergedScrolling;
74 private JCheckBox cbLockTheirScrolling;
75
76 private JLabel lblMyVersion;
77 private JLabel lblMergedVersion;
78 private JLabel lblTheirVersion;
79
80
81 private JLabel lblFrozenState;
82
83 abstract protected JScrollPane buildMyElementsTable();
84 abstract protected JScrollPane buildMergedElementsTable();
85 abstract protected JScrollPane buildTheirElementsTable();
86
87 protected JScrollPane embeddInScrollPane(JTable table) {
88 JScrollPane pane = new JScrollPane(table);
89 pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
90 pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
91 if (adjustmentSynchronizer == null) {
92 adjustmentSynchronizer = new AdjustmentSynchronizer();
93 }
94 return pane;
95 }
96
97 protected void wireActionsToSelectionModels() {
98 myEntriesTable.getSelectionModel().addListSelectionListener(copyStartLeftAction);
99
100 myEntriesTable.getSelectionModel().addListSelectionListener(copyBeforeCurrentLeftAction);
101 mergedEntriesTable.getSelectionModel().addListSelectionListener(copyBeforeCurrentLeftAction);
102
103 myEntriesTable.getSelectionModel().addListSelectionListener(copyAfterCurrentLeftAction);
104 mergedEntriesTable.getSelectionModel().addListSelectionListener(copyAfterCurrentLeftAction);
105
106 myEntriesTable.getSelectionModel().addListSelectionListener(copyEndLeftAction);
107
108
109 theirEntriesTable.getSelectionModel().addListSelectionListener(copyStartRightAction);
110
111 theirEntriesTable.getSelectionModel().addListSelectionListener(copyBeforeCurrentRightAction);
112 mergedEntriesTable.getSelectionModel().addListSelectionListener(copyBeforeCurrentRightAction);
113
114 theirEntriesTable.getSelectionModel().addListSelectionListener(copyAfterCurrentRightAction);
115 mergedEntriesTable.getSelectionModel().addListSelectionListener(copyAfterCurrentRightAction);
116
117 theirEntriesTable.getSelectionModel().addListSelectionListener(copyEndRightAction);
118
119 mergedEntriesTable.getSelectionModel().addListSelectionListener(moveUpMergedAction);
120 mergedEntriesTable.getSelectionModel().addListSelectionListener(moveDownMergedAction);
121 mergedEntriesTable.getSelectionModel().addListSelectionListener(removeMergedAction);
122 }
123
124 protected JPanel buildLeftButtonPanel() {
125 JPanel pnl = new JPanel();
126 pnl.setLayout(new GridBagLayout());
127 GridBagConstraints gc = new GridBagConstraints();
128
129 gc.gridx = 0;
130 gc.gridy = 0;
131 copyStartLeftAction = new CopyStartLeftAction();
132 JButton btn = new JButton(copyStartLeftAction);
133 btn.setName("button.copystartleft");
134 pnl.add(btn, gc);
135
136 gc.gridx = 0;
137 gc.gridy = 1;
138 copyBeforeCurrentLeftAction = new CopyBeforeCurrentLeftAction();
139 btn = new JButton(copyBeforeCurrentLeftAction);
140 btn.setName("button.copybeforecurrentleft");
141 pnl.add(btn, gc);
142
143 gc.gridx = 0;
144 gc.gridy = 2;
145 copyAfterCurrentLeftAction = new CopyAfterCurrentLeftAction();
146 btn = new JButton(copyAfterCurrentLeftAction);
147 btn.setName("button.copyaftercurrentleft");
148 pnl.add(btn, gc);
149
150 gc.gridx = 0;
151 gc.gridy = 3;
152 copyEndLeftAction = new CopyEndLeftAction();
153 btn = new JButton(copyEndLeftAction);
154 btn.setName("button.copyendleft");
155 pnl.add(btn, gc);
156
157 return pnl;
158 }
159
160 protected JPanel buildRightButtonPanel() {
161 JPanel pnl = new JPanel();
162 pnl.setLayout(new GridBagLayout());
163 GridBagConstraints gc = new GridBagConstraints();
164
165 gc.gridx = 0;
166 gc.gridy = 0;
167 copyStartRightAction = new CopyStartRightAction();
168 pnl.add(new JButton(copyStartRightAction), gc);
169
170 gc.gridx = 0;
171 gc.gridy = 1;
172 copyBeforeCurrentRightAction = new CopyBeforeCurrentRightAction();
173 pnl.add(new JButton(copyBeforeCurrentRightAction), gc);
174
175 gc.gridx = 0;
176 gc.gridy = 2;
177 copyAfterCurrentRightAction = new CopyAfterCurrentRightAction();
178 pnl.add(new JButton(copyAfterCurrentRightAction), gc);
179
180 gc.gridx = 0;
181 gc.gridy = 3;
182 copyEndRightAction = new CopyEndRightAction();
183 pnl.add(new JButton(copyEndRightAction), gc);
184
185 return pnl;
186 }
187
188 protected JPanel buildMergedListControlButtons() {
189 JPanel pnl = new JPanel();
190 pnl.setLayout(new GridBagLayout());
191 GridBagConstraints gc = new GridBagConstraints();
192
193 gc.gridx = 0;
194 gc.gridy = 0;
195 gc.gridwidth = 1;
196 gc.gridheight = 1;
197 gc.fill = GridBagConstraints.HORIZONTAL;
198 gc.anchor = GridBagConstraints.CENTER;
199 gc.weightx = 0.3;
200 gc.weighty = 0.0;
201 moveUpMergedAction = new MoveUpMergedAction();
202 pnl.add(new JButton(moveUpMergedAction), gc);
203
204 gc.gridx = 1;
205 gc.gridy = 0;
206 moveDownMergedAction = new MoveDownMergedAction();
207 pnl.add(new JButton(moveDownMergedAction), gc);
208
209 gc.gridx = 2;
210 gc.gridy = 0;
211 removeMergedAction = new RemoveMergedAction();
212 pnl.add(new JButton(removeMergedAction), gc);
213
214 return pnl;
215 }
216
217 protected JPanel buildAdjustmentLockControlPanel(JCheckBox cb) {
218 JPanel panel = new JPanel();
219 panel.setLayout(new FlowLayout(FlowLayout.RIGHT));
220 panel.add(new JLabel(tr("lock scrolling")));
221 panel.add(cb);
222 return panel;
223 }
224
225 protected JPanel buildComparePairSelectionPanel() {
226 JPanel p = new JPanel();
227 p.setLayout(new FlowLayout(FlowLayout.LEFT));
228 p.add(new JLabel(tr("Compare ")));
229 JComboBox cbComparePair =new JComboBox(model.getComparePairListModel());
230 cbComparePair.setRenderer(new ComparePairListCellRenderer());
231 p.add(cbComparePair);
232 return p;
233 }
234
235 protected JPanel buildFrozeStateControlPanel() {
236 JPanel p = new JPanel();
237 p.setLayout(new FlowLayout(FlowLayout.LEFT));
238 lblFrozenState = new JLabel();
239 p.add(lblFrozenState);
240 freezeAction = new FreezeAction();
241 JToggleButton btn = new JToggleButton(freezeAction);
242 freezeAction.adapt(btn);
243 btn.setName("button.freeze");
244 p.add(btn);
245
246 return p;
247 }
248
249 protected void build() {
250 setLayout(new GridBagLayout());
251 GridBagConstraints gc = new GridBagConstraints();
252
253 // ------------------
254 gc.gridx = 0;
255 gc.gridy = 0;
256 gc.gridwidth = 1;
257 gc.gridheight = 1;
258 gc.fill = GridBagConstraints.NONE;
259 gc.anchor = GridBagConstraints.CENTER;
260 gc.weightx = 0.0;
261 gc.weighty = 0.0;
262 gc.insets = new Insets(10,0,0,0);
263 lblMyVersion = new JLabel(tr("My version"));
264 lblMyVersion.setToolTipText(tr("List of elements in my dataset, i.e. the local dataset"));
265 add(lblMyVersion, gc);
266
267 gc.gridx = 2;
268 gc.gridy = 0;
269 lblMergedVersion = new JLabel(tr("Merged version"));
270 lblMergedVersion.setToolTipText(tr("List of merged elements. They will replace the my elements when the merge decisions are applied."));
271 add(lblMergedVersion, gc);
272
273 gc.gridx = 4;
274 gc.gridy = 0;
275 lblTheirVersion = new JLabel(tr("Their version"));
276 lblTheirVersion.setToolTipText(tr("List of elements in their dataset, i.e. the server dataset"));
277 add(lblTheirVersion, gc);
278
279 // ------------------------------
280 gc.gridx = 0;
281 gc.gridy = 1;
282 gc.gridwidth = 1;
283 gc.gridheight = 1;
284 gc.fill = GridBagConstraints.HORIZONTAL;
285 gc.anchor = GridBagConstraints.FIRST_LINE_START;
286 gc.weightx = 0.33;
287 gc.weighty = 0.0;
288 gc.insets = new Insets(0,0,0,0);
289 cbLockMyScrolling = new JCheckBox();
290 cbLockMyScrolling.setName("checkbox.lockmyscrolling");
291 add(buildAdjustmentLockControlPanel(cbLockMyScrolling), gc);
292
293 gc.gridx = 2;
294 gc.gridy = 1;
295 cbLockMergedScrolling = new JCheckBox();
296 cbLockMergedScrolling.setName("checkbox.lockmergedscrolling");
297 add(buildAdjustmentLockControlPanel(cbLockMergedScrolling), gc);
298
299 gc.gridx = 4;
300 gc.gridy = 1;
301 cbLockTheirScrolling = new JCheckBox();
302 cbLockTheirScrolling.setName("checkbox.locktheirscrolling");
303 add(buildAdjustmentLockControlPanel(cbLockTheirScrolling), gc);
304
305 // --------------------------------
306 gc.gridx = 0;
307 gc.gridy = 2;
308 gc.gridwidth = 1;
309 gc.gridheight = 1;
310 gc.fill = GridBagConstraints.BOTH;
311 gc.anchor = GridBagConstraints.FIRST_LINE_START;
312 gc.weightx = 0.33;
313 gc.weighty = 1.0;
314 gc.insets = new Insets(0,0,0,0);
315 JScrollPane pane = buildMyElementsTable();
316 adjustmentSynchronizer.adapt(cbLockMyScrolling, pane.getVerticalScrollBar());
317 add(pane, gc);
318
319 gc.gridx = 1;
320 gc.gridy = 2;
321 gc.fill = GridBagConstraints.NONE;
322 gc.anchor = GridBagConstraints.CENTER;
323 gc.weightx = 0.0;
324 gc.weighty = 0.0;
325 add(buildLeftButtonPanel(), gc);
326
327 gc.gridx = 2;
328 gc.gridy = 2;
329 gc.fill = GridBagConstraints.BOTH;
330 gc.anchor = GridBagConstraints.FIRST_LINE_START;
331 gc.weightx = 0.33;
332 gc.weighty = 0.0;
333 pane = buildMergedElementsTable();
334 adjustmentSynchronizer.adapt(cbLockMergedScrolling, pane.getVerticalScrollBar());
335 add(pane, gc);
336
337 gc.gridx = 3;
338 gc.gridy = 2;
339 gc.fill = GridBagConstraints.NONE;
340 gc.anchor = GridBagConstraints.CENTER;
341 gc.weightx = 0.0;
342 gc.weighty = 0.0;
343 add(buildRightButtonPanel(), gc);
344
345 gc.gridx = 4;
346 gc.gridy = 2;
347 gc.fill = GridBagConstraints.BOTH;
348 gc.anchor = GridBagConstraints.FIRST_LINE_START;
349 gc.weightx = 0.33;
350 gc.weighty = 0.0;
351 pane = buildTheirElementsTable();
352 adjustmentSynchronizer.adapt(cbLockTheirScrolling, pane.getVerticalScrollBar());
353 add(pane, gc);
354
355 // ----------------------------------
356 gc.gridx = 2;
357 gc.gridy = 3;
358 gc.gridwidth = 1;
359 gc.gridheight = 1;
360 gc.fill = GridBagConstraints.BOTH;
361 gc.anchor = GridBagConstraints.CENTER;
362 gc.weightx = 0.0;
363 gc.weighty = 0.0;
364 add(buildMergedListControlButtons(), gc);
365
366 // -----------------------------------
367 gc.gridx = 0;
368 gc.gridy = 4;
369 gc.gridwidth = 2;
370 gc.gridheight = 1;
371 gc.fill = GridBagConstraints.HORIZONTAL;
372 gc.anchor = GridBagConstraints.LINE_START;
373 gc.weightx = 0.0;
374 gc.weighty = 0.0;
375 add(buildComparePairSelectionPanel(), gc);
376
377 gc.gridx = 2;
378 gc.gridy = 4;
379 gc.gridwidth = 3;
380 gc.gridheight = 1;
381 gc.fill = GridBagConstraints.HORIZONTAL;
382 gc.anchor = GridBagConstraints.LINE_START;
383 gc.weightx = 0.0;
384 gc.weighty = 0.0;
385 add(buildFrozeStateControlPanel(), gc);
386
387
388 wireActionsToSelectionModels();
389 }
390
391 public ListMerger(ListMergeModel<T> model) {
392 this.model = model;
393 model.addObserver(this);
394 build();
395 model.addPropertyChangeListener(this);
396 }
397
398 /**
399 * Action for copying selected nodes in the list of my nodes to the list of merged
400 * nodes. Inserts the nodes at the beginning of the list of merged nodes.
401 *
402 */
403 class CopyStartLeftAction extends AbstractAction implements ListSelectionListener {
404
405 public CopyStartLeftAction() {
406 ImageIcon icon = ImageProvider.get("dialogs/conflict", "copystartleft.png");
407 putValue(Action.SMALL_ICON, icon);
408 if (icon == null) {
409 putValue(Action.NAME, tr("> top"));
410 }
411 putValue(Action.SHORT_DESCRIPTION, tr("Copy my selected nodes to the start of the merged node list"));
412 setEnabled(false);
413 }
414
415 public void actionPerformed(ActionEvent arg0) {
416 int [] rows = myEntriesTable.getSelectedRows();
417 model.copyMyToTop(rows);
418 }
419
420 public void valueChanged(ListSelectionEvent e) {
421 setEnabled(!myEntriesTable.getSelectionModel().isSelectionEmpty());
422 }
423 }
424
425 /**
426 * Action for copying selected nodes in the list of my nodes to the list of merged
427 * nodes. Inserts the nodes at the end of the list of merged nodes.
428 *
429 */
430 class CopyEndLeftAction extends AbstractAction implements ListSelectionListener {
431
432 public CopyEndLeftAction() {
433 ImageIcon icon = ImageProvider.get("dialogs/conflict", "copyendleft.png");
434 putValue(Action.SMALL_ICON, icon);
435 if (icon == null) {
436 putValue(Action.NAME, tr("> bottom"));
437 }
438 putValue(Action.SHORT_DESCRIPTION, tr("Copy my selected elements to the end of the list of merged elements"));
439 setEnabled(false);
440 }
441
442 public void actionPerformed(ActionEvent arg0) {
443 int [] rows = myEntriesTable.getSelectedRows();
444 model.copyMyToEnd(rows);
445 }
446
447 public void valueChanged(ListSelectionEvent e) {
448 setEnabled(!myEntriesTable.getSelectionModel().isSelectionEmpty());
449 }
450 }
451
452 /**
453 * Action for copying selected nodes in the list of my nodes to the list of merged
454 * nodes. Inserts the nodes before the first selected row in the list of merged nodes.
455 *
456 */
457 class CopyBeforeCurrentLeftAction extends AbstractAction implements ListSelectionListener {
458
459 public CopyBeforeCurrentLeftAction() {
460 ImageIcon icon = ImageProvider.get("dialogs/conflict", "copybeforecurrentleft.png");
461 putValue(Action.SMALL_ICON, icon);
462 if (icon == null) {
463 putValue(Action.NAME, "> before");
464 }
465 putValue(Action.SHORT_DESCRIPTION, tr("Copy my selected elements before the first selected element in the list of merged elements"));
466 setEnabled(false);
467 }
468
469 public void actionPerformed(ActionEvent arg0) {
470 int [] myRows = myEntriesTable.getSelectedRows();
471 int [] mergedRows = mergedEntriesTable.getSelectedRows();
472 if (mergedRows == null || mergedRows.length == 0)
473 return;
474 int current = mergedRows[0];
475 model.copyMyBeforeCurrent(myRows, current);
476 }
477
478 public void valueChanged(ListSelectionEvent e) {
479 setEnabled(
480 !myEntriesTable.getSelectionModel().isSelectionEmpty()
481 && ! mergedEntriesTable.getSelectionModel().isSelectionEmpty()
482 );
483 }
484 }
485
486 /**
487 * Action for copying selected nodes in the list of my nodes to the list of merged
488 * nodes. Inserts the nodes after the first selected row in the list of merged nodes.
489 *
490 */
491 class CopyAfterCurrentLeftAction extends AbstractAction implements ListSelectionListener {
492
493 public CopyAfterCurrentLeftAction() {
494 ImageIcon icon = ImageProvider.get("dialogs/conflict", "copyaftercurrentleft.png");
495 putValue(Action.SMALL_ICON, icon);
496 if (icon == null) {
497 putValue(Action.NAME, "> after");
498 }
499 putValue(Action.SHORT_DESCRIPTION, tr("Copy my selected elements after the first selected element in the list of merged elements"));
500 setEnabled(false);
501 }
502
503 public void actionPerformed(ActionEvent arg0) {
504 int [] myRows = myEntriesTable.getSelectedRows();
505 int [] mergedRows = mergedEntriesTable.getSelectedRows();
506 if (mergedRows == null || mergedRows.length == 0)
507 return;
508 int current = mergedRows[0];
509 model.copyMyAfterCurrent(myRows, current);
510 }
511
512 public void valueChanged(ListSelectionEvent e) {
513 setEnabled(
514 !myEntriesTable.getSelectionModel().isSelectionEmpty()
515 && ! mergedEntriesTable.getSelectionModel().isSelectionEmpty()
516 );
517 }
518 }
519
520
521 class CopyStartRightAction extends AbstractAction implements ListSelectionListener {
522
523 public CopyStartRightAction() {
524 ImageIcon icon = ImageProvider.get("dialogs/conflict", "copystartright.png");
525 putValue(Action.SMALL_ICON, icon);
526 if (icon == null) {
527 putValue(Action.NAME, "< top");
528 }
529 putValue(Action.SHORT_DESCRIPTION, tr("Copy their selected element to the start of the list of merged elements"));
530 setEnabled(false);
531 }
532
533 public void actionPerformed(ActionEvent arg0) {
534 int [] rows = theirEntriesTable.getSelectedRows();
535 model.copyTheirToTop(rows);
536 }
537
538 public void valueChanged(ListSelectionEvent e) {
539 setEnabled(!theirEntriesTable.getSelectionModel().isSelectionEmpty());
540 }
541 }
542
543
544 class CopyEndRightAction extends AbstractAction implements ListSelectionListener {
545
546 public CopyEndRightAction() {
547 ImageIcon icon = ImageProvider.get("dialogs/conflict", "copyendright.png");
548 putValue(Action.SMALL_ICON, icon);
549 if (icon == null) {
550 putValue(Action.NAME, "< bottom");
551 }
552 putValue(Action.SHORT_DESCRIPTION, tr("Copy their selected elements to the end of the list of merged elements"));
553 setEnabled(false);
554 }
555
556 public void actionPerformed(ActionEvent arg0) {
557 int [] rows = theirEntriesTable.getSelectedRows();
558 model.copyTheirToEnd(rows);
559 }
560
561 public void valueChanged(ListSelectionEvent e) {
562 setEnabled(!theirEntriesTable.getSelectionModel().isSelectionEmpty());
563 }
564 }
565
566 class CopyBeforeCurrentRightAction extends AbstractAction implements ListSelectionListener {
567
568 public CopyBeforeCurrentRightAction() {
569 ImageIcon icon = ImageProvider.get("dialogs/conflict", "copybeforecurrentright.png");
570 putValue(Action.SMALL_ICON, icon);
571 if (icon == null) {
572 putValue(Action.NAME, "< before");
573 }
574 putValue(Action.SHORT_DESCRIPTION, tr("Copy their selected elements before the first selected element in the list of merged elements"));
575 setEnabled(false);
576 }
577
578 public void actionPerformed(ActionEvent arg0) {
579 int [] myRows = theirEntriesTable.getSelectedRows();
580 int [] mergedRows = mergedEntriesTable.getSelectedRows();
581 if (mergedRows == null || mergedRows.length == 0)
582 return;
583 int current = mergedRows[0];
584 model.copyTheirBeforeCurrent(myRows, current);
585 }
586
587 public void valueChanged(ListSelectionEvent e) {
588 setEnabled(
589 !theirEntriesTable.getSelectionModel().isSelectionEmpty()
590 && ! mergedEntriesTable.getSelectionModel().isSelectionEmpty()
591 );
592 }
593 }
594
595
596 class CopyAfterCurrentRightAction extends AbstractAction implements ListSelectionListener {
597
598 public CopyAfterCurrentRightAction() {
599 ImageIcon icon = ImageProvider.get("dialogs/conflict", "copyaftercurrentright.png");
600 putValue(Action.SMALL_ICON, icon);
601 if (icon == null) {
602 putValue(Action.NAME, "< after");
603 }
604 putValue(Action.SHORT_DESCRIPTION, tr("Copy their selected element after the first selected element in the list of merged elements"));
605 setEnabled(false);
606 }
607
608 public void actionPerformed(ActionEvent arg0) {
609 int [] myRows = theirEntriesTable.getSelectedRows();
610 int [] mergedRows = mergedEntriesTable.getSelectedRows();
611 if (mergedRows == null || mergedRows.length == 0)
612 return;
613 int current = mergedRows[0];
614 model.copyTheirAfterCurrent(myRows, current);
615 }
616
617 public void valueChanged(ListSelectionEvent e) {
618 setEnabled(
619 !theirEntriesTable.getSelectionModel().isSelectionEmpty()
620 && ! mergedEntriesTable.getSelectionModel().isSelectionEmpty()
621 );
622 }
623 }
624
625
626 class MoveUpMergedAction extends AbstractAction implements ListSelectionListener {
627
628 public MoveUpMergedAction() {
629 ImageIcon icon = ImageProvider.get("dialogs/conflict", "moveup.png");
630 putValue(Action.SMALL_ICON, icon);
631 if (icon == null) {
632 putValue(Action.NAME, tr("Up"));
633 }
634 putValue(Action.SHORT_DESCRIPTION, tr("Move up the selected elements by one position"));
635 setEnabled(false);
636 }
637
638 public void actionPerformed(ActionEvent arg0) {
639 int [] rows = mergedEntriesTable.getSelectedRows();
640 model.moveUpMerged(rows);
641 }
642
643 public void valueChanged(ListSelectionEvent e) {
644 int [] rows = mergedEntriesTable.getSelectedRows();
645 setEnabled(
646 rows != null
647 && rows.length > 0
648 && rows[0] != 0
649 );
650 }
651 }
652
653 /**
654 * Action for moving the currently selected entries in the list of merged entries
655 * one position down
656 *
657 */
658 class MoveDownMergedAction extends AbstractAction implements ListSelectionListener {
659
660 public MoveDownMergedAction() {
661 ImageIcon icon = ImageProvider.get("dialogs/conflict", "movedown.png");
662 putValue(Action.SMALL_ICON, icon);
663 if (icon == null) {
664 putValue(Action.NAME, tr("Down"));
665 }
666 putValue(Action.SHORT_DESCRIPTION, tr("Move down the selected entries by one position"));
667 setEnabled(false);
668 }
669
670 public void actionPerformed(ActionEvent arg0) {
671 int [] rows = mergedEntriesTable.getSelectedRows();
672 model.moveDownMerged(rows);
673 }
674
675 public void valueChanged(ListSelectionEvent e) {
676 int [] rows = mergedEntriesTable.getSelectedRows();
677 setEnabled(
678 rows != null
679 && rows.length > 0
680 && rows[rows.length -1] != mergedEntriesTable.getRowCount() -1
681 );
682 }
683 }
684
685 /**
686 * Action for removing the selected entries in the list of merged entries
687 * from the list of merged entries.
688 *
689 */
690 class RemoveMergedAction extends AbstractAction implements ListSelectionListener {
691
692 public RemoveMergedAction() {
693 ImageIcon icon = ImageProvider.get("dialogs/conflict", "remove.png");
694 putValue(Action.SMALL_ICON, icon);
695 if (icon == null) {
696 putValue(Action.NAME, tr("Remove"));
697 }
698 putValue(Action.SHORT_DESCRIPTION, tr("Remove the selected entries from the list of merged elements"));
699 setEnabled(false);
700 }
701
702 public void actionPerformed(ActionEvent arg0) {
703 int [] rows = mergedEntriesTable.getSelectedRows();
704 model.removeMerged(rows);
705 }
706
707 public void valueChanged(ListSelectionEvent e) {
708 int [] rows = mergedEntriesTable.getSelectedRows();
709 setEnabled(
710 rows != null
711 && rows.length > 0
712 );
713 }
714 }
715
716 static public interface FreezeActionProperties {
717 String PROP_SELECTED = FreezeActionProperties.class.getName() + ".selected";
718 }
719
720 /**
721 * Action for freezing the current state of the list merger
722 *
723 */
724 class FreezeAction extends AbstractAction implements ItemListener, FreezeActionProperties {
725
726 public FreezeAction() {
727 putValue(Action.NAME, tr("Freeze"));
728 putValue(Action.SHORT_DESCRIPTION, tr("Freeze the current list of merged elements"));
729 putValue(PROP_SELECTED, false);
730 setEnabled(true);
731 }
732
733 public void actionPerformed(ActionEvent arg0) {
734 // do nothing
735 }
736
737 /**
738 * Java 1.5 doesn't known Action.SELECT_KEY. Wires a toggle button to this action
739 * such that the action gets notified about item state changes and the button gets
740 * notified about selection state changes of the action.
741 *
742 * @param btn a toggle button
743 */
744 public void adapt(final JToggleButton btn) {
745 btn.addItemListener(this);
746 addPropertyChangeListener(
747 new PropertyChangeListener() {
748 public void propertyChange(PropertyChangeEvent evt) {
749 if (evt.getPropertyName().equals(PROP_SELECTED)) {
750 btn.setSelected((Boolean)evt.getNewValue());
751 }
752 }
753 }
754 );
755 }
756
757 public void itemStateChanged(ItemEvent e) {
758 int state = e.getStateChange();
759 if (state == ItemEvent.SELECTED) {
760 putValue(Action.NAME, tr("Unfreeze"));
761 putValue(Action.SHORT_DESCRIPTION, tr("Unfreeze the list of merged elements and start merging"));
762 model.setFrozen(true);
763 } else if (state == ItemEvent.DESELECTED) {
764 putValue(Action.NAME, tr("Freeze"));
765 putValue(Action.SHORT_DESCRIPTION, tr("Freeze the current list of merged elements"));
766 model.setFrozen(false);
767 }
768 boolean isSelected = (Boolean)getValue(PROP_SELECTED);
769 if (isSelected != (e.getStateChange() == ItemEvent.SELECTED)) {
770 putValue(PROP_SELECTED, e.getStateChange() == ItemEvent.SELECTED);
771 }
772
773 }
774 }
775
776 protected void handlePropertyChangeFrozen(boolean oldValue, boolean newValue) {
777 myEntriesTable.getSelectionModel().clearSelection();
778 myEntriesTable.setEnabled(!newValue);
779 theirEntriesTable.getSelectionModel().clearSelection();
780 theirEntriesTable.setEnabled(!newValue);
781 mergedEntriesTable.getSelectionModel().clearSelection();
782 mergedEntriesTable.setEnabled(!newValue);
783 if (freezeAction != null) {
784 freezeAction.putValue(FreezeActionProperties.PROP_SELECTED, newValue);
785 }
786 if (newValue) {
787 lblFrozenState.setText(
788 tr("<html>Click <strong>{0}</strong> to start merging my and their entries</html>",
789 freezeAction.getValue(Action.NAME))
790 );
791 } else {
792 lblFrozenState.setText(
793 tr("<html>Click <strong>{0}</strong> to finish merging my and their entries</html>",
794 freezeAction.getValue(Action.NAME))
795 );
796 }
797 }
798
799 public void propertyChange(PropertyChangeEvent evt) {
800 if (evt.getPropertyName().equals(ListMergeModel.FROZEN_PROP)) {
801 handlePropertyChangeFrozen((Boolean)evt.getOldValue(), (Boolean)evt.getNewValue());
802 }
803 }
804
805 public ListMergeModel<T> getModel() {
806 return model;
807 }
808
809
810 public void update(Observable o, Object arg) {
811 lblMyVersion.setText(
812 trn("My version ({0} entry)", "My version ({0} entries)", model.getMyEntriesSize(), model.getMyEntriesSize())
813 );
814 lblMergedVersion.setText(
815 trn("Merged version ({0} entry)", "Merged version ({0} entries)", model.getMergedEntriesSize(), model.getMergedEntriesSize())
816 );
817 lblTheirVersion.setText(
818 trn("Their version ({0} entry)", "Their version ({0} entries)", model.getTheirEntriesSize(), model.getTheirEntriesSize())
819 );
820 }
821
822 /**
823 * Synchronizes scrollbar adjustments between a set of
824 * {@see Adjustable}s. Whenever the adjustment of one of
825 * the registerd Adjustables is updated the adjustment of
826 * the other registered Adjustables is adjusted too.
827 *
828 */
829 class AdjustmentSynchronizer implements AdjustmentListener {
830
831 private final ArrayList<Adjustable> synchronizedAdjustables;
832 private final HashMap<Adjustable, Boolean> enabledMap;
833
834 private final Observable observable;
835
836 public AdjustmentSynchronizer() {
837 synchronizedAdjustables = new ArrayList<Adjustable>();
838 enabledMap = new HashMap<Adjustable, Boolean>();
839 observable = new Observable();
840 }
841
842
843 /**
844 * registers an {@see Adjustable} for participation in synchronized
845 * scrolling.
846 *
847 * @param adjustable the adjustable
848 */
849 public void participateInSynchronizedScrolling(Adjustable adjustable) {
850 if (adjustable == null)
851 return;
852 if (synchronizedAdjustables.contains(adjustable))
853 return;
854 synchronizedAdjustables.add(adjustable);
855 setParticipatingInSynchronizedScrolling(adjustable, true);
856 adjustable.addAdjustmentListener(this);
857 }
858
859 /**
860 * event handler for {@see AdjustmentEvent}s
861 *
862 */
863 public void adjustmentValueChanged(AdjustmentEvent e) {
864 if (! enabledMap.get(e.getAdjustable()))
865 return;
866 for (Adjustable a : synchronizedAdjustables) {
867 if (a != e.getAdjustable() && isParticipatingInSynchronizedScrolling(a)) {
868 a.setValue(e.getValue());
869 }
870 }
871 }
872
873 /**
874 * sets whether adjustable participates in adjustment synchronization
875 * or not
876 *
877 * @param adjustable the adjustable
878 */
879 protected void setParticipatingInSynchronizedScrolling(Adjustable adjustable, boolean isParticipating) {
880 if (adjustable == null)
881 throw new IllegalArgumentException(tr("parameter '{0}' must not be null", "adjustable"));
882
883 if (! synchronizedAdjustables.contains(adjustable))
884 throw new IllegalStateException(tr("adjustable {0} not registered yet. Can't set participation in synchronized adjustment",adjustable));
885
886 enabledMap.put(adjustable, isParticipating);
887 observable.notifyObservers();
888 }
889
890 /**
891 * returns true if an adjustable is participating in synchronized scrolling
892 *
893 * @param adjustable the adjustable
894 * @return true, if the adjustable is participating in synchronized scrolling, false otherwise
895 * @throws IllegalStateException thrown, if adjustable is not registered for synchronized scrolling
896 */
897 protected boolean isParticipatingInSynchronizedScrolling(Adjustable adjustable) throws IllegalStateException {
898 if (! synchronizedAdjustables.contains(adjustable))
899 throw new IllegalStateException(tr("adjustable {0} not registered yet",adjustable));
900
901 return enabledMap.get(adjustable);
902 }
903
904 /**
905 * wires a {@see JCheckBox} to the adjustment synchronizer, in such a way that:
906 * <li>
907 * <ol>state changes in the checkbox control whether the adjustable participates
908 * in synchronized adjustment</ol>
909 * <ol>state changes in this {@see AdjustmentSynchronizer} are reflected in the
910 * {@see JCheckBox}</ol>
911 * </li>
912 *
913 *
914 * @param view the checkbox to control whether an adjustable participates in synchronized
915 * adjustment
916 * @param adjustable the adjustable
917 * @exception IllegalArgumentException thrown, if view is null
918 * @exception IllegalArgumentException thrown, if adjustable is null
919 */
920 protected void adapt(final JCheckBox view, final Adjustable adjustable) throws IllegalArgumentException, IllegalStateException {
921 if (adjustable == null)
922 throw new IllegalArgumentException(tr("parameter '{0}' must not be null", "adjustable"));
923 if (view == null)
924 throw new IllegalArgumentException(tr("parameter '{0}' must not be null", "view"));
925
926 if (! synchronizedAdjustables.contains(adjustable)) {
927 participateInSynchronizedScrolling(adjustable);
928 }
929
930 // register an item lister with the check box
931 //
932 view.addItemListener(new ItemListener() {
933 public void itemStateChanged(ItemEvent e) {
934 switch(e.getStateChange()) {
935 case ItemEvent.SELECTED:
936 if (!isParticipatingInSynchronizedScrolling(adjustable)) {
937 setParticipatingInSynchronizedScrolling(adjustable, true);
938 }
939 break;
940 case ItemEvent.DESELECTED:
941 if (isParticipatingInSynchronizedScrolling(adjustable)) {
942 setParticipatingInSynchronizedScrolling(adjustable, false);
943 }
944 break;
945 }
946 }
947 });
948
949
950 observable.addObserver(
951 new Observer() {
952 public void update(Observable o, Object arg) {
953 boolean sync = isParticipatingInSynchronizedScrolling(adjustable);
954 if (view.isSelected() != sync) {
955 view.setSelected(sync);
956 }
957 }
958 }
959 );
960 setParticipatingInSynchronizedScrolling(adjustable, true);
961 view.setSelected(true);
962 }
963 }
964}
Note: See TracBrowser for help on using the repository browser.