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

Last change on this file since 4163 was 3083, checked in by bastiK, 14 years ago

added svn:eol-style=native to source files

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