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

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

fix #16943 - ChangesetCacheManagerTest: fix for non-headless mode (patch by ris)

  • Property svn:eol-style set to native
File size: 27.8 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs.changeset;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.BorderLayout;
7import java.awt.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.ImageProvider;
68import org.openstreetmap.josm.tools.InputMapUtils;
69import org.openstreetmap.josm.tools.Logging;
70import org.openstreetmap.josm.tools.StreamUtils;
71
72/**
73 * ChangesetCacheManager manages the local cache of changesets
74 * retrieved from the OSM API. It displays both a table of the locally cached changesets
75 * and detail information about an individual changeset. It also provides actions for
76 * downloading, querying, closing changesets, in addition to removing changesets from
77 * the local cache.
78 * @since 2689
79 */
80public class ChangesetCacheManager extends JFrame {
81
82 /** the unique instance of the cache manager */
83 private static volatile ChangesetCacheManager instance;
84 private JTabbedPane pnlChangesetDetailTabs;
85
86 /**
87 * Replies the unique instance of the changeset cache manager
88 *
89 * @return the unique instance of the changeset cache manager
90 */
91 public static ChangesetCacheManager getInstance() {
92 if (instance == null) {
93 instance = new ChangesetCacheManager();
94 }
95 return instance;
96 }
97
98 /**
99 * Hides and destroys the unique instance of the changeset cache manager.
100 *
101 */
102 public static void destroyInstance() {
103 if (instance != null) {
104 instance.setVisible(true);
105 instance.dispose();
106 instance = null;
107 }
108 }
109
110 private ChangesetCacheManagerModel model;
111 private JSplitPane spContent;
112 private boolean needsSplitPaneAdjustment;
113
114 private RemoveFromCacheAction actRemoveFromCacheAction;
115 private CloseSelectedChangesetsAction actCloseSelectedChangesetsAction;
116 private DownloadSelectedChangesetsAction actDownloadSelectedChangesets;
117 private DownloadSelectedChangesetContentAction actDownloadSelectedContent;
118 private DownloadSelectedChangesetObjectsAction actDownloadSelectedChangesetObjects;
119 private JTable tblChangesets;
120
121 /**
122 * Creates the various models required.
123 * @return the changeset cache model
124 */
125 static ChangesetCacheManagerModel buildModel() {
126 DefaultListSelectionModel selectionModel = new DefaultListSelectionModel();
127 selectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
128 return new ChangesetCacheManagerModel(selectionModel);
129 }
130
131 /**
132 * builds the toolbar panel in the heading of the dialog
133 *
134 * @return the toolbar panel
135 */
136 static JPanel buildToolbarPanel() {
137 JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT));
138
139 JButton btn = new JButton(new QueryAction());
140 pnl.add(btn);
141 pnl.add(new SingleChangesetDownloadPanel());
142 pnl.add(new JButton(new DownloadMyChangesets()));
143
144 return pnl;
145 }
146
147 /**
148 * builds the button panel in the footer of the dialog
149 *
150 * @return the button row pane
151 */
152 static JPanel buildButtonPanel() {
153 JPanel pnl = new JPanel(new FlowLayout(FlowLayout.CENTER));
154
155 //-- cancel and close action
156 pnl.add(new JButton(new CancelAction()));
157
158 //-- help action
159 pnl.add(new JButton(new ContextSensitiveHelpAction(HelpUtil.ht("/Dialog/ChangesetManager"))));
160
161 return pnl;
162 }
163
164 /**
165 * Builds the panel with the changeset details
166 *
167 * @return the panel with the changeset details
168 */
169 protected JPanel buildChangesetDetailPanel() {
170 JPanel pnl = new JPanel(new BorderLayout());
171 JTabbedPane tp = new JTabbedPane();
172 pnlChangesetDetailTabs = tp;
173
174 // -- add the details panel
175 ChangesetDetailPanel pnlChangesetDetail = new ChangesetDetailPanel();
176 tp.add(pnlChangesetDetail);
177 model.addPropertyChangeListener(pnlChangesetDetail);
178
179 // -- add the tags panel
180 ChangesetTagsPanel pnlChangesetTags = new ChangesetTagsPanel();
181 tp.add(pnlChangesetTags);
182 model.addPropertyChangeListener(pnlChangesetTags);
183
184 // -- add the panel for the changeset content
185 ChangesetContentPanel pnlChangesetContent = new ChangesetContentPanel();
186 tp.add(pnlChangesetContent);
187 model.addPropertyChangeListener(pnlChangesetContent);
188
189 // -- add the panel for the changeset discussion
190 ChangesetDiscussionPanel pnlChangesetDiscussion = new ChangesetDiscussionPanel();
191 tp.add(pnlChangesetDiscussion);
192 model.addPropertyChangeListener(pnlChangesetDiscussion);
193
194 tp.setTitleAt(0, tr("Properties"));
195 tp.setToolTipTextAt(0, tr("Display the basic properties of the changeset"));
196 tp.setTitleAt(1, tr("Tags"));
197 tp.setToolTipTextAt(1, tr("Display the tags of the changeset"));
198 tp.setTitleAt(2, tr("Content"));
199 tp.setToolTipTextAt(2, tr("Display the objects created, updated, and deleted by the changeset"));
200 tp.setTitleAt(3, tr("Discussion"));
201 tp.setToolTipTextAt(3, tr("Display the public discussion around this changeset"));
202
203 pnl.add(tp, BorderLayout.CENTER);
204 return pnl;
205 }
206
207 /**
208 * builds the content panel of the dialog
209 *
210 * @return the content panel
211 */
212 protected JPanel buildContentPanel() {
213 JPanel pnl = new JPanel(new BorderLayout());
214
215 spContent = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
216 spContent.setLeftComponent(buildChangesetTablePanel());
217 spContent.setRightComponent(buildChangesetDetailPanel());
218 spContent.setOneTouchExpandable(true);
219 spContent.setDividerLocation(0.5);
220
221 pnl.add(spContent, BorderLayout.CENTER);
222 return pnl;
223 }
224
225 /**
226 * Builds the table with actions which can be applied to the currently visible changesets
227 * in the changeset table.
228 *
229 * @return changset actions panel
230 */
231 protected JPanel buildChangesetTableActionPanel() {
232 JPanel pnl = new JPanel(new BorderLayout());
233
234 JToolBar tb = new JToolBar(JToolBar.VERTICAL);
235 tb.setFloatable(false);
236
237 // -- remove from cache action
238 model.getSelectionModel().addListSelectionListener(actRemoveFromCacheAction);
239 tb.add(actRemoveFromCacheAction);
240
241 // -- close selected changesets action
242 model.getSelectionModel().addListSelectionListener(actCloseSelectedChangesetsAction);
243 tb.add(actCloseSelectedChangesetsAction);
244
245 // -- download selected changesets
246 model.getSelectionModel().addListSelectionListener(actDownloadSelectedChangesets);
247 tb.add(actDownloadSelectedChangesets);
248
249 // -- download the content of the selected changesets
250 model.getSelectionModel().addListSelectionListener(actDownloadSelectedContent);
251 tb.add(actDownloadSelectedContent);
252
253 // -- download the objects contained in the selected changesets from the OSM server
254 model.getSelectionModel().addListSelectionListener(actDownloadSelectedChangesetObjects);
255 tb.add(actDownloadSelectedChangesetObjects);
256
257 pnl.add(tb, BorderLayout.CENTER);
258 return pnl;
259 }
260
261 /**
262 * Builds the panel with the table of changesets
263 *
264 * @return the panel with the table of changesets
265 */
266 protected JPanel buildChangesetTablePanel() {
267 JPanel pnl = new JPanel(new BorderLayout());
268 tblChangesets = new JTable(
269 model,
270 new ChangesetCacheTableColumnModel(),
271 model.getSelectionModel()
272 );
273 tblChangesets.addMouseListener(new MouseEventHandler());
274 InputMapUtils.addEnterAction(tblChangesets, new ShowDetailAction(model));
275 model.getSelectionModel().addListSelectionListener(new ChangesetDetailViewSynchronizer(model));
276
277 // activate DEL on the table
278 tblChangesets.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "removeFromCache");
279 tblChangesets.getActionMap().put("removeFromCache", actRemoveFromCacheAction);
280
281 pnl.add(new JScrollPane(tblChangesets), BorderLayout.CENTER);
282 pnl.add(buildChangesetTableActionPanel(), BorderLayout.WEST);
283 return pnl;
284 }
285
286 protected void build() {
287 setTitle(tr("Changeset Management Dialog"));
288 setIconImage(ImageProvider.get("dialogs/changeset", "changesetmanager").getImage());
289 Container cp = getContentPane();
290
291 cp.setLayout(new BorderLayout());
292
293 model = buildModel();
294 actRemoveFromCacheAction = new RemoveFromCacheAction(model);
295 actCloseSelectedChangesetsAction = new CloseSelectedChangesetsAction(model);
296 actDownloadSelectedChangesets = new DownloadSelectedChangesetsAction(model);
297 actDownloadSelectedContent = new DownloadSelectedChangesetContentAction(model);
298 actDownloadSelectedChangesetObjects = new DownloadSelectedChangesetObjectsAction();
299
300 cp.add(buildToolbarPanel(), BorderLayout.NORTH);
301 cp.add(buildContentPanel(), BorderLayout.CENTER);
302 cp.add(buildButtonPanel(), BorderLayout.SOUTH);
303
304 // the help context
305 HelpUtil.setHelpContext(getRootPane(), HelpUtil.ht("/Dialog/ChangesetManager"));
306
307 // make the dialog respond to ESC
308 InputMapUtils.addEscapeAction(getRootPane(), new CancelAction());
309
310 // install a window event handler
311 addWindowListener(new WindowEventHandler());
312 }
313
314 /**
315 * Constructs a new {@code ChangesetCacheManager}.
316 */
317 public ChangesetCacheManager() {
318 build();
319 }
320
321 @Override
322 public void setVisible(boolean visible) {
323 if (visible) {
324 new WindowGeometry(
325 getClass().getName() + ".geometry",
326 WindowGeometry.centerInWindow(
327 getParent(),
328 new Dimension(1000, 600)
329 )
330 ).applySafe(this);
331 needsSplitPaneAdjustment = true;
332 model.init();
333
334 } else if (isShowing()) { // Avoid IllegalComponentStateException like in #8775
335 model.tearDown();
336 new WindowGeometry(this).remember(getClass().getName() + ".geometry");
337 }
338 super.setVisible(visible);
339 }
340
341 /**
342 * Handler for window events
343 *
344 */
345 class WindowEventHandler extends WindowAdapter {
346 @Override
347 public void windowClosing(WindowEvent e) {
348 new CancelAction().cancelAndClose();
349 }
350
351 @Override
352 public void windowActivated(WindowEvent e) {
353 if (needsSplitPaneAdjustment) {
354 spContent.setDividerLocation(0.5);
355 needsSplitPaneAdjustment = false;
356 }
357 }
358 }
359
360 /**
361 * the cancel / close action
362 */
363 static class CancelAction extends AbstractAction {
364 CancelAction() {
365 putValue(NAME, tr("Close"));
366 new ImageProvider("cancel").getResource().attachImageIcon(this);
367 putValue(SHORT_DESCRIPTION, tr("Close the dialog"));
368 }
369
370 public void cancelAndClose() {
371 destroyInstance();
372 }
373
374 @Override
375 public void actionPerformed(ActionEvent e) {
376 cancelAndClose();
377 }
378 }
379
380 /**
381 * The action to query and download changesets
382 */
383 static class QueryAction extends AbstractAction {
384
385 QueryAction() {
386 putValue(NAME, tr("Query"));
387 new ImageProvider("dialogs", "search").getResource().attachImageIcon(this);
388 putValue(SHORT_DESCRIPTION, tr("Launch the dialog for querying changesets"));
389 setEnabled(!NetworkManager.isOffline(OnlineResource.OSM_API));
390 }
391
392 @Override
393 public void actionPerformed(ActionEvent evt) {
394 Window parent = GuiHelper.getWindowAncestorFor(evt);
395 ChangesetQueryDialog dialog = new ChangesetQueryDialog(parent);
396 dialog.initForUserInput();
397 dialog.setVisible(true);
398 if (dialog.isCanceled())
399 return;
400
401 try {
402 ChangesetQuery query = dialog.getChangesetQuery();
403 if (query != null) {
404 ChangesetCacheManager.getInstance().runDownloadTask(new ChangesetQueryTask(parent, query));
405 }
406 } catch (IllegalStateException e) {
407 Logging.error(e);
408 JOptionPane.showMessageDialog(parent, e.getMessage(), tr("Error"), JOptionPane.ERROR_MESSAGE);
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 MainApplication.worker.submit(new CloseChangesetTask(model.getSelectedChangesets()));
461 }
462
463 protected void updateEnabledState() {
464 List<Changeset> selected = model.getSelectedChangesets();
465 UserIdentityManager im = UserIdentityManager.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() && !NetworkManager.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() && !NetworkManager.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() {
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 MainApplication.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() && !NetworkManager.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(!NetworkManager.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 try {
638 ChangesetQuery query = ChangesetQuery.forCurrentUser();
639 if (!GraphicsEnvironment.isHeadless()) {
640 ChangesetCacheManager.getInstance().runDownloadTask(new ChangesetQueryTask(parent, query));
641 }
642 } catch (IllegalStateException ex) {
643 alertAnonymousUser(parent);
644 Logging.trace(ex);
645 }
646 }
647 }
648
649 class MouseEventHandler extends PopupMenuLauncher {
650
651 MouseEventHandler() {
652 super(new ChangesetTablePopupMenu());
653 }
654
655 @Override
656 public void mouseClicked(MouseEvent evt) {
657 if (isDoubleClick(evt)) {
658 new ShowDetailAction(model).showDetails();
659 }
660 }
661 }
662
663 class ChangesetTablePopupMenu extends JPopupMenu {
664 ChangesetTablePopupMenu() {
665 add(actRemoveFromCacheAction);
666 add(actCloseSelectedChangesetsAction);
667 add(actDownloadSelectedChangesets);
668 add(actDownloadSelectedContent);
669 add(actDownloadSelectedChangesetObjects);
670 }
671 }
672
673 static class ChangesetDetailViewSynchronizer implements ListSelectionListener {
674 private final ChangesetCacheManagerModel model;
675
676 ChangesetDetailViewSynchronizer(ChangesetCacheManagerModel model) {
677 this.model = model;
678 }
679
680 @Override
681 public void valueChanged(ListSelectionEvent e) {
682 List<Changeset> selected = model.getSelectedChangesets();
683 if (selected.size() == 1) {
684 model.setChangesetInDetailView(selected.get(0));
685 } else {
686 model.setChangesetInDetailView(null);
687 }
688 }
689 }
690
691 /**
692 * Returns the changeset cache model.
693 * @return the changeset cache model
694 * @since 12495
695 */
696 public ChangesetCacheManagerModel getModel() {
697 return model;
698 }
699
700 /**
701 * Selects the changesets in <code>changests</code>, provided the
702 * respective changesets are already present in the local changeset cache.
703 *
704 * @param changesets the collection of changesets. If {@code null}, the
705 * selection is cleared.
706 */
707 public void setSelectedChangesets(Collection<Changeset> changesets) {
708 model.setSelectedChangesets(changesets);
709 final int idx = model.getSelectionModel().getMinSelectionIndex();
710 if (idx < 0)
711 return;
712 GuiHelper.runInEDTAndWait(() -> tblChangesets.scrollRectToVisible(tblChangesets.getCellRect(idx, 0, true)));
713 repaint();
714 }
715
716 /**
717 * Selects the changesets with the ids in <code>ids</code>, provided the
718 * respective changesets are already present in the local changeset cache.
719 *
720 * @param ids the collection of ids. If null, the selection is cleared.
721 */
722 public void setSelectedChangesetsById(Collection<Integer> ids) {
723 if (ids == null) {
724 setSelectedChangesets(null);
725 return;
726 }
727 Set<Changeset> toSelect = new HashSet<>();
728 ChangesetCache cc = ChangesetCache.getInstance();
729 for (int id: ids) {
730 if (cc.contains(id)) {
731 toSelect.add(cc.get(id));
732 }
733 }
734 setSelectedChangesets(toSelect);
735 }
736
737 /**
738 * Selects the given component in the detail tabbed panel
739 * @param clazz the class of the component to select
740 */
741 public void setSelectedComponentInDetailPanel(Class<? extends JComponent> clazz) {
742 for (Component component : pnlChangesetDetailTabs.getComponents()) {
743 if (component.getClass().equals(clazz)) {
744 pnlChangesetDetailTabs.setSelectedComponent(component);
745 break;
746 }
747 }
748 }
749
750 /**
751 * Runs the given changeset download task.
752 * @param task The changeset download task to run
753 */
754 public void runDownloadTask(final AbstractChangesetDownloadTask task) {
755 MainApplication.worker.submit(new PostDownloadHandler(task, task.download()));
756 MainApplication.worker.submit(() -> {
757 if (task.isCanceled() || task.isFailed())
758 return;
759 GuiHelper.runInEDT(() -> setSelectedChangesets(task.getDownloadedData()));
760 });
761 }
762}
Note: See TracBrowser for help on using the repository browser.