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

Last change on this file since 6340 was 6070, checked in by stoecker, 11 years ago

see #8853 remove tabs, trailing spaces, windows line ends, strange characters

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