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

Last change on this file since 30712 was 30712, checked in by donvip, 10 years ago

[josm_mege_overlap] move duplicated code to "hack" package and move inner classes into separate files + code alignment of MyTagConflictResolver* to latest version of JOSM classes

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