source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/ChangesetCacheManager.java@ 4864

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

see #7258 - IllegalStateException when attempting download in Changeset Management Dialog (error message has to be translated after stabilization)

  • Property svn:eol-style set to native
File size: 22.8 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs.changeset;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.BorderLayout;
7import java.awt.Container;
8import java.awt.Dimension;
9import java.awt.FlowLayout;
10import java.awt.event.ActionEvent;
11import java.awt.event.KeyEvent;
12import java.awt.event.MouseAdapter;
13import java.awt.event.MouseEvent;
14import java.awt.event.WindowAdapter;
15import java.awt.event.WindowEvent;
16import java.util.Collection;
17import java.util.HashSet;
18import java.util.List;
19import java.util.Set;
20
21import javax.swing.AbstractAction;
22import javax.swing.DefaultListSelectionModel;
23import javax.swing.JComponent;
24import javax.swing.JFrame;
25import javax.swing.JOptionPane;
26import javax.swing.JPanel;
27import javax.swing.JPopupMenu;
28import javax.swing.JScrollPane;
29import javax.swing.JSplitPane;
30import javax.swing.JTabbedPane;
31import javax.swing.JTable;
32import javax.swing.JToolBar;
33import javax.swing.KeyStroke;
34import javax.swing.ListSelectionModel;
35import javax.swing.SwingUtilities;
36import javax.swing.event.ListSelectionEvent;
37import javax.swing.event.ListSelectionListener;
38
39import org.openstreetmap.josm.Main;
40import org.openstreetmap.josm.data.osm.Changeset;
41import org.openstreetmap.josm.data.osm.ChangesetCache;
42import org.openstreetmap.josm.gui.HelpAwareOptionPane;
43import org.openstreetmap.josm.gui.JosmUserIdentityManager;
44import org.openstreetmap.josm.gui.SideButton;
45import org.openstreetmap.josm.gui.dialogs.changeset.query.ChangesetQueryDialog;
46import org.openstreetmap.josm.gui.dialogs.changeset.query.ChangesetQueryTask;
47import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
48import org.openstreetmap.josm.gui.help.HelpUtil;
49import org.openstreetmap.josm.gui.io.CloseChangesetTask;
50import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
51import org.openstreetmap.josm.io.ChangesetQuery;
52import org.openstreetmap.josm.tools.ImageProvider;
53import org.openstreetmap.josm.tools.WindowGeometry;
54
55/**
56 * ChangesetCacheManager manages the local cache of changesets
57 * retrieved from the OSM API. It displays both a table of the locally cached changesets
58 * and detail information about an individual changeset. It also provides actions for
59 * downloading, querying, closing changesets, in addition to removing changesets from
60 * the local cache.
61 *
62 */
63public class ChangesetCacheManager extends JFrame {
64
65 /** the unique instance of the cache manager */
66 private static ChangesetCacheManager instance;
67
68 /**
69 * Replies the unique instance of the changeset cache manager
70 *
71 * @return the unique instance of the changeset cache manager
72 */
73 public static ChangesetCacheManager getInstance() {
74 if (instance == null) {
75 instance = new ChangesetCacheManager();
76 }
77 return instance;
78 }
79
80 /**
81 * Hides and destroys the unique instance of the changeset cache
82 * manager.
83 *
84 */
85 public static void destroyInstance() {
86 if (instance != null) {
87 instance.setVisible(true);
88 instance.dispose();
89 instance = null;
90 }
91 }
92
93 private ChangesetCacheManagerModel model;
94 private JSplitPane spContent;
95 private boolean needsSplitPaneAdjustment;
96
97 private RemoveFromCacheAction actRemoveFromCacheAction;
98 private CloseSelectedChangesetsAction actCloseSelectedChangesetsAction;
99 private DownloadSelectedChangesetsAction actDownloadSelectedChangesets;
100 private DownloadSelectedChangesetContentAction actDownloadSelectedContent;
101 private JTable tblChangesets;
102
103 /**
104 * Creates the various models required
105 */
106 protected void buildModel() {
107 DefaultListSelectionModel selectionModel = new DefaultListSelectionModel();
108 selectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
109 model = new ChangesetCacheManagerModel(selectionModel);
110
111 actRemoveFromCacheAction = new RemoveFromCacheAction();
112 actCloseSelectedChangesetsAction = new CloseSelectedChangesetsAction();
113 actDownloadSelectedChangesets = new DownloadSelectedChangesetsAction();
114 actDownloadSelectedContent = new DownloadSelectedChangesetContentAction();
115 }
116
117 /**
118 * builds the toolbar panel in the heading of the dialog
119 *
120 * @return the toolbar panel
121 */
122 protected JPanel buildToolbarPanel() {
123 JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT));
124
125 SideButton btn = new SideButton(new QueryAction());
126 pnl.add(btn);
127 pnl.add(new SingleChangesetDownloadPanel());
128 pnl.add(new SideButton(new DownloadMyChangesets()));
129
130 return pnl;
131 }
132
133 /**
134 * builds the button panel in the footer of the dialog
135 *
136 * @return the button row pane
137 */
138 protected JPanel buildButtonPanel() {
139 JPanel pnl = new JPanel(new FlowLayout(FlowLayout.CENTER));
140
141 //-- cancel and close action
142 pnl.add(new SideButton(new CancelAction()));
143
144 //-- help action
145 pnl.add(new SideButton(
146 new ContextSensitiveHelpAction(
147 HelpUtil.ht("/Dialog/ChangesetCacheManager"))
148 )
149 );
150
151 return pnl;
152 }
153
154 /**
155 * Builds the panel with the changeset details
156 *
157 * @return the panel with the changeset details
158 */
159 protected JPanel buildChangesetDetailPanel() {
160 JPanel pnl = new JPanel(new BorderLayout());
161 JTabbedPane tp = new JTabbedPane();
162
163 // -- add the details panel
164 ChangesetDetailPanel pnlChangesetDetail;
165 tp.add(pnlChangesetDetail = new ChangesetDetailPanel());
166 model.addPropertyChangeListener(pnlChangesetDetail);
167
168 // -- add the tags panel
169 ChangesetTagsPanel pnlChangesetTags = new ChangesetTagsPanel();
170 tp.add(pnlChangesetTags);
171 model.addPropertyChangeListener(pnlChangesetTags);
172
173 // -- add the panel for the changeset content
174 ChangesetContentPanel pnlChangesetContent = new ChangesetContentPanel();
175 tp.add(pnlChangesetContent);
176 model.addPropertyChangeListener(pnlChangesetContent);
177
178 tp.setTitleAt(0, tr("Properties"));
179 tp.setToolTipTextAt(0, tr("Display the basic properties of the changeset"));
180 tp.setTitleAt(1, tr("Tags"));
181 tp.setToolTipTextAt(1, tr("Display the tags of the changeset"));
182 tp.setTitleAt(2, tr("Content"));
183 tp.setToolTipTextAt(2, tr("Display the objects created, updated, and deleted by the changeset"));
184
185 pnl.add(tp, BorderLayout.CENTER);
186 return pnl;
187 }
188
189 /**
190 * builds the content panel of the dialog
191 *
192 * @return the content panel
193 */
194 protected JPanel buildContentPanel() {
195 JPanel pnl = new JPanel(new BorderLayout());
196
197 spContent = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
198 spContent.setLeftComponent(buildChangesetTablePanel());
199 spContent.setRightComponent(buildChangesetDetailPanel());
200 spContent.setOneTouchExpandable(true);
201 spContent.setDividerLocation(0.5);
202
203 pnl.add(spContent, BorderLayout.CENTER);
204 return pnl;
205 }
206
207 /**
208 * Builds the table with actions which can be applied to the currently visible changesets
209 * in the changeset table.
210 *
211 * @return
212 */
213 protected JPanel buildChangesetTableActionPanel() {
214 JPanel pnl = new JPanel(new BorderLayout());
215
216 JToolBar tb = new JToolBar(JToolBar.VERTICAL);
217 tb.setFloatable(false);
218
219 // -- remove from cache action
220 model.getSelectionModel().addListSelectionListener(actRemoveFromCacheAction);
221 tb.add(actRemoveFromCacheAction);
222
223 // -- close selected changesets action
224 model.getSelectionModel().addListSelectionListener(actCloseSelectedChangesetsAction);
225 tb.add(actCloseSelectedChangesetsAction);
226
227 // -- download selected changesets
228 model.getSelectionModel().addListSelectionListener(actDownloadSelectedChangesets);
229 tb.add(actDownloadSelectedChangesets);
230
231 // -- download the content of the selected changesets
232 model.getSelectionModel().addListSelectionListener(actDownloadSelectedContent);
233 tb.add(actDownloadSelectedContent);
234
235 pnl.add(tb, BorderLayout.CENTER);
236 return pnl;
237 }
238
239 /**
240 * Builds the panel with the table of changesets
241 *
242 * @return the panel with the table of changesets
243 */
244 protected JPanel buildChangesetTablePanel() {
245 JPanel pnl = new JPanel(new BorderLayout());
246 tblChangesets = new JTable(
247 model,
248 new ChangesetCacheTableColumnModel(),
249 model.getSelectionModel()
250 );
251 tblChangesets.addMouseListener(new ChangesetTablePopupMenuLauncher());
252 tblChangesets.addMouseListener(new DblClickHandler());
253 tblChangesets.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0), "showDetails");
254 tblChangesets.getActionMap().put("showDetails", new ShowDetailAction());
255 model.getSelectionModel().addListSelectionListener(new ChangesetDetailViewSynchronizer());
256
257 // activate DEL on the table
258 tblChangesets.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE,0), "removeFromCache");
259 tblChangesets.getActionMap().put("removeFromCache", actRemoveFromCacheAction);
260
261 pnl.add(new JScrollPane(tblChangesets), BorderLayout.CENTER);
262 pnl.add(buildChangesetTableActionPanel(), BorderLayout.WEST);
263 return pnl;
264 }
265
266 protected void build() {
267 setTitle(tr("Changeset Management Dialog"));
268 setIconImage(ImageProvider.get("dialogs/changeset", "changesetmanager").getImage());
269 Container cp = getContentPane();
270
271 cp.setLayout(new BorderLayout());
272
273 buildModel();
274 cp.add(buildToolbarPanel(), BorderLayout.NORTH);
275 cp.add(buildContentPanel(), BorderLayout.CENTER);
276 cp.add(buildButtonPanel(), BorderLayout.SOUTH);
277
278 // the help context
279 HelpUtil.setHelpContext(getRootPane(), HelpUtil.ht("/Dialog/ChangesetCacheManager"));
280
281 // make the dialog respond to ESC
282 getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE,0), "cancelAndClose");
283 getRootPane().getActionMap().put("cancelAndClose", new CancelAction());
284
285 // install a window event handler
286 addWindowListener(new WindowEventHandler());
287 }
288
289 public ChangesetCacheManager() {
290 build();
291 }
292
293 @Override
294 public void setVisible(boolean visible) {
295 if (visible) {
296 new WindowGeometry(
297 getClass().getName() + ".geometry",
298 WindowGeometry.centerInWindow(
299 getParent(),
300 new Dimension(1000,600)
301 )
302 ).applySafe(this);
303 needsSplitPaneAdjustment = true;
304 model.init();
305
306 } else if (!visible && isShowing()){
307 model.tearDown();
308 new WindowGeometry(this).remember(getClass().getName() + ".geometry");
309 }
310 super.setVisible(visible);
311 }
312
313 /**
314 * Handler for window events
315 *
316 */
317 class WindowEventHandler extends WindowAdapter {
318 @Override
319 public void windowClosing(WindowEvent e) {
320 new CancelAction().cancelAndClose();
321 }
322
323 @Override
324 public void windowActivated(WindowEvent arg0) {
325 if (needsSplitPaneAdjustment) {
326 spContent.setDividerLocation(0.5);
327 needsSplitPaneAdjustment = false;
328 }
329 }
330 }
331
332 /**
333 * the cancel / close action
334 */
335 static class CancelAction extends AbstractAction {
336 public CancelAction() {
337 putValue(NAME, tr("Close"));
338 putValue(SMALL_ICON, ImageProvider.get("cancel"));
339 putValue(SHORT_DESCRIPTION, tr("Close the dialog"));
340 }
341
342 public void cancelAndClose() {
343 destroyInstance();
344 }
345
346 public void actionPerformed(ActionEvent arg0) {
347 cancelAndClose();
348 }
349 }
350
351 /**
352 * The action to query and download changesets
353 */
354 class QueryAction extends AbstractAction {
355 public QueryAction() {
356 putValue(NAME, tr("Query"));
357 putValue(SMALL_ICON, ImageProvider.get("dialogs","search"));
358 putValue(SHORT_DESCRIPTION, tr("Launch the dialog for querying changesets"));
359 }
360
361 public void actionPerformed(ActionEvent evt) {
362 ChangesetQueryDialog dialog = new ChangesetQueryDialog(ChangesetCacheManager.this);
363 dialog.initForUserInput();
364 dialog.setVisible(true);
365 if (dialog.isCanceled())
366 return;
367
368 try {
369 ChangesetQuery query = dialog.getChangesetQuery();
370 if (query == null) return;
371 ChangesetQueryTask task = new ChangesetQueryTask(ChangesetCacheManager.this, query);
372 ChangesetCacheManager.getInstance().runDownloadTask(task);
373 } catch (IllegalStateException e) {
374 JOptionPane.showMessageDialog(ChangesetCacheManager.this, e.getMessage(), tr("Error"), JOptionPane.ERROR_MESSAGE);
375 }
376 }
377 }
378
379 /**
380 * Removes the selected changesets from the local changeset cache
381 *
382 */
383 class RemoveFromCacheAction extends AbstractAction implements ListSelectionListener{
384 public RemoveFromCacheAction() {
385 putValue(NAME, tr("Remove from cache"));
386 putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
387 putValue(SHORT_DESCRIPTION, tr("Remove the selected changesets from the local cache"));
388 updateEnabledState();
389 }
390
391 public void actionPerformed(ActionEvent arg0) {
392 List<Changeset> selected = model.getSelectedChangesets();
393 ChangesetCache.getInstance().remove(selected);
394 }
395
396 protected void updateEnabledState() {
397 setEnabled(model.hasSelectedChangesets());
398 }
399
400 public void valueChanged(ListSelectionEvent e) {
401 updateEnabledState();
402
403 }
404 }
405
406 /**
407 * Closes the selected changesets
408 *
409 */
410 class CloseSelectedChangesetsAction extends AbstractAction implements ListSelectionListener{
411 public CloseSelectedChangesetsAction() {
412 putValue(NAME, tr("Close"));
413 putValue(SMALL_ICON, ImageProvider.get("closechangeset"));
414 putValue(SHORT_DESCRIPTION, tr("Close the selected changesets"));
415 updateEnabledState();
416 }
417
418 public void actionPerformed(ActionEvent arg0) {
419 List<Changeset> selected = model.getSelectedChangesets();
420 Main.worker.submit(new CloseChangesetTask(selected));
421 }
422
423 protected void updateEnabledState() {
424 List<Changeset> selected = model.getSelectedChangesets();
425 JosmUserIdentityManager im = JosmUserIdentityManager.getInstance();
426 for (Changeset cs: selected) {
427 if (cs.isOpen()) {
428 if (im.isPartiallyIdentified() && cs.getUser() != null && cs.getUser().getName().equals(im.getUserName())) {
429 setEnabled(true);
430 return;
431 }
432 if (im.isFullyIdentified() && cs.getUser() != null && cs.getUser().getId() == im.getUserId()) {
433 setEnabled(true);
434 return;
435 }
436 }
437 }
438 setEnabled(false);
439 }
440
441 public void valueChanged(ListSelectionEvent e) {
442 updateEnabledState();
443 }
444 }
445
446 /**
447 * Downloads the selected changesets
448 *
449 */
450 class DownloadSelectedChangesetsAction extends AbstractAction implements ListSelectionListener{
451 public DownloadSelectedChangesetsAction() {
452 putValue(NAME, tr("Update changeset"));
453 putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset", "updatechangeset"));
454 putValue(SHORT_DESCRIPTION, tr("Updates the selected changesets with current data from the OSM server"));
455 updateEnabledState();
456 }
457
458 public void actionPerformed(ActionEvent arg0) {
459 List<Changeset> selected = model.getSelectedChangesets();
460 ChangesetHeaderDownloadTask task =ChangesetHeaderDownloadTask.buildTaskForChangesets(ChangesetCacheManager.this,selected);
461 ChangesetCacheManager.getInstance().runDownloadTask(task);
462 }
463
464 protected void updateEnabledState() {
465 setEnabled(model.hasSelectedChangesets());
466 }
467
468 public void valueChanged(ListSelectionEvent e) {
469 updateEnabledState();
470 }
471 }
472
473 /**
474 * Downloads the content of selected changesets from the OSM server
475 *
476 */
477 class DownloadSelectedChangesetContentAction extends AbstractAction implements ListSelectionListener{
478 public DownloadSelectedChangesetContentAction() {
479 putValue(NAME, tr("Download changeset content"));
480 putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset", "downloadchangesetcontent"));
481 putValue(SHORT_DESCRIPTION, tr("Download the content of the selected changesets from the server"));
482 updateEnabledState();
483 }
484
485 public void actionPerformed(ActionEvent arg0) {
486 ChangesetContentDownloadTask task = new ChangesetContentDownloadTask(ChangesetCacheManager.this,model.getSelectedChangesetIds());
487 ChangesetCacheManager.getInstance().runDownloadTask(task);
488 }
489
490 protected void updateEnabledState() {
491 setEnabled(model.hasSelectedChangesets());
492 }
493
494 public void valueChanged(ListSelectionEvent e) {
495 updateEnabledState();
496 }
497 }
498
499 class ShowDetailAction extends AbstractAction {
500
501 public void showDetails() {
502 List<Changeset> selected = model.getSelectedChangesets();
503 if (selected.size() != 1) return;
504 model.setChangesetInDetailView(selected.get(0));
505 }
506
507 public void actionPerformed(ActionEvent arg0) {
508 showDetails();
509 }
510 }
511
512 class DownloadMyChangesets extends AbstractAction {
513 public DownloadMyChangesets() {
514 putValue(NAME, tr("My changesets"));
515 putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset", "downloadchangeset"));
516 putValue(SHORT_DESCRIPTION, tr("Download my changesets from the OSM server (max. 100 changesets)"));
517 }
518
519 protected void alertAnonymousUser() {
520 HelpAwareOptionPane.showOptionDialog(
521 ChangesetCacheManager.this,
522 tr("<html>JOSM is currently running with an anonymous user. It cannot download<br>"
523 + "your changesets from the OSM server unless you enter your OSM user name<br>"
524 + "in the JOSM preferences.</html>"
525 ),
526 tr("Warning"),
527 JOptionPane.WARNING_MESSAGE,
528 HelpUtil.ht("/Dialog/ChangesetCacheManager#CanDownloadMyChangesets")
529 );
530 }
531
532 public void actionPerformed(ActionEvent arg0) {
533 JosmUserIdentityManager im = JosmUserIdentityManager.getInstance();
534 im.initFromPreferences();
535 if (im.isAnonymous()) {
536 alertAnonymousUser();
537 return;
538 }
539 ChangesetQuery query = new ChangesetQuery();
540 if (im.isFullyIdentified()) {
541 query = query.forUser(im.getUserId());
542 } else {
543 query = query.forUser(im.getUserName());
544 }
545 ChangesetQueryTask task = new ChangesetQueryTask(ChangesetCacheManager.this, query);
546 ChangesetCacheManager.getInstance().runDownloadTask(task);
547 }
548 }
549
550 class DblClickHandler extends MouseAdapter {
551 @Override
552 public void mouseClicked(MouseEvent evt) {
553 if (! SwingUtilities.isLeftMouseButton(evt) || evt.getClickCount()<2)
554 return;
555 new ShowDetailAction().showDetails();
556 }
557 }
558
559 class ChangesetTablePopupMenuLauncher extends PopupMenuLauncher {
560 ChangesetTablePopupMenu menu = new ChangesetTablePopupMenu();
561 @Override
562 public void launch(MouseEvent evt) {
563 if (! model.hasSelectedChangesets()) {
564 int row = tblChangesets.rowAtPoint(evt.getPoint());
565 if (row >= 0) {
566 model.setSelectedByIdx(row);
567 }
568 }
569 menu.show(tblChangesets, evt.getPoint().x, evt.getPoint().y);
570 }
571 }
572
573 class ChangesetTablePopupMenu extends JPopupMenu {
574 public ChangesetTablePopupMenu() {
575 add(actRemoveFromCacheAction);
576 add(actCloseSelectedChangesetsAction);
577 add(actDownloadSelectedChangesets);
578 add(actDownloadSelectedContent);
579 }
580 }
581
582 class ChangesetDetailViewSynchronizer implements ListSelectionListener {
583 public void valueChanged(ListSelectionEvent e) {
584 List<Changeset> selected = model.getSelectedChangesets();
585 if (selected.size() == 1) {
586 model.setChangesetInDetailView(selected.get(0));
587 } else {
588 model.setChangesetInDetailView(null);
589 }
590 }
591 }
592
593 /**
594 * Selects the changesets in <code>changests</code>, provided the
595 * respective changesets are already present in the local changeset cache.
596 *
597 * @param ids the collection of changesets. If null, the selection is cleared.
598 */
599 public void setSelectedChangesets(Collection<Changeset> changesets) {
600 model.setSelectedChangesets(changesets);
601 int idx = model.getSelectionModel().getMinSelectionIndex();
602 if (idx < 0) return;
603 tblChangesets.scrollRectToVisible(tblChangesets.getCellRect(idx, 0, true));
604 repaint();
605 }
606
607 /**
608 * Selects the changesets with the ids in <code>ids</code>, provided the
609 * respective changesets are already present in the local changeset cache.
610 *
611 * @param ids the collection of ids. If null, the selection is cleared.
612 */
613 public void setSelectedChangesetsById(Collection<Integer> ids) {
614 if (ids == null) {
615 setSelectedChangesets(null);
616 return;
617 }
618 Set<Changeset> toSelect = new HashSet<Changeset>();
619 ChangesetCache cc = ChangesetCache.getInstance();
620 for (int id: ids) {
621 if (cc.contains(id)) {
622 toSelect.add(cc.get(id));
623 }
624 }
625 setSelectedChangesets(toSelect);
626 }
627
628 public void runDownloadTask(final ChangesetDownloadTask task) {
629 Main.worker.submit(task);
630 Runnable r = new Runnable() {
631 public void run() {
632 if (task.isCanceled() || task.isFailed()) return;
633 setSelectedChangesets(task.getDownloadedChangesets());
634 }
635 };
636 Main.worker.submit(r);
637 }
638}
Note: See TracBrowser for help on using the repository browser.