1 | // License: GPL. For details, see LICENSE file.
|
---|
2 | package mergeoverlap;
|
---|
3 |
|
---|
4 | import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
|
---|
5 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
6 | import static org.openstreetmap.josm.tools.I18n.trc;
|
---|
7 |
|
---|
8 | import java.awt.BorderLayout;
|
---|
9 | import java.awt.Component;
|
---|
10 | import java.awt.Dimension;
|
---|
11 | import java.awt.FlowLayout;
|
---|
12 | import java.awt.GridBagConstraints;
|
---|
13 | import java.awt.GridBagLayout;
|
---|
14 | import java.awt.Insets;
|
---|
15 | import java.awt.event.ActionEvent;
|
---|
16 | import java.awt.event.FocusAdapter;
|
---|
17 | import java.awt.event.FocusEvent;
|
---|
18 | import java.awt.event.HierarchyBoundsListener;
|
---|
19 | import java.awt.event.HierarchyEvent;
|
---|
20 | import java.awt.event.KeyEvent;
|
---|
21 | import java.awt.event.WindowAdapter;
|
---|
22 | import java.awt.event.WindowEvent;
|
---|
23 | import java.beans.PropertyChangeEvent;
|
---|
24 | import java.beans.PropertyChangeListener;
|
---|
25 | import java.beans.PropertyChangeSupport;
|
---|
26 | import java.util.ArrayList;
|
---|
27 | import java.util.Collection;
|
---|
28 | import java.util.Collections;
|
---|
29 | import java.util.Comparator;
|
---|
30 | import java.util.HashMap;
|
---|
31 | import java.util.HashSet;
|
---|
32 | import java.util.LinkedList;
|
---|
33 | import java.util.List;
|
---|
34 | import java.util.Map;
|
---|
35 | import java.util.Set;
|
---|
36 |
|
---|
37 | import javax.swing.AbstractAction;
|
---|
38 | import javax.swing.AbstractButton;
|
---|
39 | import javax.swing.Action;
|
---|
40 | import javax.swing.BorderFactory;
|
---|
41 | import javax.swing.BoxLayout;
|
---|
42 | import javax.swing.ButtonModel;
|
---|
43 | import javax.swing.JButton;
|
---|
44 | import javax.swing.JCheckBox;
|
---|
45 | import javax.swing.JComboBox;
|
---|
46 | import javax.swing.JComponent;
|
---|
47 | import javax.swing.JDialog;
|
---|
48 | import javax.swing.JLabel;
|
---|
49 | import javax.swing.JOptionPane;
|
---|
50 | import javax.swing.JPanel;
|
---|
51 | import javax.swing.JScrollPane;
|
---|
52 | import javax.swing.JSplitPane;
|
---|
53 | import javax.swing.JTable;
|
---|
54 | import javax.swing.KeyStroke;
|
---|
55 | import javax.swing.ListSelectionModel;
|
---|
56 | import javax.swing.UIManager;
|
---|
57 | import javax.swing.event.ChangeEvent;
|
---|
58 | import javax.swing.event.ChangeListener;
|
---|
59 | import javax.swing.table.DefaultTableModel;
|
---|
60 |
|
---|
61 | import org.openstreetmap.josm.Main;
|
---|
62 | import org.openstreetmap.josm.command.ChangePropertyCommand;
|
---|
63 | import org.openstreetmap.josm.command.Command;
|
---|
64 | import org.openstreetmap.josm.data.osm.Node;
|
---|
65 | import org.openstreetmap.josm.data.osm.OsmPrimitive;
|
---|
66 | import org.openstreetmap.josm.data.osm.Relation;
|
---|
67 | import org.openstreetmap.josm.data.osm.RelationMember;
|
---|
68 | import org.openstreetmap.josm.data.osm.RelationToChildReference;
|
---|
69 | import org.openstreetmap.josm.data.osm.TagCollection;
|
---|
70 | import org.openstreetmap.josm.data.osm.Way;
|
---|
71 | import org.openstreetmap.josm.gui.DefaultNameFormatter;
|
---|
72 | import org.openstreetmap.josm.gui.JMultilineLabel;
|
---|
73 | import org.openstreetmap.josm.gui.SideButton;
|
---|
74 | import org.openstreetmap.josm.gui.conflict.tags.MultiValueCellEditor;
|
---|
75 | import org.openstreetmap.josm.gui.conflict.tags.MultiValueDecisionType;
|
---|
76 | import org.openstreetmap.josm.gui.conflict.tags.MultiValueResolutionDecision;
|
---|
77 | import org.openstreetmap.josm.gui.conflict.tags.RelationMemberConflictDecision;
|
---|
78 | import org.openstreetmap.josm.gui.conflict.tags.RelationMemberConflictDecisionType;
|
---|
79 | import org.openstreetmap.josm.gui.conflict.tags.RelationMemberConflictResolverColumnModel;
|
---|
80 | import org.openstreetmap.josm.gui.conflict.tags.TagConflictResolverColumnModel;
|
---|
81 | import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
|
---|
82 | import org.openstreetmap.josm.gui.help.HelpUtil;
|
---|
83 | import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
|
---|
84 | import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
|
---|
85 | import org.openstreetmap.josm.tools.CheckParameterUtil;
|
---|
86 | import org.openstreetmap.josm.tools.ImageProvider;
|
---|
87 | import 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 | */
|
---|
118 | public 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 | }
|
---|