source: osm/applications/editors/josm/plugins/merge-overlap/src/mergeoverlap/MyCombinePrimitiveResolverDialog.java@ 27852

Last change on this file since 27852 was 26705, checked in by sbrunner, 14 years ago

fix tr, remove debug logs

File size: 56.3 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package mergeoverlap;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6import static org.openstreetmap.josm.tools.I18n.trc;
7
8import java.awt.BorderLayout;
9import java.awt.Component;
10import java.awt.Dimension;
11import java.awt.FlowLayout;
12import java.awt.GridBagConstraints;
13import java.awt.GridBagLayout;
14import java.awt.Insets;
15import java.awt.event.ActionEvent;
16import java.awt.event.FocusAdapter;
17import java.awt.event.FocusEvent;
18import java.awt.event.HierarchyBoundsListener;
19import java.awt.event.HierarchyEvent;
20import java.awt.event.KeyEvent;
21import java.awt.event.WindowAdapter;
22import java.awt.event.WindowEvent;
23import java.beans.PropertyChangeEvent;
24import java.beans.PropertyChangeListener;
25import java.beans.PropertyChangeSupport;
26import java.util.ArrayList;
27import java.util.Collection;
28import java.util.Collections;
29import java.util.Comparator;
30import java.util.HashMap;
31import java.util.HashSet;
32import java.util.LinkedList;
33import java.util.List;
34import java.util.Map;
35import java.util.Set;
36
37import javax.swing.AbstractAction;
38import javax.swing.AbstractButton;
39import javax.swing.Action;
40import javax.swing.BorderFactory;
41import javax.swing.BoxLayout;
42import javax.swing.ButtonModel;
43import javax.swing.JButton;
44import javax.swing.JCheckBox;
45import javax.swing.JComboBox;
46import javax.swing.JComponent;
47import javax.swing.JDialog;
48import javax.swing.JLabel;
49import javax.swing.JOptionPane;
50import javax.swing.JPanel;
51import javax.swing.JScrollPane;
52import javax.swing.JSplitPane;
53import javax.swing.JTable;
54import javax.swing.KeyStroke;
55import javax.swing.ListSelectionModel;
56import javax.swing.UIManager;
57import javax.swing.event.ChangeEvent;
58import javax.swing.event.ChangeListener;
59import javax.swing.table.DefaultTableModel;
60
61import org.openstreetmap.josm.Main;
62import org.openstreetmap.josm.command.ChangePropertyCommand;
63import org.openstreetmap.josm.command.Command;
64import org.openstreetmap.josm.data.osm.Node;
65import org.openstreetmap.josm.data.osm.OsmPrimitive;
66import org.openstreetmap.josm.data.osm.Relation;
67import org.openstreetmap.josm.data.osm.RelationMember;
68import org.openstreetmap.josm.data.osm.RelationToChildReference;
69import org.openstreetmap.josm.data.osm.TagCollection;
70import org.openstreetmap.josm.data.osm.Way;
71import org.openstreetmap.josm.gui.DefaultNameFormatter;
72import org.openstreetmap.josm.gui.JMultilineLabel;
73import org.openstreetmap.josm.gui.SideButton;
74import org.openstreetmap.josm.gui.conflict.tags.MultiValueCellEditor;
75import org.openstreetmap.josm.gui.conflict.tags.MultiValueDecisionType;
76import org.openstreetmap.josm.gui.conflict.tags.MultiValueResolutionDecision;
77import org.openstreetmap.josm.gui.conflict.tags.RelationMemberConflictDecision;
78import org.openstreetmap.josm.gui.conflict.tags.RelationMemberConflictDecisionType;
79import org.openstreetmap.josm.gui.conflict.tags.RelationMemberConflictResolverColumnModel;
80import org.openstreetmap.josm.gui.conflict.tags.TagConflictResolverColumnModel;
81import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
82import org.openstreetmap.josm.gui.help.HelpUtil;
83import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
84import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
85import org.openstreetmap.josm.tools.CheckParameterUtil;
86import org.openstreetmap.josm.tools.ImageProvider;
87import org.openstreetmap.josm.tools.WindowGeometry;
88
89/**
90 * This dialog helps to resolve conflicts occurring when ways are combined or
91 * nodes are merged.
92 *
93 * There is a singleton instance of this dialog which can be retrieved using
94 * {@see #getInstance()}.
95 *
96 * The dialog uses two models: one for resolving tag conflicts, the other
97 * for resolving conflicts in relation memberships. For both models there are accessors,
98 * i.e {@see #getTagConflictResolverModel()} and {@see #getRelationMemberConflictResolverModel()}.
99 *
100 * Models have to be <strong>populated</strong> before the dialog is launched. Example:
101 * <pre>
102 * CombinePrimitiveResolverDialog dialog = CombinePrimitiveResolverDialog.getInstance();
103 * dialog.getTagConflictResolverModel().populate(aTagCollection);
104 * dialog.getRelationMemberConflictResolverModel().populate(aRelationLinkCollection);
105 * dialog.prepareDefaultDecisions();
106 * </pre>
107 *
108 * You should also set the target primitive which other primitives (ways or nodes) are
109 * merged to, see {@see #setTargetPrimitive(OsmPrimitive)}.
110 *
111 * After the dialog is closed use {@see #isCancelled()} to check whether the user canceled
112 * the dialog. If it wasn't canceled you may build a collection of {@see Command} objects
113 * which reflect the conflict resolution decisions the user made in the dialog:
114 * see {@see #buildResolutionCommands()}
115 *
116 *
117 */
118public class MyCombinePrimitiveResolverDialog extends JDialog {
119
120 /** the unique instance of the dialog */
121 static private MyCombinePrimitiveResolverDialog instance;
122
123 /**
124 * Replies the unique instance of the dialog
125 *
126 * @return the unique instance of the dialog
127 */
128 public static MyCombinePrimitiveResolverDialog getInstance() {
129 if (instance == null) {
130 instance = new MyCombinePrimitiveResolverDialog(Main.parent);
131 }
132 return instance;
133 }
134
135 private AutoAdjustingSplitPane spTagConflictTypes;
136 private MyTagConflictResolver pnlTagConflictResolver;
137 private MyRelationMemberConflictResolver pnlRelationMemberConflictResolver;
138 private boolean cancelled;
139 private JPanel pnlButtons;
140 private OsmPrimitive targetPrimitive;
141
142 /** the private help action */
143 private ContextSensitiveHelpAction helpAction;
144 /** the apply button */
145 private SideButton btnApply;
146
147 /**
148 * Replies the target primitive the collection of primitives is merged
149 * or combined to.
150 *
151 * @return the target primitive
152 */
153 public OsmPrimitive getTargetPrimitmive() {
154 return targetPrimitive;
155 }
156
157 /**
158 * Sets the primitive the collection of primitives is merged or combined
159 * to.
160 *
161 * @param primitive the target primitive
162 */
163 public void setTargetPrimitive(OsmPrimitive primitive) {
164 this.targetPrimitive = primitive;
165 updateTitle();
166 if (primitive instanceof Way) {
167 pnlRelationMemberConflictResolver.initForWayCombining();
168 } else if (primitive instanceof Node) {
169 pnlRelationMemberConflictResolver.initForNodeMerging();
170 }
171 }
172
173 protected void updateTitle() {
174 if (targetPrimitive == null) {
175 setTitle(tr("Conflicts when combining primitives", null));
176 return;
177 }
178 if (targetPrimitive instanceof Way) {
179 setTitle(tr("Conflicts when combining ways - combined way is ''{0}''", targetPrimitive
180 .getDisplayName(DefaultNameFormatter.getInstance())));
181 helpAction.setHelpTopic(ht("/Action/CombineWay#ResolvingConflicts"));
182 getRootPane().putClientProperty("help", ht("/Action/CombineWay#ResolvingConflicts"));
183 } else if (targetPrimitive instanceof Node) {
184 setTitle(tr("Conflicts when merging nodes - target node is ''{0}''", targetPrimitive
185 .getDisplayName(DefaultNameFormatter.getInstance())));
186 helpAction.setHelpTopic(ht("/Action/MergeNodes#ResolvingConflicts"));
187 getRootPane().putClientProperty("help", ht("/Action/MergeNodes#ResolvingConflicts"));
188 }
189 }
190
191 protected void build() {
192 getContentPane().setLayout(new BorderLayout());
193 updateTitle();
194 spTagConflictTypes = new AutoAdjustingSplitPane(JSplitPane.VERTICAL_SPLIT);
195 spTagConflictTypes.setTopComponent(buildTagConflictResolverPanel());
196 spTagConflictTypes.setBottomComponent(buildRelationMemberConflictResolverPanel());
197 getContentPane().add(pnlButtons = buildButtonPanel(), BorderLayout.SOUTH);
198 addWindowListener(new AdjustDividerLocationAction());
199 HelpUtil.setHelpContext(getRootPane(), ht("/"));
200 }
201
202 protected JPanel buildTagConflictResolverPanel() {
203 pnlTagConflictResolver = new MyTagConflictResolver();
204 return pnlTagConflictResolver;
205 }
206
207 protected JPanel buildRelationMemberConflictResolverPanel() {
208 pnlRelationMemberConflictResolver = new MyRelationMemberConflictResolver();
209 return pnlRelationMemberConflictResolver;
210 }
211
212 protected JPanel buildButtonPanel() {
213 JPanel pnl = new JPanel();
214 pnl.setLayout(new FlowLayout(FlowLayout.CENTER));
215
216 // -- apply button
217 ApplyAction applyAction = new ApplyAction();
218 pnlTagConflictResolver.getModel().addPropertyChangeListener(applyAction);
219 pnlRelationMemberConflictResolver.getModel().addPropertyChangeListener(applyAction);
220 btnApply = new SideButton(applyAction);
221 btnApply.setFocusable(true);
222 pnl.add(btnApply);
223
224 // -- cancel button
225 CancelAction cancelAction = new CancelAction();
226 pnl.add(new SideButton(cancelAction));
227
228 // -- help button
229 helpAction = new ContextSensitiveHelpAction();
230 pnl.add(new SideButton(helpAction));
231
232 return pnl;
233 }
234
235 public MyCombinePrimitiveResolverDialog(Component owner) {
236 super(JOptionPane.getFrameForComponent(owner), ModalityType.DOCUMENT_MODAL);
237 build();
238 }
239
240 public MyTagConflictResolverModel getTagConflictResolverModel() {
241 return pnlTagConflictResolver.getModel();
242 }
243
244 public MyRelationMemberConflictResolverModel getRelationMemberConflictResolverModel() {
245 return pnlRelationMemberConflictResolver.getModel();
246 }
247
248 protected List<Command> buildTagChangeCommand(OsmPrimitive primitive, TagCollection tc) {
249 LinkedList<Command> cmds = new LinkedList<Command>();
250 for (String key : tc.getKeys()) {
251 if (tc.hasUniqueEmptyValue(key)) {
252 if (primitive.get(key) != null) {
253 cmds.add(new ChangePropertyCommand(primitive, key, null));
254 }
255 } else {
256 String value = tc.getJoinedValues(key);
257 if (!value.equals(primitive.get(key))) {
258 cmds.add(new ChangePropertyCommand(primitive, key, value));
259 }
260 }
261 }
262 return cmds;
263 }
264
265 public List<Command> buildWayResolutionCommands() {
266 List<Command> cmds = new LinkedList<Command>();
267
268 TagCollection allResolutions = getTagConflictResolverModel().getAllResolutions();
269 if (allResolutions.size() > 0) {
270 cmds.addAll(buildTagChangeCommand(targetPrimitive, allResolutions));
271 }
272 if (targetPrimitive.get("created_by") != null) {
273 cmds.add(new ChangePropertyCommand(targetPrimitive, "created_by", null));
274 }
275
276 Command cmd = pnlRelationMemberConflictResolver.buildTagApplyCommands(getRelationMemberConflictResolverModel()
277 .getModifiedRelations(targetPrimitive));
278 if (cmd != null) {
279 cmds.add(cmd);
280 }
281 return cmds;
282 }
283
284 public void buildRelationCorrespondance(Map<Relation, Relation> newRelations, Map<Way, Way> oldWays) {
285 getRelationMemberConflictResolverModel().buildRelationCorrespondance(targetPrimitive, newRelations, oldWays);
286 }
287
288 protected void prepareDefaultTagDecisions() {
289 MyTagConflictResolverModel model = getTagConflictResolverModel();
290 for (int i = 0; i < model.getRowCount(); i++) {
291 MultiValueResolutionDecision decision = model.getDecision(i);
292 List<String> values = decision.getValues();
293 values.remove("");
294 if (values.size() == 1) {
295 decision.keepOne(values.get(0));
296 } else {
297 decision.keepAll();
298 }
299 }
300 model.rebuild();
301 }
302
303 protected void prepareDefaultRelationDecisions() {
304 MyRelationMemberConflictResolverModel model = getRelationMemberConflictResolverModel();
305 Set<Relation> relations = new HashSet<Relation>();
306 for (int i = 0; i < model.getNumDecisions(); i++) {
307 RelationMemberConflictDecision decision = model.getDecision(i);
308 if (!relations.contains(decision.getRelation())) {
309 decision.decide(RelationMemberConflictDecisionType.KEEP);
310 relations.add(decision.getRelation());
311 } else {
312 decision.decide(RelationMemberConflictDecisionType.REMOVE);
313 }
314 }
315 model.refresh();
316 }
317
318 public void prepareDefaultDecisions() {
319 prepareDefaultTagDecisions();
320 prepareDefaultRelationDecisions();
321 }
322
323 protected JPanel buildEmptyConflictsPanel() {
324 JPanel pnl = new JPanel();
325 pnl.setLayout(new BorderLayout());
326 pnl.add(new JLabel(tr("No conflicts to resolve", null)));
327 return pnl;
328 }
329
330 protected void prepareGUIBeforeConflictResolutionStarts() {
331 MyRelationMemberConflictResolverModel relModel = getRelationMemberConflictResolverModel();
332 MyTagConflictResolverModel tagModel = getTagConflictResolverModel();
333 getContentPane().removeAll();
334
335 if (relModel.getNumDecisions() > 0 && tagModel.getNumDecisions() > 0) {
336 // display both, the dialog for resolving relation conflicts and for resolving
337 // tag conflicts
338 spTagConflictTypes.setTopComponent(pnlTagConflictResolver);
339 spTagConflictTypes.setBottomComponent(pnlRelationMemberConflictResolver);
340 getContentPane().add(spTagConflictTypes, BorderLayout.CENTER);
341 } else if (relModel.getNumDecisions() > 0) {
342 // relation conflicts only
343 //
344 getContentPane().add(pnlRelationMemberConflictResolver, BorderLayout.CENTER);
345 } else if (tagModel.getNumDecisions() > 0) {
346 // tag conflicts only
347 //
348 getContentPane().add(pnlTagConflictResolver, BorderLayout.CENTER);
349 } else {
350 getContentPane().add(buildEmptyConflictsPanel(), BorderLayout.CENTER);
351 }
352
353 getContentPane().add(pnlButtons, BorderLayout.SOUTH);
354 validate();
355 int numTagDecisions = getTagConflictResolverModel().getNumDecisions();
356 int numRelationDecisions = getRelationMemberConflictResolverModel().getNumDecisions();
357 if (numTagDecisions > 0 && numRelationDecisions > 0) {
358 spTagConflictTypes.setDividerLocation(0.5);
359 }
360 pnlRelationMemberConflictResolver.prepareForEditing();
361 }
362
363 protected void setCancelled(boolean cancelled) {
364 this.cancelled = cancelled;
365 }
366
367 public boolean isCancelled() {
368 return cancelled;
369 }
370
371 @Override
372 public void setVisible(boolean visible) {
373 if (visible) {
374 prepareGUIBeforeConflictResolutionStarts();
375 new WindowGeometry(getClass().getName() + ".geometry", WindowGeometry.centerInWindow(Main.parent,
376 new Dimension(600, 400))).applySafe(this);
377 setCancelled(false);
378 btnApply.requestFocusInWindow();
379 } else {
380 new WindowGeometry(this).remember(getClass().getName() + ".geometry");
381 }
382 super.setVisible(visible);
383 }
384
385 class CancelAction extends AbstractAction {
386
387 public CancelAction() {
388 putValue(Action.SHORT_DESCRIPTION, tr("Cancel conflict resolution", null));
389 putValue(Action.NAME, tr("Cancel", null));
390 putValue(Action.SMALL_ICON, ImageProvider.get("", "cancel"));
391 setEnabled(true);
392 }
393
394 public void actionPerformed(ActionEvent arg0) {
395 setCancelled(true);
396 setVisible(false);
397 }
398 }
399
400 class ApplyAction extends AbstractAction implements PropertyChangeListener {
401
402 public ApplyAction() {
403 putValue(Action.SHORT_DESCRIPTION, tr("Apply resolved conflicts", null));
404 putValue(Action.NAME, tr("Apply", null));
405 putValue(Action.SMALL_ICON, ImageProvider.get("ok"));
406 updateEnabledState();
407 }
408
409 public void actionPerformed(ActionEvent arg0) {
410 setVisible(false);
411 pnlTagConflictResolver.rememberPreferences();
412 }
413
414 protected void updateEnabledState() {
415 setEnabled(pnlTagConflictResolver.getModel().getNumConflicts() == 0
416 && pnlRelationMemberConflictResolver.getModel().getNumConflicts() == 0);
417 }
418
419 public void propertyChange(PropertyChangeEvent evt) {
420 if (evt.getPropertyName().equals(MyTagConflictResolverModel.NUM_CONFLICTS_PROP)) {
421 updateEnabledState();
422 }
423 if (evt.getPropertyName().equals(MyRelationMemberConflictResolverModel.NUM_CONFLICTS_PROP)) {
424 updateEnabledState();
425 }
426 }
427 }
428
429 class AdjustDividerLocationAction extends WindowAdapter {
430 @Override
431 public void windowOpened(WindowEvent e) {
432 int numTagDecisions = getTagConflictResolverModel().getNumDecisions();
433 int numRelationDecisions = getRelationMemberConflictResolverModel().getNumDecisions();
434 if (numTagDecisions > 0 && numRelationDecisions > 0) {
435 spTagConflictTypes.setDividerLocation(0.5);
436 }
437 }
438 }
439
440 static class AutoAdjustingSplitPane extends JSplitPane implements PropertyChangeListener, HierarchyBoundsListener {
441 private double dividerLocation;
442
443 public AutoAdjustingSplitPane(int newOrientation) {
444 super(newOrientation);
445 addPropertyChangeListener(JSplitPane.DIVIDER_LOCATION_PROPERTY, this);
446 addHierarchyBoundsListener(this);
447 }
448
449 public void ancestorResized(HierarchyEvent e) {
450 setDividerLocation((int) (dividerLocation * getHeight()));
451 }
452
453 public void ancestorMoved(HierarchyEvent e) {
454 // do nothing
455 }
456
457 public void propertyChange(PropertyChangeEvent evt) {
458 if (evt.getPropertyName().equals(JSplitPane.DIVIDER_LOCATION_PROPERTY)) {
459 int newVal = (Integer) evt.getNewValue();
460 if (getHeight() != 0) {
461 dividerLocation = (double) newVal / (double) getHeight();
462 }
463 }
464 }
465 }
466
467 /**
468 * This model manages a list of conflicting relation members.
469 *
470 * It can be used as {@see TableModel}.
471 *
472 *
473 */
474 public static class MyRelationMemberConflictResolverModel extends DefaultTableModel {
475 /** the property name for the number conflicts managed by this model */
476 static public final String NUM_CONFLICTS_PROP = MyRelationMemberConflictResolverModel.class.getName() + ".numConflicts";
477
478 /** the list of conflict decisions */
479 private List<RelationMemberConflictDecision> decisions;
480 /** the collection of relations for which we manage conflicts */
481 private Collection<Relation> relations;
482 /** the number of conflicts */
483 private int numConflicts;
484 private PropertyChangeSupport support;
485
486 /**
487 * Replies the current number of conflicts
488 *
489 * @return the current number of conflicts
490 */
491 public int getNumConflicts() {
492 return numConflicts;
493 }
494
495 /**
496 * Updates the current number of conflicts from list of decisions and emits
497 * a property change event if necessary.
498 *
499 */
500 protected void updateNumConflicts() {
501 int count = 0;
502 for (RelationMemberConflictDecision decision: decisions) {
503 if (!decision.isDecided()) {
504 count++;
505 }
506 }
507 int oldValue = numConflicts;
508 numConflicts = count;
509 if (numConflicts != oldValue) {
510 support.firePropertyChange(NUM_CONFLICTS_PROP, oldValue, numConflicts);
511 }
512 }
513
514 public void addPropertyChangeListener(PropertyChangeListener l) {
515 support.addPropertyChangeListener(l);
516 }
517
518 public void removePropertyChangeListener(PropertyChangeListener l) {
519 support.removePropertyChangeListener(l);
520 }
521
522 public MyRelationMemberConflictResolverModel() {
523 decisions = new ArrayList<RelationMemberConflictDecision>();
524 support = new PropertyChangeSupport(this);
525 }
526
527 @Override
528 public int getRowCount() {
529 if (decisions == null) return 0;
530 return decisions.size();
531 }
532
533 @Override
534 public Object getValueAt(int row, int column) {
535 if (decisions == null) return null;
536
537 RelationMemberConflictDecision d = decisions.get(row);
538 switch(column) {
539 case 0: /* relation */ return d.getRelation();
540 case 1: /* pos */ return Integer.toString(d.getPos() + 1); // position in "user space" starting at 1
541 case 2: /* role */ return d.getRole();
542 case 3: /* original */ return d.getOriginalPrimitive();
543 case 4: /* decision */ return d.getDecision();
544 }
545 return null;
546 }
547
548 @Override
549 public void setValueAt(Object value, int row, int column) {
550 RelationMemberConflictDecision d = decisions.get(row);
551 switch(column) {
552 case 2: /* role */
553 d.setRole((String)value);
554 break;
555 case 4: /* decision */
556 d.decide((RelationMemberConflictDecisionType)value);
557 refresh();
558 break;
559 }
560 fireTableDataChanged();
561 }
562
563 /**
564 * Populates the model with the members of the relation <code>relation</code>
565 * referring to <code>primitive</code>.
566 *
567 * @param relation the parent relation
568 * @param primitive the child primitive
569 */
570 protected void populate(Relation relation, OsmPrimitive primitive, Map<Way, Way> oldWays) {
571 for (int i = 0; i<relation.getMembersCount(); i++) {
572 if (MergeOverlapAction.getOld(relation.getMember(i).getWay(), oldWays) == MergeOverlapAction.getOld((Way)primitive, oldWays)) {
573 decisions.add(new RelationMemberConflictDecision(relation, i));
574 }
575 }
576 }
577
578 /**
579 * Populates the model with the relation members belonging to one of the relations in <code>relations</code>
580 * and referring to one of the primitives in <code>memberPrimitives</code>.
581 *
582 * @param relations the parent relations. Empty list assumed if null.
583 * @param memberPrimitives the child primitives. Empty list assumed if null.
584 */
585 public void populate(Collection<Relation> relations, Collection<? extends OsmPrimitive> memberPrimitives, Map<Way, Way> oldWays) {
586 decisions.clear();
587
588 relations = relations == null ? new LinkedList<Relation>() : relations;
589 memberPrimitives = memberPrimitives == null ? new LinkedList<OsmPrimitive>() : memberPrimitives;
590 for (Relation r : relations) {
591 for (OsmPrimitive p: memberPrimitives) {
592 populate(r, p, oldWays);
593 }
594 }
595 this.relations = relations;
596 refresh();
597 }
598
599 /**
600 * Populates the model with the relation members represented as a collection of
601 * {@see RelationToChildReference}s.
602 *
603 * @param references the references. Empty list assumed if null.
604 */
605 public void populate(Collection<RelationToChildReference> references) {
606 references = references == null ? new LinkedList<RelationToChildReference>() : references;
607 decisions.clear();
608 this.relations = new HashSet<Relation>(references.size());
609 for (RelationToChildReference reference: references) {
610 decisions.add(new RelationMemberConflictDecision(reference.getParent(), reference.getPosition()));
611 relations.add(reference.getParent());
612 }
613 refresh();
614 }
615
616 /**
617 * Replies the decision at position <code>row</code>
618 *
619 * @param row
620 * @return the decision at position <code>row</code>
621 */
622 public RelationMemberConflictDecision getDecision(int row) {
623 return decisions.get(row);
624 }
625
626 /**
627 * Replies the number of decisions managed by this model
628 *
629 * @return the number of decisions managed by this model
630 */
631 public int getNumDecisions() {
632 return getRowCount();
633 }
634
635 /**
636 * Refreshes the model state. Invoke this method to trigger necessary change
637 * events after an update of the model data.
638 *
639 */
640 public void refresh() {
641 updateNumConflicts();
642 fireTableDataChanged();
643 }
644
645 /**
646 * Apply a role to all member managed by this model.
647 *
648 * @param role the role. Empty string assumed if null.
649 */
650 public void applyRole(String role) {
651 role = role == null ? "" : role;
652 for (RelationMemberConflictDecision decision : decisions) {
653 decision.setRole(role);
654 }
655 refresh();
656 }
657
658 protected RelationMemberConflictDecision getDecision(Relation relation, int pos) {
659 for(RelationMemberConflictDecision decision: decisions) {
660 if (decision.matches(relation, pos)) return decision;
661 }
662 return null;
663 }
664
665 protected void buildResolveCorrespondance(Relation relation, OsmPrimitive newPrimitive, Map<Relation, Relation> newRelations, Map<Way, Way> oldWays) {
666
667 List<RelationMember> relationsMembers = relation.getMembers();
668 Relation modifiedRelation = MergeOverlapAction.getNew(relation, newRelations);
669 modifiedRelation.setMembers(null);
670// boolean isChanged = false;
671 for (int i=0; i < relationsMembers.size(); i++) {
672 RelationMember rm = relationsMembers.get(i);
673// RelationMember rm = relation.getMember(i);
674// RelationMember rmNew;
675 RelationMemberConflictDecision decision = getDecision(relation, i);
676 if (decision == null) {
677 modifiedRelation.addMember(rm);
678 } else {
679 System.out.println(modifiedRelation);
680 System.out.println(111);
681 switch(decision.getDecision()) {
682 case KEEP:
683// modifiedRelation.removeMembersFor(newPrimitive);
684 System.out.println(222);
685 if (newPrimitive instanceof Way) {
686 modifiedRelation.addMember(new RelationMember(decision.getRole(), MergeOverlapAction.getOld((Way)newPrimitive, oldWays)));
687 }
688 else {
689 modifiedRelation.addMember(new RelationMember(decision.getRole(), newPrimitive));
690 }
691// modifiedRelation.addMember(new RelationMember(decision.getRole(), newPrimitive));
692 break;
693 case REMOVE:
694 System.out.println(333);
695// modifiedRelation.removeMembersFor(rm.getMember());
696// isChanged = true;
697 // do nothing
698 break;
699 case UNDECIDED:
700 // FIXME: this is an error
701 break;
702 }
703 }
704 }
705 }
706
707 /**
708 * Builds a collection of commands executing the decisions made in this model.
709 *
710 * @param newPrimitive the primitive which members shall refer to if the
711 * decision is {@see RelationMemberConflictDecisionType#REPLACE}
712 * @return a list of commands
713 */
714 public void buildRelationCorrespondance(OsmPrimitive newPrimitive, Map<Relation, Relation> newRelations, Map<Way, Way> oldWays) {
715 for (Relation relation : relations) {
716 buildResolveCorrespondance(relation, newPrimitive, newRelations, oldWays);
717 }
718 }
719
720 protected boolean isChanged(Relation relation, OsmPrimitive newPrimitive) {
721 for (int i=0; i < relation.getMembersCount(); i++) {
722 RelationMemberConflictDecision decision = getDecision(relation, i);
723 if (decision == null) {
724 continue;
725 }
726 switch(decision.getDecision()) {
727 case REMOVE: return true;
728 case KEEP:
729 if (!relation.getMember(i).getRole().equals(decision.getRole()))
730 return true;
731 if (relation.getMember(i).getMember() != newPrimitive)
732 return true;
733 case UNDECIDED:
734 // FIXME: handle error
735 }
736 }
737 return false;
738 }
739
740 /**
741 * Replies the set of relations which have to be modified according
742 * to the decisions managed by this model.
743 *
744 * @param newPrimitive the primitive which members shall refer to if the
745 * decision is {@see RelationMemberConflictDecisionType#REPLACE}
746 *
747 * @return the set of relations which have to be modified according
748 * to the decisions managed by this model
749 */
750 public Set<Relation> getModifiedRelations(OsmPrimitive newPrimitive) {
751 HashSet<Relation> ret = new HashSet<Relation>();
752 for (Relation relation: relations) {
753 if (isChanged(relation, newPrimitive)) {
754 ret.add(relation);
755 }
756 }
757 return ret;
758 }
759 }
760
761 public class MyRelationMemberConflictResolver extends JPanel {
762
763 private AutoCompletingTextField tfRole;
764 private AutoCompletingTextField tfKey;
765 private AutoCompletingTextField tfValue;
766 private JCheckBox cbTagRelations;
767 private MyRelationMemberConflictResolverModel model;
768 private MyRelationMemberConflictResolverTable tblResolver;
769 private JMultilineLabel lblHeader;
770
771 protected void build() {
772 setLayout(new GridBagLayout());
773 JPanel pnl = new JPanel();
774 pnl.setLayout(new BorderLayout());
775 pnl.add(lblHeader = new JMultilineLabel(""));
776 GridBagConstraints gc = new GridBagConstraints();
777 gc.fill = GridBagConstraints.HORIZONTAL;
778 gc.weighty = 0.0;
779 gc.weightx = 1.0;
780 gc.insets = new Insets(5,5,5,5);
781 add(pnl, gc);
782 model = new MyRelationMemberConflictResolverModel();
783
784 gc.gridy = 1;
785 gc.weighty = 1.0;
786 gc.fill = GridBagConstraints.BOTH;
787 gc.insets = new Insets(0,0,0,0);
788 add(new JScrollPane(tblResolver = new MyRelationMemberConflictResolverTable(model)), gc);
789 pnl = new JPanel();
790 pnl.setLayout(new BoxLayout(pnl, BoxLayout.Y_AXIS));
791 pnl.add(buildRoleEditingPanel());
792 pnl.add(buildTagRelationsPanel());
793 gc.gridy = 2;
794 gc.weighty = 0.0;
795 gc.fill = GridBagConstraints.HORIZONTAL;
796 add(pnl,gc);
797 }
798
799 protected JPanel buildRoleEditingPanel() {
800 JPanel pnl = new JPanel();
801 pnl.setLayout(new FlowLayout(FlowLayout.LEFT));
802 pnl.add(new JLabel(tr("Role:", null)));
803 pnl.add(tfRole = new AutoCompletingTextField(10));
804 tfRole.setToolTipText(tr("Enter a role for all relation memberships", null));
805 pnl.add(new JButton(new ApplyRoleAction()));
806 tfRole.addActionListener(new ApplyRoleAction());
807 tfRole.addFocusListener(
808 new FocusAdapter() {
809 @Override
810 public void focusGained(FocusEvent e) {
811 tfRole.selectAll();
812 }
813 }
814 );
815 return pnl;
816 }
817
818 protected JPanel buildTagRelationsPanel() {
819 JPanel pnl = new JPanel();
820 pnl.setLayout(new FlowLayout(FlowLayout.LEFT));
821 cbTagRelations = new JCheckBox(tr("Tag modified relations with ", null));
822 cbTagRelations.addChangeListener(new ToggleTagRelationsAction());
823 cbTagRelations.setToolTipText(
824 tr("<html>Select to enable entering a tag which will be applied<br>"
825 + "to all modified relations.</html>", null));
826 pnl.add(cbTagRelations);
827 pnl.add(new JLabel(trc("tag", "Key:")));
828 pnl.add(tfKey = new AutoCompletingTextField(10));
829 tfKey.setToolTipText(tr("<html>Enter a tag key, i.e. <strong><tt>fixme</tt></strong></html>", null));
830 pnl.add(new JLabel(tr("Value:", null)));
831 pnl.add(tfValue = new AutoCompletingTextField(10));
832 tfValue.setToolTipText(tr("<html>Enter a tag value, i.e. <strong><tt>check members</tt></strong></html>", null));
833 cbTagRelations.setSelected(false);
834 tfKey.setEnabled(false);
835 tfValue.setEnabled(false);
836 return pnl;
837 }
838
839 public MyRelationMemberConflictResolver() {
840 build();
841 }
842
843 public void initForWayCombining() {
844 lblHeader.setText(tr("<html>The combined ways are members in one ore more relations. "
845 + "Please decide whether you want to <strong>keep</strong> these memberships "
846 + "for the combined way or whether you want to <strong>remove</strong> them.<br>"
847 + "The default is to <strong>keep</strong> the first way and <strong>remove</strong> "
848 + "the other ways that are members of the same relation: the combined way will "
849 + "take the place of the original way in the relation."
850 + "</html>", null));
851 invalidate();
852 }
853
854 public void initForNodeMerging() {
855 lblHeader.setText(tr("<html>The merged nodes are members in one ore more relations. "
856 + "Please decide whether you want to <strong>keep</strong> these memberships "
857 + "for the target node or whether you want to <strong>remove</strong> them.<br>"
858 + "The default is to <strong>keep</strong> the first node and <strong>remove</strong> "
859 + "the other nodes that are members of the same relation: the target node will "
860 + "take the place of the original node in the relation."
861 + "</html>", null));
862 invalidate();
863 }
864
865 class ApplyRoleAction extends AbstractAction {
866 public ApplyRoleAction() {
867 putValue(NAME, tr("Apply", null));
868 putValue(SMALL_ICON, ImageProvider.get("ok"));
869 putValue(SHORT_DESCRIPTION, tr("Apply this role to all members", null));
870 }
871
872 public void actionPerformed(ActionEvent e) {
873 model.applyRole(tfRole.getText());
874 }
875 }
876
877 class ToggleTagRelationsAction implements ChangeListener {
878 public void stateChanged(ChangeEvent e) {
879 ButtonModel buttonModel = ((AbstractButton) e.getSource()).getModel();
880 tfKey.setEnabled(buttonModel.isSelected());
881 tfValue.setEnabled(buttonModel.isSelected());
882 tfKey.setBackground(buttonModel.isSelected() ? UIManager.getColor("TextField.background") : UIManager
883 .getColor("Panel.background"));
884 tfValue.setBackground(buttonModel.isSelected() ? UIManager.getColor("TextField.background") : UIManager
885 .getColor("Panel.background"));
886 }
887 }
888
889 public MyRelationMemberConflictResolverModel getModel() {
890 return model;
891 }
892
893 public Command buildTagApplyCommands(Collection<? extends OsmPrimitive> primitives) {
894 if (!cbTagRelations.isSelected())
895 return null;
896 if (tfKey.getText().trim().equals(""))
897 return null;
898 if (tfValue.getText().trim().equals(""))
899 return null;
900 if (primitives == null || primitives.isEmpty())
901 return null;
902 return new ChangePropertyCommand(primitives, tfKey.getText(), tfValue.getText());
903 }
904
905 public void prepareForEditing() {
906 AutoCompletionList acList = new AutoCompletionList();
907 Main.main.getEditLayer().data.getAutoCompletionManager().populateWithMemberRoles(acList);
908 tfRole.setAutoCompletionList(acList);
909 AutoCompletingTextField editor = (AutoCompletingTextField) tblResolver.getColumnModel().getColumn(2).getCellEditor();
910 if (editor != null) {
911 editor.setAutoCompletionList(acList);
912 }
913 AutoCompletionList acList2 = new AutoCompletionList();
914 Main.main.getEditLayer().data.getAutoCompletionManager().populateWithKeys(acList2);
915 tfKey.setAutoCompletionList(acList2);
916 }
917 }
918
919
920 public class MyRelationMemberConflictResolverTable extends JTable implements MultiValueCellEditor.NavigationListener {
921
922 private SelectNextColumnCellAction selectNextColumnCellAction;
923 private SelectPreviousColumnCellAction selectPreviousColumnCellAction;
924
925 public MyRelationMemberConflictResolverTable(MyRelationMemberConflictResolverModel model) {
926 super(model, new RelationMemberConflictResolverColumnModel());
927 build();
928 }
929
930 protected void build() {
931 setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
932 setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
933 putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
934
935 // make ENTER behave like TAB
936 //
937 getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
938 KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false), "selectNextColumnCell");
939
940 // install custom navigation actions
941 //
942 selectNextColumnCellAction = new SelectNextColumnCellAction();
943 selectPreviousColumnCellAction = new SelectPreviousColumnCellAction();
944 getActionMap().put("selectNextColumnCell", selectNextColumnCellAction);
945 getActionMap().put("selectPreviousColumnCell", selectPreviousColumnCellAction);
946
947 setRowHeight((int)new JComboBox().getPreferredSize().getHeight());
948 }
949
950 /**
951 * Action to be run when the user navigates to the next cell in the table, for instance by
952 * pressing TAB or ENTER. The action alters the standard navigation path from cell to cell: <ul>
953 * <li>it jumps over cells in the first column</li> <li>it automatically add a new empty row
954 * when the user leaves the last cell in the table</li> <ul>
955 *
956 *
957 */
958 class SelectNextColumnCellAction extends AbstractAction {
959 public void actionPerformed(ActionEvent e) {
960 run();
961 }
962
963 public void run() {
964 int col = getSelectedColumn();
965 int row = getSelectedRow();
966 if (getCellEditor() != null) {
967 getCellEditor().stopCellEditing();
968 }
969
970 if (col == 2 && row < getRowCount() - 1) {
971 row++;
972 } else if (row < getRowCount() - 1) {
973 col = 2;
974 row++;
975 }
976 changeSelection(row, col, false, false);
977 editCellAt(getSelectedRow(), getSelectedColumn());
978 getEditorComponent().requestFocusInWindow();
979 }
980 }
981
982 /**
983 * Action to be run when the user navigates to the previous cell in the table, for instance by
984 * pressing Shift-TAB
985 *
986 */
987 class SelectPreviousColumnCellAction extends AbstractAction {
988
989 public void actionPerformed(ActionEvent e) {
990 run();
991 }
992
993 public void run() {
994 int col = getSelectedColumn();
995 int row = getSelectedRow();
996 if (getCellEditor() != null) {
997 getCellEditor().stopCellEditing();
998 }
999
1000 if (col <= 0 && row <= 0) {
1001 // change nothing
1002 } else if (row > 0) {
1003 col = 2;
1004 row--;
1005 }
1006 changeSelection(row, col, false, false);
1007 editCellAt(getSelectedRow(), getSelectedColumn());
1008 getEditorComponent().requestFocusInWindow();
1009 }
1010 }
1011
1012 public void gotoNextDecision() {
1013 selectNextColumnCellAction.run();
1014 }
1015
1016 public void gotoPreviousDecision() {
1017 selectPreviousColumnCellAction.run();
1018 }
1019 }
1020
1021
1022 public static class MyTagConflictResolverModel extends DefaultTableModel {
1023 static public final String NUM_CONFLICTS_PROP = MyTagConflictResolverModel.class.getName() + ".numConflicts";
1024
1025 private TagCollection tags;
1026 private List<String> displayedKeys;
1027 private Set<String> keysWithConflicts;
1028 private HashMap<String, MultiValueResolutionDecision> decisions;
1029 private int numConflicts;
1030 private PropertyChangeSupport support;
1031 private boolean showTagsWithConflictsOnly = false;
1032 private boolean showTagsWithMultiValuesOnly = false;
1033
1034 public MyTagConflictResolverModel() {
1035 numConflicts = 0;
1036 support = new PropertyChangeSupport(this);
1037 }
1038
1039 public void addPropertyChangeListener(PropertyChangeListener listener) {
1040 support.addPropertyChangeListener(listener);
1041 }
1042
1043 public void removePropertyChangeListener(PropertyChangeListener listener) {
1044 support.removePropertyChangeListener(listener);
1045 }
1046
1047 protected void setNumConflicts(int numConflicts) {
1048 int oldValue = this.numConflicts;
1049 this.numConflicts = numConflicts;
1050 if (oldValue != this.numConflicts) {
1051 support.firePropertyChange(NUM_CONFLICTS_PROP, oldValue, this.numConflicts);
1052 }
1053 }
1054
1055 protected void refreshNumConflicts() {
1056 int count = 0;
1057 for (MultiValueResolutionDecision d : decisions.values()) {
1058 if (!d.isDecided()) {
1059 count++;
1060 }
1061 }
1062 setNumConflicts(count);
1063 }
1064
1065 protected void sort() {
1066 Collections.sort(
1067 displayedKeys,
1068 new Comparator<String>() {
1069 public int compare(String key1, String key2) {
1070 if (decisions.get(key1).isDecided() && ! decisions.get(key2).isDecided())
1071 return 1;
1072 else if (!decisions.get(key1).isDecided() && decisions.get(key2).isDecided())
1073 return -1;
1074 return key1.compareTo(key2);
1075 }
1076 }
1077 );
1078 }
1079
1080 /**
1081 * initializes the model from the current tags
1082 *
1083 */
1084 protected void rebuild() {
1085 if (tags == null) return;
1086 for(String key: tags.getKeys()) {
1087 MultiValueResolutionDecision decision = new MultiValueResolutionDecision(tags.getTagsFor(key));
1088 if (decisions.get(key) == null) {
1089 decisions.put(key,decision);
1090 }
1091 }
1092 displayedKeys.clear();
1093 Set<String> keys = tags.getKeys();
1094 if (showTagsWithConflictsOnly) {
1095 keys.retainAll(keysWithConflicts);
1096 if (showTagsWithMultiValuesOnly) {
1097 Set<String> keysWithMultiValues = new HashSet<String>();
1098 for (String key: keys) {
1099 if (decisions.get(key).canKeepAll()) {
1100 keysWithMultiValues.add(key);
1101 }
1102 }
1103 keys.retainAll(keysWithMultiValues);
1104 }
1105 for (String key: tags.getKeys()) {
1106 if (!decisions.get(key).isDecided() && !keys.contains(key)) {
1107 keys.add(key);
1108 }
1109 }
1110 }
1111 displayedKeys.addAll(keys);
1112 refreshNumConflicts();
1113 sort();
1114 fireTableDataChanged();
1115 }
1116
1117 /**
1118 * Populates the model with the tags for which conflicts are to be resolved.
1119 *
1120 * @param tags the tag collection with the tags. Must not be null.
1121 * @param keysWithConflicts the set of tag keys with conflicts
1122 * @throws IllegalArgumentException thrown if tags is null
1123 */
1124 public void populate(TagCollection tags, Set<String> keysWithConflicts) {
1125 CheckParameterUtil.ensureParameterNotNull(tags, "tags");
1126 this.tags = tags;
1127 displayedKeys = new ArrayList<String>();
1128 this.keysWithConflicts = keysWithConflicts == null ? new HashSet<String>() : keysWithConflicts;
1129 decisions = new HashMap<String, MultiValueResolutionDecision>();
1130 rebuild();
1131 }
1132
1133 @Override
1134 public int getRowCount() {
1135 if (displayedKeys == null) return 0;
1136 return displayedKeys.size();
1137 }
1138
1139 @Override
1140 public Object getValueAt(int row, int column) {
1141 return decisions.get(displayedKeys.get(row));
1142 }
1143
1144 @Override
1145 public boolean isCellEditable(int row, int column) {
1146 return column == 2;
1147 }
1148
1149 @Override
1150 public void setValueAt(Object value, int row, int column) {
1151 MultiValueResolutionDecision decision = decisions.get(displayedKeys.get(row));
1152 if (value instanceof String) {
1153 decision.keepOne((String)value);
1154 } else if (value instanceof MultiValueDecisionType) {
1155 MultiValueDecisionType type = (MultiValueDecisionType)value;
1156 switch(type) {
1157 case KEEP_NONE:
1158 decision.keepNone();
1159 break;
1160 case KEEP_ALL:
1161 decision.keepAll();
1162 break;
1163 }
1164 }
1165 fireTableDataChanged();
1166 refreshNumConflicts();
1167 }
1168
1169 /**
1170 * Replies true if each {@see MultiValueResolutionDecision} is decided.
1171 *
1172 * @return true if each {@see MultiValueResolutionDecision} is decided; false
1173 * otherwise
1174 */
1175 public boolean isResolvedCompletely() {
1176 return numConflicts == 0;
1177 }
1178
1179 public int getNumConflicts() {
1180 return numConflicts;
1181 }
1182
1183 public int getNumDecisions() {
1184 return getRowCount();
1185 }
1186
1187 //TODO Should this method work with all decisions or only with displayed decisions? For MergeNodes it should be
1188 //all decisions, but this method is also used on other places, so I've made new method just for MergeNodes
1189 public TagCollection getResolution() {
1190 TagCollection tc = new TagCollection();
1191 for (String key: displayedKeys) {
1192 tc.add(decisions.get(key).getResolution());
1193 }
1194 return tc;
1195 }
1196
1197 public TagCollection getAllResolutions() {
1198 TagCollection tc = new TagCollection();
1199 for (MultiValueResolutionDecision value: decisions.values()) {
1200 tc.add(value.getResolution());
1201 }
1202 return tc;
1203 }
1204
1205 public MultiValueResolutionDecision getDecision(int row) {
1206 return decisions.get(displayedKeys.get(row));
1207 }
1208
1209 /**
1210 * Sets whether all tags or only tags with conflicts are displayed
1211 *
1212 * @param showTagsWithConflictsOnly if true, only tags with conflicts are displayed
1213 */
1214 public void setShowTagsWithConflictsOnly(boolean showTagsWithConflictsOnly) {
1215 this.showTagsWithConflictsOnly = showTagsWithConflictsOnly;
1216 rebuild();
1217 }
1218
1219 /**
1220 * Sets whether all conflicts or only conflicts with multiple values are displayed
1221 *
1222 * @param showTagsWithMultiValuesOnly if true, only tags with multiple values are displayed
1223 */
1224 public void setShowTagsWithMultiValuesOnly(boolean showTagsWithMultiValuesOnly) {
1225 this.showTagsWithMultiValuesOnly = showTagsWithMultiValuesOnly;
1226 rebuild();
1227 }
1228
1229 /**
1230 * Prepare the default decisions for the current model
1231 *
1232 */
1233 public void prepareDefaultTagDecisions() {
1234 for (MultiValueResolutionDecision decision: decisions.values()) {
1235 List<String> values = decision.getValues();
1236 values.remove("");
1237 if (values.size() == 1) {
1238 decision.keepOne(values.get(0));
1239 } else {
1240 decision.keepAll();
1241 }
1242 }
1243 rebuild();
1244 }
1245
1246 }
1247
1248
1249 /**
1250 * This is a UI widget for resolving tag conflicts, i.e. differences of the tag values
1251 * of multiple {@see OsmPrimitive}s.
1252 *
1253 *
1254 */
1255 public class MyTagConflictResolver extends JPanel {
1256
1257 /** the model for the tag conflict resolver */
1258 private MyTagConflictResolverModel model;
1259 /** selects wheter only tags with conflicts are displayed */
1260 private JCheckBox cbShowTagsWithConflictsOnly;
1261 private JCheckBox cbShowTagsWithMultiValuesOnly;
1262
1263 protected JPanel buildInfoPanel() {
1264 JPanel pnl = new JPanel();
1265 pnl.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
1266 pnl.setLayout(new GridBagLayout());
1267 GridBagConstraints gc = new GridBagConstraints();
1268 gc.fill = GridBagConstraints.BOTH;
1269 gc.weighty = 1.0;
1270 gc.weightx = 1.0;
1271 gc.anchor = GridBagConstraints.LINE_START;
1272 pnl.add(new JLabel(tr("<html>Please select the values to keep for the following tags.</html>", null)), gc);
1273
1274 gc.gridy = 1;
1275 gc.fill = GridBagConstraints.HORIZONTAL;
1276 gc.weighty = 0.0;
1277 pnl.add(cbShowTagsWithConflictsOnly = new JCheckBox(tr("Show tags with conflicts only", null)), gc);
1278 pnl.add(cbShowTagsWithMultiValuesOnly = new JCheckBox(tr("Show tags with multiple values only", null)), gc);
1279 cbShowTagsWithConflictsOnly.addChangeListener(
1280 new ChangeListener() {
1281 public void stateChanged(ChangeEvent e) {
1282 model.setShowTagsWithConflictsOnly(cbShowTagsWithConflictsOnly.isSelected());
1283 cbShowTagsWithMultiValuesOnly.setEnabled(cbShowTagsWithConflictsOnly.isSelected());
1284 }
1285 }
1286 );
1287 cbShowTagsWithConflictsOnly.setSelected(
1288 Main.pref.getBoolean(getClass().getName() + ".showTagsWithConflictsOnly", false)
1289 );
1290 cbShowTagsWithMultiValuesOnly.addChangeListener(
1291 new ChangeListener() {
1292 public void stateChanged(ChangeEvent e) {
1293 model.setShowTagsWithMultiValuesOnly(cbShowTagsWithMultiValuesOnly.isSelected());
1294 }
1295 }
1296 );
1297 cbShowTagsWithMultiValuesOnly.setSelected(
1298 Main.pref.getBoolean(getClass().getName() + ".showTagsWithMultiValuesOnly", false)
1299 );
1300 cbShowTagsWithMultiValuesOnly.setEnabled(cbShowTagsWithConflictsOnly.isSelected());
1301 return pnl;
1302 }
1303
1304 /**
1305 * Remembers the current settings in the global preferences
1306 *
1307 */
1308 public void rememberPreferences() {
1309 Main.pref.put(getClass().getName() + ".showTagsWithConflictsOnly", cbShowTagsWithConflictsOnly.isSelected());
1310 Main.pref.put(getClass().getName() + ".showTagsWithMultiValuesOnly", cbShowTagsWithMultiValuesOnly.isSelected());
1311 }
1312
1313 protected void build() {
1314 setLayout(new BorderLayout());
1315 add(buildInfoPanel(), BorderLayout.NORTH);
1316 add(new JScrollPane(new MyTagConflictResolverTable(model)), BorderLayout.CENTER);
1317 }
1318
1319 public MyTagConflictResolver() {
1320 this.model = new MyTagConflictResolverModel();
1321 build();
1322 }
1323
1324 /**
1325 * Replies the model used by this dialog
1326 *
1327 * @return the model
1328 */
1329 public MyTagConflictResolverModel getModel() {
1330 return model;
1331 }
1332 }
1333
1334 public class MyTagConflictResolverTable extends JTable implements MultiValueCellEditor.NavigationListener {
1335
1336 private SelectNextColumnCellAction selectNextColumnCellAction;
1337 private SelectPreviousColumnCellAction selectPreviousColumnCellAction;
1338
1339 public MyTagConflictResolverTable(MyTagConflictResolverModel model) {
1340 super(model, new TagConflictResolverColumnModel());
1341 build();
1342 }
1343
1344 protected void build() {
1345 setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
1346 setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
1347 putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
1348
1349 // make ENTER behave like TAB
1350 //
1351 getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
1352 KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false), "selectNextColumnCell");
1353
1354 // install custom navigation actions
1355 //
1356 selectNextColumnCellAction = new SelectNextColumnCellAction();
1357 selectPreviousColumnCellAction = new SelectPreviousColumnCellAction();
1358 getActionMap().put("selectNextColumnCell", selectNextColumnCellAction);
1359 getActionMap().put("selectPreviousColumnCell", selectPreviousColumnCellAction);
1360
1361 ((MultiValueCellEditor)getColumnModel().getColumn(2).getCellEditor()).addNavigationListeners(this);
1362
1363 setRowHeight((int)new JComboBox().getPreferredSize().getHeight());
1364 }
1365
1366 /**
1367 * Action to be run when the user navigates to the next cell in the table, for instance by
1368 * pressing TAB or ENTER. The action alters the standard navigation path from cell to cell: <ul>
1369 * <li>it jumps over cells in the first column</li> <li>it automatically add a new empty row
1370 * when the user leaves the last cell in the table</li> <ul>
1371 *
1372 *
1373 */
1374 class SelectNextColumnCellAction extends AbstractAction {
1375 public void actionPerformed(ActionEvent e) {
1376 run();
1377 }
1378
1379 public void run() {
1380 int col = getSelectedColumn();
1381 int row = getSelectedRow();
1382 if (getCellEditor() != null) {
1383 getCellEditor().stopCellEditing();
1384 }
1385
1386 if (col == 2 && row < getRowCount() - 1) {
1387 row++;
1388 } else if (row < getRowCount() - 1) {
1389 col = 2;
1390 row++;
1391 }
1392 changeSelection(row, col, false, false);
1393 editCellAt(getSelectedRow(), getSelectedColumn());
1394 getEditorComponent().requestFocusInWindow();
1395 }
1396 }
1397
1398 /**
1399 * Action to be run when the user navigates to the previous cell in the table, for instance by
1400 * pressing Shift-TAB
1401 *
1402 */
1403 class SelectPreviousColumnCellAction extends AbstractAction {
1404
1405 public void actionPerformed(ActionEvent e) {
1406 run();
1407 }
1408
1409 public void run() {
1410 int col = getSelectedColumn();
1411 int row = getSelectedRow();
1412 if (getCellEditor() != null) {
1413 getCellEditor().stopCellEditing();
1414 }
1415
1416 if (col <= 0 && row <= 0) {
1417 // change nothing
1418 } else if (row > 0) {
1419 col = 2;
1420 row--;
1421 }
1422 changeSelection(row, col, false, false);
1423 editCellAt(getSelectedRow(), getSelectedColumn());
1424 getEditorComponent().requestFocusInWindow();
1425 }
1426 }
1427
1428 public void gotoNextDecision() {
1429 selectNextColumnCellAction.run();
1430 }
1431
1432 public void gotoPreviousDecision() {
1433 selectPreviousColumnCellAction.run();
1434 }
1435 }
1436}
Note: See TracBrowser for help on using the repository browser.