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

Last change on this file since 5299 was 5298, checked in by Don-vip, 12 years ago

see #4899, see #7266, see #7333: Resolved NPE in conflict manager when copying a member created by merging two layers

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