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

Last change on this file since 2936 was 2936, checked in by jttt, 14 years ago

Fixed #4408 confict manager: do not have to solve tag- and element-conficts when deleting

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