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

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

Fixed some FindBugs warnings

File size: 22.6 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 ).apply(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 ChangesetQuery query = dialog.getChangesetQuery();
369 if (query == null) return;
370 ChangesetQueryTask task = new ChangesetQueryTask(ChangesetCacheManager.this, query);
371 ChangesetCacheManager.getInstance().runDownloadTask(task);
372 }
373 }
374
375 /**
376 * Removes the selected changesets from the local changeset cache
377 *
378 */
379 class RemoveFromCacheAction extends AbstractAction implements ListSelectionListener{
380 public RemoveFromCacheAction() {
381 putValue(NAME, tr("Remove from cache"));
382 putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
383 putValue(SHORT_DESCRIPTION, tr("Remove the selected changesets from the local cache"));
384 updateEnabledState();
385 }
386
387 public void actionPerformed(ActionEvent arg0) {
388 List<Changeset> selected = model.getSelectedChangesets();
389 ChangesetCache.getInstance().remove(selected);
390 }
391
392 protected void updateEnabledState() {
393 setEnabled(model.hasSelectedChangesets());
394 }
395
396 public void valueChanged(ListSelectionEvent e) {
397 updateEnabledState();
398
399 }
400 }
401
402 /**
403 * Closes the selected changesets
404 *
405 */
406 class CloseSelectedChangesetsAction extends AbstractAction implements ListSelectionListener{
407 public CloseSelectedChangesetsAction() {
408 putValue(NAME, tr("Close"));
409 putValue(SMALL_ICON, ImageProvider.get("closechangeset"));
410 putValue(SHORT_DESCRIPTION, tr("Close the selected changesets"));
411 updateEnabledState();
412 }
413
414 public void actionPerformed(ActionEvent arg0) {
415 List<Changeset> selected = model.getSelectedChangesets();
416 Main.worker.submit(new CloseChangesetTask(selected));
417 }
418
419 protected void updateEnabledState() {
420 List<Changeset> selected = model.getSelectedChangesets();
421 JosmUserIdentityManager im = JosmUserIdentityManager.getInstance();
422 for (Changeset cs: selected) {
423 if (cs.isOpen()) {
424 if (im.isPartiallyIdentified() && cs.getUser() != null && cs.getUser().getName().equals(im.getUserName())) {
425 setEnabled(true);
426 return;
427 }
428 if (im.isFullyIdentified() && cs.getUser() != null && cs.getUser().getId() == im.getUserId()) {
429 setEnabled(true);
430 return;
431 }
432 }
433 }
434 setEnabled(false);
435 }
436
437 public void valueChanged(ListSelectionEvent e) {
438 updateEnabledState();
439 }
440 }
441
442 /**
443 * Downloads the selected changesets
444 *
445 */
446 class DownloadSelectedChangesetsAction extends AbstractAction implements ListSelectionListener{
447 public DownloadSelectedChangesetsAction() {
448 putValue(NAME, tr("Update changeset"));
449 putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset", "updatechangeset"));
450 putValue(SHORT_DESCRIPTION, tr("Updates the selected changesets with current data from the OSM server"));
451 updateEnabledState();
452 }
453
454 public void actionPerformed(ActionEvent arg0) {
455 List<Changeset> selected = model.getSelectedChangesets();
456 ChangesetHeaderDownloadTask task =ChangesetHeaderDownloadTask.buildTaskForChangesets(ChangesetCacheManager.this,selected);
457 ChangesetCacheManager.getInstance().runDownloadTask(task);
458 }
459
460 protected void updateEnabledState() {
461 setEnabled(model.hasSelectedChangesets());
462 }
463
464 public void valueChanged(ListSelectionEvent e) {
465 updateEnabledState();
466 }
467 }
468
469 /**
470 * Downloads the content of selected changesets from the OSM server
471 *
472 */
473 class DownloadSelectedChangesetContentAction extends AbstractAction implements ListSelectionListener{
474 public DownloadSelectedChangesetContentAction() {
475 putValue(NAME, tr("Download changeset content"));
476 putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset", "downloadchangesetcontent"));
477 putValue(SHORT_DESCRIPTION, tr("Download the content of the selected changesets from the server"));
478 updateEnabledState();
479 }
480
481 public void actionPerformed(ActionEvent arg0) {
482 ChangesetContentDownloadTask task = new ChangesetContentDownloadTask(ChangesetCacheManager.this,model.getSelectedChangesetIds());
483 ChangesetCacheManager.getInstance().runDownloadTask(task);
484 }
485
486 protected void updateEnabledState() {
487 setEnabled(model.hasSelectedChangesets());
488 }
489
490 public void valueChanged(ListSelectionEvent e) {
491 updateEnabledState();
492 }
493 }
494
495 class ShowDetailAction extends AbstractAction {
496
497 public void showDetails() {
498 List<Changeset> selected = model.getSelectedChangesets();
499 if (selected.size() != 1) return;
500 model.setChangesetInDetailView(selected.get(0));
501 }
502
503 public void actionPerformed(ActionEvent arg0) {
504 showDetails();
505 }
506 }
507
508 class DownloadMyChangesets extends AbstractAction {
509 public DownloadMyChangesets() {
510 putValue(NAME, tr("My changesets"));
511 putValue(SMALL_ICON, ImageProvider.get("dialogs/changeset", "downloadchangeset"));
512 putValue(SHORT_DESCRIPTION, tr("Download my changesets from the OSM server (max. 100 changesets)"));
513 }
514
515 protected void alertAnonymousUser() {
516 HelpAwareOptionPane.showOptionDialog(
517 ChangesetCacheManager.this,
518 tr("<html>JOSM is currently running with an anonymous user. It can't download<br>"
519 + "your changesets from the OSM server unless you enter your OSM user name<br>"
520 + "in the JOSM preferences.</html>"
521 ),
522 tr("Warning"),
523 JOptionPane.WARNING_MESSAGE,
524 HelpUtil.ht("/Dialog/ChangesetCacheManager#CanDownloadMyChangesets")
525 );
526 }
527
528 public void actionPerformed(ActionEvent arg0) {
529 JosmUserIdentityManager im = JosmUserIdentityManager.getInstance();
530 im.initFromPreferences();
531 if (im.isAnonymous()) {
532 alertAnonymousUser();
533 return;
534 }
535 ChangesetQuery query = new ChangesetQuery();
536 if (im.isFullyIdentified()) {
537 query = query.forUser(im.getUserId());
538 } else {
539 query = query.forUser(im.getUserName());
540 }
541 ChangesetQueryTask task = new ChangesetQueryTask(ChangesetCacheManager.this, query);
542 ChangesetCacheManager.getInstance().runDownloadTask(task);
543 }
544 }
545
546 class DblClickHandler extends MouseAdapter {
547 @Override
548 public void mouseClicked(MouseEvent evt) {
549 if (! SwingUtilities.isLeftMouseButton(evt) || evt.getClickCount()<2)
550 return;
551 new ShowDetailAction().showDetails();
552 }
553 }
554
555 class ChangesetTablePopupMenuLauncher extends PopupMenuLauncher {
556 ChangesetTablePopupMenu menu = new ChangesetTablePopupMenu();
557 @Override
558 public void launch(MouseEvent evt) {
559 if (! model.hasSelectedChangesets()) {
560 int row = tblChangesets.rowAtPoint(evt.getPoint());
561 if (row >= 0) {
562 model.setSelectedByIdx(row);
563 }
564 }
565 menu.show(tblChangesets, evt.getPoint().x, evt.getPoint().y);
566 }
567 }
568
569 class ChangesetTablePopupMenu extends JPopupMenu {
570 public ChangesetTablePopupMenu() {
571 add(actRemoveFromCacheAction);
572 add(actCloseSelectedChangesetsAction);
573 add(actDownloadSelectedChangesets);
574 add(actDownloadSelectedContent);
575 }
576 }
577
578 class ChangesetDetailViewSynchronizer implements ListSelectionListener {
579 public void valueChanged(ListSelectionEvent e) {
580 List<Changeset> selected = model.getSelectedChangesets();
581 if (selected.size() == 1) {
582 model.setChangesetInDetailView(selected.get(0));
583 } else {
584 model.setChangesetInDetailView(null);
585 }
586 }
587 }
588
589 /**
590 * Selects the changesets in <code>changests</code>, provided the
591 * respective changesets are already present in the local changeset cache.
592 *
593 * @param ids the collection of changesets. If null, the selection is cleared.
594 */
595 public void setSelectedChangesets(Collection<Changeset> changesets) {
596 model.setSelectedChangesets(changesets);
597 int idx = model.getSelectionModel().getMinSelectionIndex();
598 if (idx < 0) return;
599 tblChangesets.scrollRectToVisible(tblChangesets.getCellRect(idx, 0, true));
600 repaint();
601 }
602
603 /**
604 * Selects the changesets with the ids in <code>ids</code>, provided the
605 * respective changesets are already present in the local changeset cache.
606 *
607 * @param ids the collection of ids. If null, the selection is cleared.
608 */
609 public void setSelectedChangesetsById(Collection<Integer> ids) {
610 if (ids == null) {
611 setSelectedChangesets(null);
612 return;
613 }
614 Set<Changeset> toSelect = new HashSet<Changeset>();
615 ChangesetCache cc = ChangesetCache.getInstance();
616 for (int id: ids) {
617 if (cc.contains(id)) {
618 toSelect.add(cc.get(id));
619 }
620 }
621 setSelectedChangesets(toSelect);
622 }
623
624 public void runDownloadTask(final ChangesetDownloadTask task) {
625 Main.worker.submit(task);
626 Runnable r = new Runnable() {
627 public void run() {
628 if (task.isCanceled() || task.isFailed()) return;
629 setSelectedChangesets(task.getDownloadedChangesets());
630 }
631 };
632 Main.worker.submit(r);
633 }
634}
Note: See TracBrowser for help on using the repository browser.