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

Last change on this file since 14602 was 14602, checked in by GerdP, 5 years ago

see #17040 fix typo in ChangesetCacheManager.destroyInstance() so that
ChangesetCacheManagerModel.tearDown() is called

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