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

Last change on this file since 30714 was 30714, checked in by donvip, 12 years ago

[josm_merge_overlap] fix #josm10626 - fix ClassCastException

File size: 16.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package mergeoverlap.hack;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.awt.BorderLayout;
8import java.awt.Component;
9import java.awt.Dimension;
10import java.awt.FlowLayout;
11import java.awt.event.ActionEvent;
12import java.awt.event.HierarchyBoundsListener;
13import java.awt.event.HierarchyEvent;
14import java.awt.event.WindowAdapter;
15import java.awt.event.WindowEvent;
16import java.beans.PropertyChangeEvent;
17import java.beans.PropertyChangeListener;
18import java.util.HashSet;
19import java.util.LinkedList;
20import java.util.List;
21import java.util.Map;
22import java.util.Set;
23
24import javax.swing.AbstractAction;
25import javax.swing.Action;
26import javax.swing.JDialog;
27import javax.swing.JLabel;
28import javax.swing.JOptionPane;
29import javax.swing.JPanel;
30import javax.swing.JSplitPane;
31
32import org.openstreetmap.josm.Main;
33import org.openstreetmap.josm.command.ChangePropertyCommand;
34import org.openstreetmap.josm.command.Command;
35import org.openstreetmap.josm.data.osm.Node;
36import org.openstreetmap.josm.data.osm.OsmPrimitive;
37import org.openstreetmap.josm.data.osm.Relation;
38import org.openstreetmap.josm.data.osm.TagCollection;
39import org.openstreetmap.josm.data.osm.Way;
40import org.openstreetmap.josm.gui.DefaultNameFormatter;
41import org.openstreetmap.josm.gui.SideButton;
42import org.openstreetmap.josm.gui.conflict.tags.MultiValueResolutionDecision;
43import org.openstreetmap.josm.gui.conflict.tags.RelationMemberConflictDecision;
44import org.openstreetmap.josm.gui.conflict.tags.RelationMemberConflictDecisionType;
45import org.openstreetmap.josm.gui.conflict.tags.TagConflictResolver;
46import org.openstreetmap.josm.gui.conflict.tags.TagConflictResolverModel;
47import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
48import org.openstreetmap.josm.gui.help.HelpUtil;
49import org.openstreetmap.josm.tools.ImageProvider;
50import org.openstreetmap.josm.tools.WindowGeometry;
51
52/**
53 * This dialog helps to resolve conflicts occurring when ways are combined or
54 * nodes are merged.
55 *
56 * There is a singleton instance of this dialog which can be retrieved using
57 * {@see #getInstance()}.
58 *
59 * The dialog uses two models: one for resolving tag conflicts, the other
60 * for resolving conflicts in relation memberships. For both models there are accessors,
61 * i.e {@see #getTagConflictResolverModel()} and {@see #getRelationMemberConflictResolverModel()}.
62 *
63 * Models have to be <strong>populated</strong> before the dialog is launched. Example:
64 * <pre>
65 * CombinePrimitiveResolverDialog dialog = CombinePrimitiveResolverDialog.getInstance();
66 * dialog.getTagConflictResolverModel().populate(aTagCollection);
67 * dialog.getRelationMemberConflictResolverModel().populate(aRelationLinkCollection);
68 * dialog.prepareDefaultDecisions();
69 * </pre>
70 *
71 * You should also set the target primitive which other primitives (ways or nodes) are
72 * merged to, see {@see #setTargetPrimitive(OsmPrimitive)}.
73 *
74 * After the dialog is closed use {@see #isCancelled()} to check whether the user canceled
75 * the dialog. If it wasn't canceled you may build a collection of {@see Command} objects
76 * which reflect the conflict resolution decisions the user made in the dialog:
77 * see {@see #buildResolutionCommands()}
78 *
79 *
80 */
81public class MyCombinePrimitiveResolverDialog extends JDialog {
82
83 /** the unique instance of the dialog */
84 static private MyCombinePrimitiveResolverDialog instance;
85
86 /**
87 * Replies the unique instance of the dialog
88 *
89 * @return the unique instance of the dialog
90 */
91 public static MyCombinePrimitiveResolverDialog getInstance() {
92 if (instance == null) {
93 instance = new MyCombinePrimitiveResolverDialog(Main.parent);
94 }
95 return instance;
96 }
97
98 private AutoAdjustingSplitPane spTagConflictTypes;
99 private TagConflictResolver pnlTagConflictResolver;
100 private MyRelationMemberConflictResolver pnlRelationMemberConflictResolver;
101 private boolean cancelled;
102 private JPanel pnlButtons;
103 private OsmPrimitive targetPrimitive;
104
105 /** the private help action */
106 private ContextSensitiveHelpAction helpAction;
107 /** the apply button */
108 private SideButton btnApply;
109
110 /**
111 * Replies the target primitive the collection of primitives is merged
112 * or combined to.
113 *
114 * @return the target primitive
115 */
116 public OsmPrimitive getTargetPrimitmive() {
117 return targetPrimitive;
118 }
119
120 /**
121 * Sets the primitive the collection of primitives is merged or combined
122 * to.
123 *
124 * @param primitive the target primitive
125 */
126 public void setTargetPrimitive(OsmPrimitive primitive) {
127 this.targetPrimitive = primitive;
128 updateTitle();
129 if (primitive instanceof Way) {
130 pnlRelationMemberConflictResolver.initForWayCombining();
131 } else if (primitive instanceof Node) {
132 pnlRelationMemberConflictResolver.initForNodeMerging();
133 }
134 }
135
136 protected void updateTitle() {
137 if (targetPrimitive == null) {
138 setTitle(tr("Conflicts when combining primitives"));
139 return;
140 }
141 if (targetPrimitive instanceof Way) {
142 setTitle(tr("Conflicts when combining ways - combined way is ''{0}''", targetPrimitive
143 .getDisplayName(DefaultNameFormatter.getInstance())));
144 helpAction.setHelpTopic(ht("/Action/CombineWay#ResolvingConflicts"));
145 getRootPane().putClientProperty("help", ht("/Action/CombineWay#ResolvingConflicts"));
146 } else if (targetPrimitive instanceof Node) {
147 setTitle(tr("Conflicts when merging nodes - target node is ''{0}''", targetPrimitive
148 .getDisplayName(DefaultNameFormatter.getInstance())));
149 helpAction.setHelpTopic(ht("/Action/MergeNodes#ResolvingConflicts"));
150 getRootPane().putClientProperty("help", ht("/Action/MergeNodes#ResolvingConflicts"));
151 }
152 }
153
154 protected void build() {
155 getContentPane().setLayout(new BorderLayout());
156 updateTitle();
157 spTagConflictTypes = new AutoAdjustingSplitPane(JSplitPane.VERTICAL_SPLIT);
158 spTagConflictTypes.setTopComponent(buildTagConflictResolverPanel());
159 spTagConflictTypes.setBottomComponent(buildRelationMemberConflictResolverPanel());
160 getContentPane().add(pnlButtons = buildButtonPanel(), BorderLayout.SOUTH);
161 addWindowListener(new AdjustDividerLocationAction());
162 HelpUtil.setHelpContext(getRootPane(), ht("/"));
163 }
164
165 protected JPanel buildTagConflictResolverPanel() {
166 pnlTagConflictResolver = new TagConflictResolver();
167 return pnlTagConflictResolver;
168 }
169
170 protected JPanel buildRelationMemberConflictResolverPanel() {
171 pnlRelationMemberConflictResolver = new MyRelationMemberConflictResolver();
172 return pnlRelationMemberConflictResolver;
173 }
174
175 protected JPanel buildButtonPanel() {
176 JPanel pnl = new JPanel();
177 pnl.setLayout(new FlowLayout(FlowLayout.CENTER));
178
179 // -- apply button
180 ApplyAction applyAction = new ApplyAction();
181 pnlTagConflictResolver.getModel().addPropertyChangeListener(applyAction);
182 pnlRelationMemberConflictResolver.getModel().addPropertyChangeListener(applyAction);
183 btnApply = new SideButton(applyAction);
184 btnApply.setFocusable(true);
185 pnl.add(btnApply);
186
187 // -- cancel button
188 CancelAction cancelAction = new CancelAction();
189 pnl.add(new SideButton(cancelAction));
190
191 // -- help button
192 helpAction = new ContextSensitiveHelpAction();
193 pnl.add(new SideButton(helpAction));
194
195 return pnl;
196 }
197
198 public MyCombinePrimitiveResolverDialog(Component owner) {
199 super(JOptionPane.getFrameForComponent(owner), ModalityType.DOCUMENT_MODAL);
200 build();
201 }
202
203 public TagConflictResolverModel getTagConflictResolverModel() {
204 return pnlTagConflictResolver.getModel();
205 }
206
207 public MyRelationMemberConflictResolverModel getRelationMemberConflictResolverModel() {
208 return pnlRelationMemberConflictResolver.getModel();
209 }
210
211 protected List<Command> buildTagChangeCommand(OsmPrimitive primitive, TagCollection tc) {
212 LinkedList<Command> cmds = new LinkedList<>();
213 for (String key : tc.getKeys()) {
214 if (tc.hasUniqueEmptyValue(key)) {
215 if (primitive.get(key) != null) {
216 cmds.add(new ChangePropertyCommand(primitive, key, null));
217 }
218 } else {
219 String value = tc.getJoinedValues(key);
220 if (!value.equals(primitive.get(key))) {
221 cmds.add(new ChangePropertyCommand(primitive, key, value));
222 }
223 }
224 }
225 return cmds;
226 }
227
228 public List<Command> buildWayResolutionCommands() {
229 List<Command> cmds = new LinkedList<>();
230
231 TagCollection allResolutions = getTagConflictResolverModel().getAllResolutions();
232 if (allResolutions.size() > 0) {
233 cmds.addAll(buildTagChangeCommand(targetPrimitive, allResolutions));
234 }
235 if (targetPrimitive.get("created_by") != null) {
236 cmds.add(new ChangePropertyCommand(targetPrimitive, "created_by", null));
237 }
238
239 Command cmd = pnlRelationMemberConflictResolver.buildTagApplyCommands(getRelationMemberConflictResolverModel()
240 .getModifiedRelations(targetPrimitive));
241 if (cmd != null) {
242 cmds.add(cmd);
243 }
244 return cmds;
245 }
246
247 public void buildRelationCorrespondance(Map<Relation, Relation> newRelations, Map<Way, Way> oldWays) {
248 getRelationMemberConflictResolverModel().buildRelationCorrespondance(targetPrimitive, newRelations, oldWays);
249 }
250
251 protected void prepareDefaultTagDecisions() {
252 TagConflictResolverModel model = getTagConflictResolverModel();
253 for (int i = 0; i < model.getRowCount(); i++) {
254 MultiValueResolutionDecision decision = model.getDecision(i);
255 List<String> values = decision.getValues();
256 values.remove("");
257 if (values.size() == 1) {
258 decision.keepOne(values.get(0));
259 } else {
260 decision.keepAll();
261 }
262 }
263 model.rebuild();
264 }
265
266 protected void prepareDefaultRelationDecisions() {
267 MyRelationMemberConflictResolverModel model = getRelationMemberConflictResolverModel();
268 Set<Relation> relations = new HashSet<>();
269 for (int i = 0; i < model.getNumDecisions(); i++) {
270 RelationMemberConflictDecision decision = model.getDecision(i);
271 if (!relations.contains(decision.getRelation())) {
272 decision.decide(RelationMemberConflictDecisionType.KEEP);
273 relations.add(decision.getRelation());
274 } else {
275 decision.decide(RelationMemberConflictDecisionType.REMOVE);
276 }
277 }
278 model.refresh();
279 }
280
281 public void prepareDefaultDecisions() {
282 prepareDefaultTagDecisions();
283 prepareDefaultRelationDecisions();
284 }
285
286 protected JPanel buildEmptyConflictsPanel() {
287 JPanel pnl = new JPanel();
288 pnl.setLayout(new BorderLayout());
289 pnl.add(new JLabel(tr("No conflicts to resolve")));
290 return pnl;
291 }
292
293 protected void prepareGUIBeforeConflictResolutionStarts() {
294 MyRelationMemberConflictResolverModel relModel = getRelationMemberConflictResolverModel();
295 TagConflictResolverModel tagModel = getTagConflictResolverModel();
296 getContentPane().removeAll();
297
298 if (relModel.getNumDecisions() > 0 && tagModel.getNumDecisions() > 0) {
299 // display both, the dialog for resolving relation conflicts and for resolving
300 // tag conflicts
301 spTagConflictTypes.setTopComponent(pnlTagConflictResolver);
302 spTagConflictTypes.setBottomComponent(pnlRelationMemberConflictResolver);
303 getContentPane().add(spTagConflictTypes, BorderLayout.CENTER);
304 } else if (relModel.getNumDecisions() > 0) {
305 // relation conflicts only
306 //
307 getContentPane().add(pnlRelationMemberConflictResolver, BorderLayout.CENTER);
308 } else if (tagModel.getNumDecisions() > 0) {
309 // tag conflicts only
310 //
311 getContentPane().add(pnlTagConflictResolver, BorderLayout.CENTER);
312 } else {
313 getContentPane().add(buildEmptyConflictsPanel(), BorderLayout.CENTER);
314 }
315
316 getContentPane().add(pnlButtons, BorderLayout.SOUTH);
317 validate();
318 int numTagDecisions = getTagConflictResolverModel().getNumDecisions();
319 int numRelationDecisions = getRelationMemberConflictResolverModel().getNumDecisions();
320 if (numTagDecisions > 0 && numRelationDecisions > 0) {
321 spTagConflictTypes.setDividerLocation(0.5);
322 }
323 pnlRelationMemberConflictResolver.prepareForEditing();
324 }
325
326 protected void setCancelled(boolean cancelled) {
327 this.cancelled = cancelled;
328 }
329
330 public boolean isCancelled() {
331 return cancelled;
332 }
333
334 @Override
335 public void setVisible(boolean visible) {
336 if (visible) {
337 prepareGUIBeforeConflictResolutionStarts();
338 new WindowGeometry(getClass().getName() + ".geometry", WindowGeometry.centerInWindow(Main.parent,
339 new Dimension(600, 400))).applySafe(this);
340 setCancelled(false);
341 btnApply.requestFocusInWindow();
342 } else if (isShowing()) { // Avoid IllegalComponentStateException like in #8775
343 new WindowGeometry(this).remember(getClass().getName() + ".geometry");
344 }
345 super.setVisible(visible);
346 }
347
348 class CancelAction extends AbstractAction {
349
350 public CancelAction() {
351 putValue(Action.SHORT_DESCRIPTION, tr("Cancel conflict resolution"));
352 putValue(Action.NAME, tr("Cancel"));
353 putValue(Action.SMALL_ICON, ImageProvider.get("", "cancel"));
354 setEnabled(true);
355 }
356
357 @Override
358 public void actionPerformed(ActionEvent arg0) {
359 setCancelled(true);
360 setVisible(false);
361 }
362 }
363
364 class ApplyAction extends AbstractAction implements PropertyChangeListener {
365
366 public ApplyAction() {
367 putValue(Action.SHORT_DESCRIPTION, tr("Apply resolved conflicts"));
368 putValue(Action.NAME, tr("Apply"));
369 putValue(Action.SMALL_ICON, ImageProvider.get("ok"));
370 updateEnabledState();
371 }
372
373 @Override
374 public void actionPerformed(ActionEvent arg0) {
375 setVisible(false);
376 pnlTagConflictResolver.rememberPreferences();
377 }
378
379 protected void updateEnabledState() {
380 setEnabled(pnlTagConflictResolver.getModel().getNumConflicts() == 0
381 && pnlRelationMemberConflictResolver.getModel().getNumConflicts() == 0);
382 }
383
384 @Override
385 public void propertyChange(PropertyChangeEvent evt) {
386 if (evt.getPropertyName().equals(TagConflictResolverModel.NUM_CONFLICTS_PROP)) {
387 updateEnabledState();
388 }
389 if (evt.getPropertyName().equals(MyRelationMemberConflictResolverModel.NUM_CONFLICTS_PROP)) {
390 updateEnabledState();
391 }
392 }
393 }
394
395 class AdjustDividerLocationAction extends WindowAdapter {
396 @Override
397 public void windowOpened(WindowEvent e) {
398 int numTagDecisions = getTagConflictResolverModel().getNumDecisions();
399 int numRelationDecisions = getRelationMemberConflictResolverModel().getNumDecisions();
400 if (numTagDecisions > 0 && numRelationDecisions > 0) {
401 spTagConflictTypes.setDividerLocation(0.5);
402 }
403 }
404 }
405
406 static class AutoAdjustingSplitPane extends JSplitPane implements PropertyChangeListener, HierarchyBoundsListener {
407 private double dividerLocation;
408
409 public AutoAdjustingSplitPane(int newOrientation) {
410 super(newOrientation);
411 addPropertyChangeListener(JSplitPane.DIVIDER_LOCATION_PROPERTY, this);
412 addHierarchyBoundsListener(this);
413 }
414
415 @Override
416 public void ancestorResized(HierarchyEvent e) {
417 setDividerLocation((int) (dividerLocation * getHeight()));
418 }
419
420 @Override
421 public void ancestorMoved(HierarchyEvent e) {
422 // do nothing
423 }
424
425 @Override
426 public void propertyChange(PropertyChangeEvent evt) {
427 if (evt.getPropertyName().equals(JSplitPane.DIVIDER_LOCATION_PROPERTY)) {
428 int newVal = (Integer) evt.getNewValue();
429 if (getHeight() != 0) {
430 dividerLocation = (double) newVal / (double) getHeight();
431 }
432 }
433 }
434 }
435}
Note: See TracBrowser for help on using the repository browser.