source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/HistoryDialog.java@ 1814

Last change on this file since 1814 was 1814, checked in by Gubaer, 15 years ago

removed dependencies to Main.ds, removed Main.ds
removed AddVisitor, NameVisitor, DeleteVisitor - unnecessary double dispatching for these simple cases

  • Property svn:eol-style set to native
File size: 16.7 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.gui.dialogs;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.BorderLayout;
7import java.awt.Color;
8import java.awt.Component;
9import java.awt.GridLayout;
10import java.awt.event.ActionEvent;
11import java.awt.event.KeyEvent;
12import java.awt.event.MouseAdapter;
13import java.awt.event.MouseEvent;
14import java.io.IOException;
15import java.util.ArrayList;
16import java.util.Collection;
17import java.util.HashMap;
18import java.util.Iterator;
19
20import javax.swing.AbstractAction;
21import javax.swing.Action;
22import javax.swing.ImageIcon;
23import javax.swing.JComponent;
24import javax.swing.JLabel;
25import javax.swing.JOptionPane;
26import javax.swing.JPanel;
27import javax.swing.JScrollPane;
28import javax.swing.JTable;
29import javax.swing.ListSelectionModel;
30import javax.swing.event.ListSelectionEvent;
31import javax.swing.event.ListSelectionListener;
32import javax.swing.table.DefaultTableCellRenderer;
33import javax.swing.table.DefaultTableColumnModel;
34import javax.swing.table.DefaultTableModel;
35import javax.swing.table.TableCellRenderer;
36import javax.swing.table.TableColumn;
37
38import org.openstreetmap.josm.Main;
39import org.openstreetmap.josm.data.SelectionChangedListener;
40import org.openstreetmap.josm.data.osm.DataSet;
41import org.openstreetmap.josm.data.osm.OsmPrimitive;
42import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
43import org.openstreetmap.josm.data.osm.history.History;
44import org.openstreetmap.josm.data.osm.history.HistoryDataSet;
45import org.openstreetmap.josm.gui.PleaseWaitRunnable;
46import org.openstreetmap.josm.gui.SideButton;
47import org.openstreetmap.josm.gui.history.HistoryBrowserDialog;
48import org.openstreetmap.josm.io.OsmApi;
49import org.openstreetmap.josm.io.OsmApiException;
50import org.openstreetmap.josm.io.OsmServerHistoryReader;
51import org.openstreetmap.josm.io.OsmTransferException;
52import org.openstreetmap.josm.tools.ImageProvider;
53import org.openstreetmap.josm.tools.Shortcut;
54import org.xml.sax.SAXException;
55
56/**
57 * History dialog works like follows:
58 *
59 * There is a history cache hold in the back for primitives of the last refresh.
60 * When the user refreshes, this cache is cleared and all currently selected items
61 * are reloaded.
62 * If the user has selected at least one primitive not in the cache, the list
63 * is not displayed. Elsewhere, the list of all changes of all currently selected
64 * objects are displayed.
65 *
66 * @author imi
67 */
68public class HistoryDialog extends ToggleDialog {
69
70 /** the registry of history browser dialogs which are currently displaying */
71 static private HashMap<Long, HistoryBrowserDialog> historyBrowserDialogs;
72
73 /**
74 * registers a {@see HistoryBrowserDialog}
75 * @param id the id of the primitive dialog shows the history for
76 * @param dialog the dialog
77 */
78 public static void registerHistoryBrowserDialog(long id, HistoryBrowserDialog dialog) {
79 if (historyBrowserDialogs == null) {
80 historyBrowserDialogs = new HashMap<Long, HistoryBrowserDialog>();
81 }
82 historyBrowserDialogs.put(id, dialog);
83 }
84
85 /**
86 * unregisters a {@see HistoryBrowserDialog}
87 * @param id the id of the primitive whose history dialog is to be unregistered
88 *
89 */
90 public static void unregisterHistoryBrowserDialog(long id) {
91 if (historyBrowserDialogs == null)
92 return;
93 historyBrowserDialogs.remove(id);
94 }
95
96 /**
97 * replies the history dialog for the primitive with id <code>id</code>; null, if
98 * no such {@see HistoryBrowserDialog} is currently showing
99 *
100 * @param id the id of the primitive
101 * @return the dialog; null, if no such dialog is showing
102 */
103 public static HistoryBrowserDialog getHistoryBrowserDialog(long id) {
104 if (historyBrowserDialogs == null)
105 return null;
106 return historyBrowserDialogs.get(id);
107 }
108
109
110 /** the table model */
111 protected HistoryItemDataModel model;
112 /** the table with the history items */
113 protected JTable historyTable;
114
115 protected ShowHistoryAction showHistoryAction;
116 protected ReloadAction reloadAction;
117
118 /**
119 * builds the row with the command buttons
120 *
121 * @return the rows with the command buttons
122 */
123 protected JPanel buildButtonRow() {
124 JPanel buttons = new JPanel(new GridLayout(1,2));
125
126 SideButton btn = new SideButton(reloadAction = new ReloadAction());
127 btn.setName("btn.reload");
128 buttons.add(btn);
129
130 btn = new SideButton(showHistoryAction = new ShowHistoryAction());
131 btn.setName("btn.showhistory");
132 buttons.add(btn);
133
134 return buttons;
135 }
136
137 /**
138 * builds the GUI
139 */
140 protected void build() {
141 model = new HistoryItemDataModel();
142 //setLayout(new BorderLayout());
143 historyTable = new JTable(
144 model,
145 new HistoryTableColumnModel()
146 );
147 historyTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
148 historyTable.setName("table.historyitems");
149 final TableCellRenderer oldRenderer = historyTable.getTableHeader().getDefaultRenderer();
150 historyTable.getTableHeader().setDefaultRenderer(new DefaultTableCellRenderer(){
151 @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
152 JComponent c = (JComponent)oldRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
153 if (!value.equals(""))
154 return c;
155 JLabel l = new JLabel(ImageProvider.get("misc","showhide"));
156 l.setForeground(c.getForeground());
157 l.setBackground(c.getBackground());
158 l.setFont(c.getFont());
159 l.setBorder(c.getBorder());
160 l.setOpaque(true);
161 return l;
162 }
163 });
164 historyTable.addMouseListener(
165 new MouseAdapter() {
166 @Override
167 public void mouseClicked(MouseEvent e) {
168 if (e.getClickCount() == 2) {
169 int row = historyTable.rowAtPoint(e.getPoint());
170 History h = model.get(row);
171 showHistory(h);
172 }
173 }
174 }
175 );
176
177 JScrollPane pane = new JScrollPane(historyTable);
178 pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
179 pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
180 historyTable.setTableHeader(null);
181 pane.setColumnHeaderView(null);
182 add(pane, BorderLayout.CENTER);
183
184 add(buildButtonRow(), BorderLayout.SOUTH);
185
186 // wire actions
187 //
188 historyTable.getSelectionModel().addListSelectionListener(showHistoryAction);
189 DataSet.selListeners.add(reloadAction);
190 }
191
192 public HistoryDialog() {
193 super(tr("History"), "history", tr("Display the history of all selected items."),
194 Shortcut.registerShortcut("subwindow:history", tr("Toggle: {0}", tr("History")), KeyEvent.VK_H,
195 Shortcut.GROUP_LAYER, Shortcut.SHIFT_DEFAULT), 150);
196 build();
197 DataSet.selListeners.add(model);
198 }
199
200 /**
201 * refreshes the current list of history items; reloads history information from the server
202 */
203 protected void refresh() {
204 HistoryLoadTask task = new HistoryLoadTask();
205 Main.worker.execute(task);
206 }
207
208 /**
209 * shows the {@see HistoryBrowserDialog} for a given {@see History}
210 *
211 * @param h the history. Must not be null.
212 * @exception IllegalArgumentException thrown, if h is null
213 */
214 protected void showHistory(History h) throws IllegalArgumentException {
215 if (h == null)
216 throw new IllegalArgumentException(tr("parameter ''{0}'' must not be null", "h"));
217 HistoryBrowserDialog dialog = getHistoryBrowserDialog(h.getId());
218 if (dialog == null) {
219 dialog = new HistoryBrowserDialog(h);
220 }
221 dialog.setVisible(true);
222 }
223
224 /**
225 * invoked after the asynchronous {@see HistoryLoadTask} is finished.
226 *
227 * @param task the task which is calling back.
228 */
229 protected void postRefresh(HistoryLoadTask task) {
230 model.refresh();
231 if (task.isCancelled())
232 return;
233 if (task.getLastException() != null) {
234 task.getLastException().printStackTrace();
235 String msg = null;
236 if (task.getLastException() instanceof OsmApiException) {
237 msg = ((OsmApiException)task.getLastException()).getErrorBody();
238 if (msg == null) {
239 msg = ((OsmApiException)task.getLastException()).getErrorHeader();
240 }
241 }
242 if (msg == null) {
243 msg = task.getLastException().getMessage();
244 }
245 if (msg == null) {
246 msg = task.getLastException().toString();
247 }
248 JOptionPane.showMessageDialog(
249 Main.parent,
250 tr(
251 "<html>Failed to load history from the server. Details:<br>{0}</html>",
252 msg.replaceAll("&", "&amp;").replaceAll(">", "&gt;").replaceAll("<", "&lt;")
253 ),
254 tr("Error"),
255 JOptionPane.ERROR_MESSAGE
256 );
257 }
258 }
259
260 /**
261 * The table model with the history items
262 *
263 */
264 class HistoryItemDataModel extends DefaultTableModel implements SelectionChangedListener{
265 private ArrayList<History> data;
266
267 public HistoryItemDataModel() {
268 data = new ArrayList<History>();
269 }
270
271 @Override
272 public int getRowCount() {
273 if (data == null)
274 return 0;
275 return data.size();
276 }
277
278 @Override
279 public Object getValueAt(int row, int column) {
280 return data.get(row);
281 }
282
283 @Override
284 public boolean isCellEditable(int row, int column) {
285 return false;
286 }
287
288 public void refresh() {
289 data.clear();
290 for (OsmPrimitive primitive: Main.main.getCurrentDataSet().getSelected()) {
291 if (primitive.id == 0) {
292 continue;
293 }
294 History h = HistoryDataSet.getInstance().getHistory(primitive.id);
295 if (h !=null) {
296 data.add(h);
297 }
298 }
299 fireTableDataChanged();
300 }
301
302 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
303 refresh();
304 }
305
306 public History get(int idx) throws IndexOutOfBoundsException {
307 if (idx < 0 || idx >= data.size())
308 throw new IndexOutOfBoundsException(tr("index out of bounds Got {0}", idx));
309 return data.get(idx);
310 }
311 }
312
313
314 /**
315 * The table cell renderer for the history items.
316 *
317 */
318 class HistoryTableCellRenderer extends JLabel implements TableCellRenderer {
319
320 public final Color BGCOLOR_SELECTED = new Color(143,170,255);
321
322 private HashMap<OsmPrimitiveType, ImageIcon> icons;
323
324 public HistoryTableCellRenderer() {
325 setOpaque(true);
326 icons = new HashMap<OsmPrimitiveType, ImageIcon>();
327 icons.put(OsmPrimitiveType.NODE, ImageProvider.get("data", "node"));
328 icons.put(OsmPrimitiveType.WAY, ImageProvider.get("data", "way"));
329 icons.put(OsmPrimitiveType.RELATION, ImageProvider.get("data", "relation"));
330 }
331
332 protected void renderIcon(History history) {
333 setIcon(icons.get(history.getEarliest().getType()));
334 }
335
336 protected void renderText(History h) {
337 setText(h.getEarliest().getType().getLocalizedDisplayNameSingular() + " " + h.getId());
338 }
339
340 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
341 boolean hasFocus, int row, int column) {
342 History h = (History)value;
343 renderIcon(h);
344 renderText(h);
345 if (isSelected) {
346 setBackground(BGCOLOR_SELECTED);
347 } else {
348 setBackground(Color.WHITE);
349 }
350 return this;
351 }
352 }
353
354 /**
355 * The column model
356 */
357 class HistoryTableColumnModel extends DefaultTableColumnModel {
358 protected void createColumns() {
359 TableColumn col = null;
360 HistoryTableCellRenderer renderer = new HistoryTableCellRenderer();
361 // column 0 - History item
362 col = new TableColumn(0);
363 col.setHeaderValue(tr("History item"));
364 col.setCellRenderer(renderer);
365 addColumn(col);
366 }
367
368 public HistoryTableColumnModel() {
369 createColumns();
370 }
371 }
372
373 /**
374 * The asynchronous task which loads history information for the currently selected
375 * primitives from the server.
376 *
377 */
378 class HistoryLoadTask extends PleaseWaitRunnable {
379
380 private boolean cancelled = false;
381 private Exception lastException = null;
382
383 public HistoryLoadTask() {
384 super(tr("Load history"), true);
385 }
386
387 @Override
388 protected void cancel() {
389 OsmApi.getOsmApi().cancel();
390 cancelled = true;
391 }
392
393 @Override
394 protected void finish() {
395 postRefresh(this);
396 }
397
398 @Override
399 protected void realRun() throws SAXException, IOException, OsmTransferException {
400 Collection<OsmPrimitive> selection = Main.main.getCurrentDataSet().getSelected();
401 Iterator<OsmPrimitive> it = selection.iterator();
402 try {
403 while(it.hasNext()) {
404 OsmPrimitive primitive = it.next();
405 if (cancelled) {
406 break;
407 }
408 if (primitive.id == 0) {
409 continue;
410 }
411 progressMonitor.indeterminateSubTask(tr("Loading history for {0} with id {1}",
412 OsmPrimitiveType.from(primitive).getLocalizedDisplayNameSingular(),
413 Long.toString(primitive.id)));
414 OsmServerHistoryReader reader = null;
415 HistoryDataSet ds = null;
416 try {
417 reader = new OsmServerHistoryReader(OsmPrimitiveType.from(primitive), primitive.id);
418 ds = reader.parseHistory(progressMonitor.createSubTaskMonitor(1, false));
419 } catch(OsmTransferException e) {
420 if (cancelled)
421 return;
422 throw e;
423 }
424 HistoryDataSet.getInstance().mergeInto(ds);
425 }
426 } catch(OsmTransferException e) {
427 lastException = e;
428 return;
429 }
430 }
431
432 public boolean isCancelled() {
433 return cancelled;
434 }
435
436 public Exception getLastException() {
437 return lastException;
438 }
439 }
440
441 /**
442 * The action for reloading history information of the currently selected primitives.
443 *
444 */
445 class ReloadAction extends AbstractAction implements SelectionChangedListener {
446 public ReloadAction() {
447 putValue(Action.SMALL_ICON, ImageProvider.get("dialogs","refresh"));
448 putValue(Action.SHORT_DESCRIPTION, tr("Reload all currently selected objects and refresh the list."));
449 }
450
451 public void actionPerformed(ActionEvent e) {
452 refresh();
453 }
454
455 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
456 setEnabled(Main.main.getCurrentDataSet().getSelected().size() > 0);
457
458 }
459 }
460
461 /**
462 * The action for showing history information of the current history item.
463 */
464 class ShowHistoryAction extends AbstractAction implements ListSelectionListener {
465 public ShowHistoryAction() {
466 //putValue(Action.SMALL_ICON, ImageProvider.get("dialogs","refresh"));
467 putValue(Action.NAME, tr("Show"));
468 putValue(Action.SHORT_DESCRIPTION, tr("Display the history of the selected primitive"));
469 }
470
471 public void actionPerformed(ActionEvent e) {
472 int row = historyTable.getSelectionModel().getMinSelectionIndex();
473 if (row < 0) return;
474 History h = model.get(row);
475 showHistory(h);
476 }
477
478 public void valueChanged(ListSelectionEvent e) {
479 setEnabled(historyTable.getSelectionModel().getMinSelectionIndex() >= 0);
480 }
481 }
482}
Note: See TracBrowser for help on using the repository browser.