1 | // License: GPL. Copyright 2007 by Immanuel Scholz and others
|
---|
2 | package org.openstreetmap.josm.gui.dialogs;
|
---|
3 |
|
---|
4 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
5 |
|
---|
6 | import java.awt.BorderLayout;
|
---|
7 | import java.awt.Color;
|
---|
8 | import java.awt.Component;
|
---|
9 | import java.awt.GridLayout;
|
---|
10 | import java.awt.event.ActionEvent;
|
---|
11 | import java.awt.event.KeyEvent;
|
---|
12 | import java.awt.event.MouseAdapter;
|
---|
13 | import java.awt.event.MouseEvent;
|
---|
14 | import java.io.IOException;
|
---|
15 | import java.util.ArrayList;
|
---|
16 | import java.util.Collection;
|
---|
17 | import java.util.HashMap;
|
---|
18 | import java.util.Iterator;
|
---|
19 |
|
---|
20 | import javax.swing.AbstractAction;
|
---|
21 | import javax.swing.Action;
|
---|
22 | import javax.swing.ImageIcon;
|
---|
23 | import javax.swing.JComponent;
|
---|
24 | import javax.swing.JLabel;
|
---|
25 | import javax.swing.JOptionPane;
|
---|
26 | import javax.swing.JPanel;
|
---|
27 | import javax.swing.JScrollPane;
|
---|
28 | import javax.swing.JTable;
|
---|
29 | import javax.swing.ListSelectionModel;
|
---|
30 | import javax.swing.event.ListSelectionEvent;
|
---|
31 | import javax.swing.event.ListSelectionListener;
|
---|
32 | import javax.swing.table.DefaultTableCellRenderer;
|
---|
33 | import javax.swing.table.DefaultTableColumnModel;
|
---|
34 | import javax.swing.table.DefaultTableModel;
|
---|
35 | import javax.swing.table.TableCellRenderer;
|
---|
36 | import javax.swing.table.TableColumn;
|
---|
37 |
|
---|
38 | import org.openstreetmap.josm.Main;
|
---|
39 | import org.openstreetmap.josm.data.SelectionChangedListener;
|
---|
40 | import org.openstreetmap.josm.data.osm.DataSet;
|
---|
41 | import org.openstreetmap.josm.data.osm.OsmPrimitive;
|
---|
42 | import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
|
---|
43 | import org.openstreetmap.josm.data.osm.history.History;
|
---|
44 | import org.openstreetmap.josm.data.osm.history.HistoryDataSet;
|
---|
45 | import org.openstreetmap.josm.gui.PleaseWaitRunnable;
|
---|
46 | import org.openstreetmap.josm.gui.SideButton;
|
---|
47 | import org.openstreetmap.josm.gui.history.HistoryBrowserDialog;
|
---|
48 | import org.openstreetmap.josm.io.OsmApi;
|
---|
49 | import org.openstreetmap.josm.io.OsmApiException;
|
---|
50 | import org.openstreetmap.josm.io.OsmServerHistoryReader;
|
---|
51 | import org.openstreetmap.josm.io.OsmTransferException;
|
---|
52 | import org.openstreetmap.josm.tools.ImageProvider;
|
---|
53 | import org.openstreetmap.josm.tools.Shortcut;
|
---|
54 | import 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 | */
|
---|
68 | public 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("&", "&").replaceAll(">", ">").replaceAll("<", "<")
|
---|
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 | }
|
---|