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

Last change on this file since 11091 was 11091, checked in by simon04, 8 years ago

fix #13745 - Changeset manager: allow to download objects from a changeset

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