source: josm/trunk/src/org/openstreetmap/josm/gui/conflict/pair/tags/TagMerger.java@ 11381

Last change on this file since 11381 was 11381, checked in by Don-vip, 7 years ago

findbugs - RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE

  • Property svn:eol-style set to native
File size: 14.3 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.conflict.pair.tags;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Adjustable;
7import java.awt.GridBagConstraints;
8import java.awt.GridBagLayout;
9import java.awt.Insets;
10import java.awt.event.ActionEvent;
11import java.awt.event.AdjustmentEvent;
12import java.awt.event.AdjustmentListener;
13import java.awt.event.MouseAdapter;
14import java.awt.event.MouseEvent;
15import java.util.HashSet;
16import java.util.Set;
17
18import javax.swing.AbstractAction;
19import javax.swing.Action;
20import javax.swing.ImageIcon;
21import javax.swing.JButton;
22import javax.swing.JLabel;
23import javax.swing.JPanel;
24import javax.swing.JScrollPane;
25import javax.swing.JTable;
26import javax.swing.event.ListSelectionEvent;
27import javax.swing.event.ListSelectionListener;
28
29import org.openstreetmap.josm.data.conflict.Conflict;
30import org.openstreetmap.josm.data.osm.OsmPrimitive;
31import org.openstreetmap.josm.gui.conflict.pair.IConflictResolver;
32import org.openstreetmap.josm.gui.conflict.pair.MergeDecisionType;
33import org.openstreetmap.josm.gui.tagging.TagTableColumnModelBuilder;
34import org.openstreetmap.josm.tools.ImageProvider;
35
36/**
37 * UI component for resolving conflicts in the tag sets of two {@link OsmPrimitive}s.
38 * @since 1622
39 */
40public class TagMerger extends JPanel implements IConflictResolver {
41
42 private JTable mineTable;
43 private JTable mergedTable;
44 private JTable theirTable;
45 private final TagMergeModel model;
46 private final String[] keyvalue;
47 private transient AdjustmentSynchronizer adjustmentSynchronizer;
48
49 /**
50 * Constructs a new {@code TagMerger}.
51 */
52 public TagMerger() {
53 model = new TagMergeModel();
54 keyvalue = new String[]{tr("Key"), tr("Value")};
55 build();
56 }
57
58 /**
59 * embeds table in a new {@link JScrollPane} and returns th scroll pane
60 *
61 * @param table the table
62 * @return the scroll pane embedding the table
63 */
64 protected JScrollPane embeddInScrollPane(JTable table) {
65 JScrollPane pane = new JScrollPane(table);
66 adjustmentSynchronizer.synchronizeAdjustment(pane.getVerticalScrollBar());
67 return pane;
68 }
69
70 /**
71 * builds the table for my tag set (table already embedded in a scroll pane)
72 *
73 * @return the table (embedded in a scroll pane)
74 */
75 protected JScrollPane buildMineTagTable() {
76 mineTable = new JTable(model, new TagTableColumnModelBuilder(new MineTableCellRenderer(), keyvalue).build());
77 mineTable.setName("table.my");
78 return embeddInScrollPane(mineTable);
79 }
80
81 /**
82 * builds the table for their tag set (table already embedded in a scroll pane)
83 *
84 * @return the table (embedded in a scroll pane)
85 */
86 protected JScrollPane buildTheirTable() {
87 theirTable = new JTable(model, new TagTableColumnModelBuilder(new TheirTableCellRenderer(), keyvalue).build());
88 theirTable.setName("table.their");
89 return embeddInScrollPane(theirTable);
90 }
91
92 /**
93 * builds the table for the merged tag set (table already embedded in a scroll pane)
94 *
95 * @return the table (embedded in a scroll pane)
96 */
97
98 protected JScrollPane buildMergedTable() {
99 mergedTable = new JTable(model, new TagTableColumnModelBuilder(new MergedTableCellRenderer(), keyvalue).build());
100 mergedTable.setName("table.merged");
101 return embeddInScrollPane(mergedTable);
102 }
103
104 /**
105 * build the user interface
106 */
107 protected final void build() {
108 GridBagConstraints gc = new GridBagConstraints();
109 setLayout(new GridBagLayout());
110
111 adjustmentSynchronizer = new AdjustmentSynchronizer();
112
113 gc.gridx = 0;
114 gc.gridy = 0;
115 gc.gridwidth = 1;
116 gc.gridheight = 1;
117 gc.fill = GridBagConstraints.NONE;
118 gc.anchor = GridBagConstraints.CENTER;
119 gc.weightx = 0.0;
120 gc.weighty = 0.0;
121 gc.insets = new Insets(10, 0, 10, 0);
122 JLabel lblMy = new JLabel(tr("My version (local dataset)"));
123 add(lblMy, gc);
124
125 gc.gridx = 2;
126 gc.gridy = 0;
127 gc.gridwidth = 1;
128 gc.gridheight = 1;
129 gc.fill = GridBagConstraints.NONE;
130 gc.anchor = GridBagConstraints.CENTER;
131 gc.weightx = 0.0;
132 gc.weighty = 0.0;
133 JLabel lblMerge = new JLabel(tr("Merged version"));
134 add(lblMerge, gc);
135
136 gc.gridx = 4;
137 gc.gridy = 0;
138 gc.gridwidth = 1;
139 gc.gridheight = 1;
140 gc.fill = GridBagConstraints.NONE;
141 gc.anchor = GridBagConstraints.CENTER;
142 gc.weightx = 0.0;
143 gc.weighty = 0.0;
144 gc.insets = new Insets(0, 0, 0, 0);
145 JLabel lblTheir = new JLabel(tr("Their version (server dataset)"));
146 add(lblTheir, gc);
147
148 gc.gridx = 0;
149 gc.gridy = 1;
150 gc.gridwidth = 1;
151 gc.gridheight = 1;
152 gc.fill = GridBagConstraints.BOTH;
153 gc.anchor = GridBagConstraints.FIRST_LINE_START;
154 gc.weightx = 0.3;
155 gc.weighty = 1.0;
156 JScrollPane tabMy = buildMineTagTable();
157 lblMy.setLabelFor(tabMy);
158 add(tabMy, gc);
159
160 gc.gridx = 1;
161 gc.gridy = 1;
162 gc.gridwidth = 1;
163 gc.gridheight = 1;
164 gc.fill = GridBagConstraints.NONE;
165 gc.anchor = GridBagConstraints.CENTER;
166 gc.weightx = 0.0;
167 gc.weighty = 0.0;
168 KeepMineAction keepMineAction = new KeepMineAction();
169 mineTable.getSelectionModel().addListSelectionListener(keepMineAction);
170 JButton btnKeepMine = new JButton(keepMineAction);
171 btnKeepMine.setName("button.keepmine");
172 add(btnKeepMine, gc);
173
174 gc.gridx = 2;
175 gc.gridy = 1;
176 gc.gridwidth = 1;
177 gc.gridheight = 1;
178 gc.fill = GridBagConstraints.BOTH;
179 gc.anchor = GridBagConstraints.FIRST_LINE_START;
180 gc.weightx = 0.3;
181 gc.weighty = 1.0;
182 JScrollPane tabMerge = buildMergedTable();
183 lblMerge.setLabelFor(tabMerge);
184 add(tabMerge, gc);
185
186 gc.gridx = 3;
187 gc.gridy = 1;
188 gc.gridwidth = 1;
189 gc.gridheight = 1;
190 gc.fill = GridBagConstraints.NONE;
191 gc.anchor = GridBagConstraints.CENTER;
192 gc.weightx = 0.0;
193 gc.weighty = 0.0;
194 KeepTheirAction keepTheirAction = new KeepTheirAction();
195 JButton btnKeepTheir = new JButton(keepTheirAction);
196 btnKeepTheir.setName("button.keeptheir");
197 add(btnKeepTheir, gc);
198
199 gc.gridx = 4;
200 gc.gridy = 1;
201 gc.gridwidth = 1;
202 gc.gridheight = 1;
203 gc.fill = GridBagConstraints.BOTH;
204 gc.anchor = GridBagConstraints.FIRST_LINE_START;
205 gc.weightx = 0.3;
206 gc.weighty = 1.0;
207 JScrollPane tabTheir = buildTheirTable();
208 lblTheir.setLabelFor(tabTheir);
209 add(tabTheir, gc);
210 theirTable.getSelectionModel().addListSelectionListener(keepTheirAction);
211
212 DoubleClickAdapter dblClickAdapter = new DoubleClickAdapter();
213 mineTable.addMouseListener(dblClickAdapter);
214 theirTable.addMouseListener(dblClickAdapter);
215
216 gc.gridx = 2;
217 gc.gridy = 2;
218 gc.gridwidth = 1;
219 gc.gridheight = 1;
220 gc.fill = GridBagConstraints.NONE;
221 gc.anchor = GridBagConstraints.CENTER;
222 gc.weightx = 0.0;
223 gc.weighty = 0.0;
224 UndecideAction undecidedAction = new UndecideAction();
225 mergedTable.getSelectionModel().addListSelectionListener(undecidedAction);
226 JButton btnUndecide = new JButton(undecidedAction);
227 btnUndecide.setName("button.undecide");
228 add(btnUndecide, gc);
229 }
230
231 /**
232 * replies the model used by this tag merger
233 *
234 * @return the model
235 */
236 public TagMergeModel getModel() {
237 return model;
238 }
239
240 private void selectNextConflict(int ... rows) {
241 int max = rows[0];
242 for (int row: rows) {
243 if (row > max) {
244 max = row;
245 }
246 }
247 int index = model.getFirstUndecided(max+1);
248 if (index == -1) {
249 index = model.getFirstUndecided(0);
250 }
251 mineTable.getSelectionModel().setSelectionInterval(index, index);
252 theirTable.getSelectionModel().setSelectionInterval(index, index);
253 }
254
255 /**
256 * Keeps the currently selected tags in my table in the list of merged tags.
257 *
258 */
259 class KeepMineAction extends AbstractAction implements ListSelectionListener {
260 KeepMineAction() {
261 ImageIcon icon = ImageProvider.get("dialogs/conflict", "tagkeepmine");
262 if (icon != null) {
263 putValue(Action.SMALL_ICON, icon);
264 putValue(Action.NAME, "");
265 } else {
266 putValue(Action.NAME, ">");
267 }
268 putValue(Action.SHORT_DESCRIPTION, tr("Keep the selected key/value pairs from the local dataset"));
269 setEnabled(false);
270 }
271
272 @Override
273 public void actionPerformed(ActionEvent arg0) {
274 int[] rows = mineTable.getSelectedRows();
275 if (rows.length == 0)
276 return;
277 model.decide(rows, MergeDecisionType.KEEP_MINE);
278 selectNextConflict(rows);
279 }
280
281 @Override
282 public void valueChanged(ListSelectionEvent e) {
283 setEnabled(mineTable.getSelectedRowCount() > 0);
284 }
285 }
286
287 /**
288 * Keeps the currently selected tags in their table in the list of merged tags.
289 *
290 */
291 class KeepTheirAction extends AbstractAction implements ListSelectionListener {
292 KeepTheirAction() {
293 ImageIcon icon = ImageProvider.get("dialogs/conflict", "tagkeeptheir");
294 if (icon != null) {
295 putValue(Action.SMALL_ICON, icon);
296 putValue(Action.NAME, "");
297 } else {
298 putValue(Action.NAME, ">");
299 }
300 putValue(Action.SHORT_DESCRIPTION, tr("Keep the selected key/value pairs from the server dataset"));
301 setEnabled(false);
302 }
303
304 @Override
305 public void actionPerformed(ActionEvent arg0) {
306 int[] rows = theirTable.getSelectedRows();
307 if (rows.length == 0)
308 return;
309 model.decide(rows, MergeDecisionType.KEEP_THEIR);
310 selectNextConflict(rows);
311 }
312
313 @Override
314 public void valueChanged(ListSelectionEvent e) {
315 setEnabled(theirTable.getSelectedRowCount() > 0);
316 }
317 }
318
319 /**
320 * Synchronizes scrollbar adjustments between a set of
321 * {@link Adjustable}s. Whenever the adjustment of one of
322 * the registerd Adjustables is updated the adjustment of
323 * the other registered Adjustables is adjusted too.
324 *
325 */
326 static class AdjustmentSynchronizer implements AdjustmentListener {
327 private final Set<Adjustable> synchronizedAdjustables;
328
329 AdjustmentSynchronizer() {
330 synchronizedAdjustables = new HashSet<>();
331 }
332
333 public void synchronizeAdjustment(Adjustable adjustable) {
334 if (adjustable == null)
335 return;
336 if (synchronizedAdjustables.contains(adjustable))
337 return;
338 synchronizedAdjustables.add(adjustable);
339 adjustable.addAdjustmentListener(this);
340 }
341
342 @Override
343 public void adjustmentValueChanged(AdjustmentEvent e) {
344 for (Adjustable a : synchronizedAdjustables) {
345 if (a != e.getAdjustable()) {
346 a.setValue(e.getValue());
347 }
348 }
349 }
350 }
351
352 /**
353 * Handler for double clicks on entries in the three tag tables.
354 *
355 */
356 class DoubleClickAdapter extends MouseAdapter {
357
358 @Override
359 public void mouseClicked(MouseEvent e) {
360 if (e.getClickCount() != 2)
361 return;
362 JTable table;
363 MergeDecisionType mergeDecision;
364
365 if (e.getSource() == mineTable) {
366 table = mineTable;
367 mergeDecision = MergeDecisionType.KEEP_MINE;
368 } else if (e.getSource() == theirTable) {
369 table = theirTable;
370 mergeDecision = MergeDecisionType.KEEP_THEIR;
371 } else if (e.getSource() == mergedTable) {
372 table = mergedTable;
373 mergeDecision = MergeDecisionType.UNDECIDED;
374 } else
375 // double click in another component; shouldn't happen,
376 // but just in case
377 return;
378 int row = table.rowAtPoint(e.getPoint());
379 model.decide(row, mergeDecision);
380 }
381 }
382
383 /**
384 * Sets the currently selected tags in the table of merged tags to state
385 * {@link MergeDecisionType#UNDECIDED}
386 *
387 */
388 class UndecideAction extends AbstractAction implements ListSelectionListener {
389
390 UndecideAction() {
391 ImageIcon icon = ImageProvider.get("dialogs/conflict", "tagundecide");
392 if (icon != null) {
393 putValue(Action.SMALL_ICON, icon);
394 putValue(Action.NAME, "");
395 } else {
396 putValue(Action.NAME, tr("Undecide"));
397 }
398 putValue(SHORT_DESCRIPTION, tr("Mark the selected tags as undecided"));
399 setEnabled(false);
400 }
401
402 @Override
403 public void actionPerformed(ActionEvent arg0) {
404 int[] rows = mergedTable.getSelectedRows();
405 if (rows.length == 0)
406 return;
407 model.decide(rows, MergeDecisionType.UNDECIDED);
408 }
409
410 @Override
411 public void valueChanged(ListSelectionEvent e) {
412 setEnabled(mergedTable.getSelectedRowCount() > 0);
413 }
414 }
415
416 @Override
417 public void deletePrimitive(boolean deleted) {
418 // Use my entries, as it doesn't really matter
419 MergeDecisionType decision = deleted ? MergeDecisionType.KEEP_MINE : MergeDecisionType.UNDECIDED;
420 for (int i = 0; i < model.getRowCount(); i++) {
421 model.decide(i, decision);
422 }
423 }
424
425 @Override
426 public void populate(Conflict<? extends OsmPrimitive> conflict) {
427 model.populate(conflict.getMy(), conflict.getTheir());
428 for (JTable table : new JTable[]{mineTable, theirTable}) {
429 int index = table.getRowCount() > 0 ? 0 : -1;
430 table.getSelectionModel().setSelectionInterval(index, index);
431 }
432 }
433
434 @Override
435 public void decideRemaining(MergeDecisionType decision) {
436 model.decideRemaining(decision);
437 }
438}
Note: See TracBrowser for help on using the repository browser.