source: osm/applications/editors/josm/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/gui/ModulePreference.java@ 34675

Last change on this file since 34675 was 34675, checked in by donvip, 6 years ago

fix EDT violation, use scaled icons

File size: 21.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.plugins.opendata.core.gui;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trn;
6
7import java.awt.BorderLayout;
8import java.awt.Component;
9import java.awt.GridBagConstraints;
10import java.awt.GridBagLayout;
11import java.awt.GridLayout;
12import java.awt.Insets;
13import java.awt.event.ActionEvent;
14import java.awt.event.ComponentAdapter;
15import java.awt.event.ComponentEvent;
16import java.util.ArrayList;
17import java.util.Collection;
18import java.util.Collections;
19import java.util.Iterator;
20import java.util.LinkedList;
21import java.util.List;
22
23import javax.swing.AbstractAction;
24import javax.swing.BorderFactory;
25import javax.swing.DefaultListModel;
26import javax.swing.JButton;
27import javax.swing.JLabel;
28import javax.swing.JList;
29import javax.swing.JOptionPane;
30import javax.swing.JPanel;
31import javax.swing.JScrollPane;
32import javax.swing.JTabbedPane;
33import javax.swing.JTextField;
34import javax.swing.SwingUtilities;
35import javax.swing.UIManager;
36import javax.swing.event.ChangeEvent;
37import javax.swing.event.ChangeListener;
38import javax.swing.event.DocumentEvent;
39import javax.swing.event.DocumentListener;
40
41import org.openstreetmap.josm.gui.HelpAwareOptionPane;
42import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
43import org.openstreetmap.josm.gui.MainApplication;
44import org.openstreetmap.josm.gui.help.HelpUtil;
45import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
46import org.openstreetmap.josm.gui.preferences.SubPreferenceSetting;
47import org.openstreetmap.josm.gui.preferences.TabPreferenceSetting;
48import org.openstreetmap.josm.gui.util.GuiHelper;
49import org.openstreetmap.josm.gui.widgets.SelectAllOnFocusGainedDecorator;
50import org.openstreetmap.josm.plugins.opendata.core.OdConstants;
51import org.openstreetmap.josm.plugins.opendata.core.modules.ModuleDownloadTask;
52import org.openstreetmap.josm.plugins.opendata.core.modules.ModuleInformation;
53import org.openstreetmap.josm.plugins.opendata.core.modules.ReadLocalModuleInformationTask;
54import org.openstreetmap.josm.plugins.opendata.core.modules.ReadRemoteModuleInformationTask;
55import org.openstreetmap.josm.spi.preferences.Config;
56import org.openstreetmap.josm.tools.GBC;
57import org.openstreetmap.josm.tools.ImageProvider;
58
59public class ModulePreference implements SubPreferenceSetting {
60
61 public static String buildDownloadSummary(ModuleDownloadTask task) {
62 Collection<ModuleInformation> downloaded = task.getDownloadedModules();
63 Collection<ModuleInformation> failed = task.getFailedModules();
64 StringBuilder sb = new StringBuilder();
65 if (!downloaded.isEmpty()) {
66 sb.append(trn(
67 "The following module has been downloaded <strong>successfully</strong>:",
68 "The following {0} modules have been downloaded <strong>successfully</strong>:",
69 downloaded.size(),
70 downloaded.size()
71 ));
72 sb.append("<ul>");
73 for (ModuleInformation pi : downloaded) {
74 sb.append("<li>").append(pi.name).append(" (").append(pi.version).append(")").append("</li>");
75 }
76 sb.append("</ul>");
77 }
78 if (!failed.isEmpty()) {
79 sb.append(trn(
80 "Downloading the following module has <strong>failed</strong>:",
81 "Downloading the following {0} modules has <strong>failed</strong>:",
82 failed.size(),
83 failed.size()
84 ));
85 sb.append("<ul>");
86 for (ModuleInformation pi : failed) {
87 sb.append("<li>").append(pi.name).append("</li>");
88 }
89 sb.append("</ul>");
90 }
91 return sb.toString();
92 }
93
94 private JTextField tfFilter;
95 private final ModulePreferencesModel model = new ModulePreferencesModel();
96 private final ModuleListPanel pnlModulePreferences = new ModuleListPanel(model);
97 private JScrollPane spModulePreferences;
98
99 /**
100 * is set to true if this preference pane has been selected
101 * by the user
102 */
103 private boolean modulePreferencesActivated = false;
104
105 protected JPanel buildSearchFieldPanel() {
106 JPanel pnl = new JPanel(new GridBagLayout());
107 pnl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
108 GridBagConstraints gc = new GridBagConstraints();
109
110 gc.anchor = GridBagConstraints.NORTHWEST;
111 gc.fill = GridBagConstraints.HORIZONTAL;
112 gc.weightx = 0.0;
113 gc.insets = new Insets(0, 0, 0, 3);
114 pnl.add(new JLabel(tr("Search:")), gc);
115
116 gc.gridx = 1;
117 gc.weightx = 1.0;
118 pnl.add(tfFilter = new JTextField(), gc);
119 tfFilter.setToolTipText(tr("Enter a search expression"));
120 SelectAllOnFocusGainedDecorator.decorate(tfFilter);
121 tfFilter.getDocument().addDocumentListener(new SearchFieldAdapter());
122 return pnl;
123 }
124
125 protected JPanel buildActionPanel() {
126 JPanel pnl = new JPanel(new GridLayout(1, 3));
127
128 pnl.add(new JButton(new DownloadAvailableModulesAction()));
129 pnl.add(new JButton(new UpdateSelectedModulesAction()));
130 pnl.add(new JButton(new ConfigureSitesAction()));
131 return pnl;
132 }
133
134 protected JPanel buildModuleListPanel() {
135 JPanel pnl = new JPanel(new BorderLayout());
136 pnl.add(buildSearchFieldPanel(), BorderLayout.NORTH);
137 spModulePreferences = new JScrollPane(pnlModulePreferences);
138 spModulePreferences.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
139 spModulePreferences.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
140 spModulePreferences.getVerticalScrollBar().addComponentListener(
141 new ComponentAdapter() {
142 @Override
143 public void componentShown(ComponentEvent e) {
144 spModulePreferences.setBorder(UIManager.getBorder("ScrollPane.border"));
145 }
146
147 @Override
148 public void componentHidden(ComponentEvent e) {
149 spModulePreferences.setBorder(null);
150 }
151 }
152 );
153
154 pnl.add(spModulePreferences, BorderLayout.CENTER);
155 pnl.add(buildActionPanel(), BorderLayout.SOUTH);
156 return pnl;
157 }
158
159 @Override
160 public void addGui(final PreferenceTabbedPane gui) {
161 GridBagConstraints gc = new GridBagConstraints();
162 gc.weightx = 1.0;
163 gc.weighty = 1.0;
164 gc.anchor = GridBagConstraints.NORTHWEST;
165 gc.fill = GridBagConstraints.BOTH;
166 OdPreferenceSetting settings = gui.getSetting(OdPreferenceSetting.class);
167 settings.tabPane.addTab(tr("Modules"), buildModuleListPanel());
168 pnlModulePreferences.refreshView();
169 gui.addChangeListener(new ModulePreferenceActivationListener(settings.masterPanel));
170 }
171
172 private void configureSites() {
173 ButtonSpec[] options = new ButtonSpec[] {
174 new ButtonSpec(
175 tr("OK"),
176 new ImageProvider("ok"),
177 tr("Accept the new module sites and close the dialog"),
178 null /* no special help topic */
179 ),
180 new ButtonSpec(
181 tr("Cancel"),
182 new ImageProvider("cancel"),
183 tr("Close the dialog"),
184 null /* no special help topic */
185 )
186 };
187 ModuleConfigurationSitesPanel pnl = new ModuleConfigurationSitesPanel();
188
189 int answer = HelpAwareOptionPane.showOptionDialog(
190 pnlModulePreferences,
191 pnl,
192 tr("Configure Module Sites"),
193 JOptionPane.QUESTION_MESSAGE,
194 null,
195 options,
196 options[0],
197 null /* no help topic */
198 );
199 if (answer != 0 /* OK */)
200 return;
201 List<String> sites = pnl.getUpdateSites();
202 OdPreferenceSetting.setModuleSites(sites);
203 }
204
205 /**
206 * Replies the list of modules waiting for update or download
207 *
208 * @return the list of modules waiting for update or download
209 */
210 public List<ModuleInformation> getModulesScheduledForUpdateOrDownload() {
211 return model != null ? model.getModulesScheduledForUpdateOrDownload() : null;
212 }
213
214 @Override
215 public boolean ok() {
216 if (!modulePreferencesActivated)
217 return false;
218 if (model.isActiveModulesChanged()) {
219 LinkedList<String> l = new LinkedList<>(model.getSelectedModuleNames());
220 Collections.sort(l);
221 Config.getPref().putList(OdConstants.PREF_MODULES, l);
222 return true;
223 }
224 return false;
225 }
226
227 /**
228 * Reads locally available information about modules from the local file system.
229 * Scans cached module lists from module download sites and locally available
230 * module jar files.
231 *
232 */
233 public void readLocalModuleInformation() {
234 final ReadLocalModuleInformationTask task = new ReadLocalModuleInformationTask();
235 Runnable r = () -> {
236 if (task.isCanceled()) return;
237 SwingUtilities.invokeLater(() -> {
238 model.setAvailableModules(task.getAvailableModules());
239 pnlModulePreferences.refreshView();
240 });
241 };
242 MainApplication.worker.submit(task);
243 MainApplication.worker.submit(r);
244 }
245
246 /**
247 * The action for downloading the list of available modules
248 *
249 */
250 class DownloadAvailableModulesAction extends AbstractAction {
251
252 DownloadAvailableModulesAction() {
253 putValue(NAME, tr("Download list"));
254 putValue(SHORT_DESCRIPTION, tr("Download the list of available modules"));
255 new ImageProvider("download").getResource().attachImageIcon(this);
256 }
257
258 @Override
259 public void actionPerformed(ActionEvent e) {
260 final ReadRemoteModuleInformationTask task = new ReadRemoteModuleInformationTask(OdPreferenceSetting.getModuleSites());
261 Runnable continuation = () -> {
262 if (task.isCanceled()) return;
263 SwingUtilities.invokeLater(() -> {
264 model.updateAvailableModules(task.getAvailableModules());
265 pnlModulePreferences.refreshView();
266 });
267 };
268 MainApplication.worker.submit(task);
269 MainApplication.worker.submit(continuation);
270 }
271 }
272
273 /**
274 * The action for downloading the list of available modules
275 *
276 */
277 class UpdateSelectedModulesAction extends AbstractAction {
278 UpdateSelectedModulesAction() {
279 putValue(NAME, tr("Update modules"));
280 putValue(SHORT_DESCRIPTION, tr("Update the selected modules"));
281 new ImageProvider("dialogs", "refresh").getResource().attachImageIcon(this);
282 }
283
284 protected void notifyDownloadResults(ModuleDownloadTask task) {
285 Collection<ModuleInformation> downloaded = task.getDownloadedModules();
286 Collection<ModuleInformation> failed = task.getFailedModules();
287 StringBuilder sb = new StringBuilder();
288 sb.append("<html>");
289 sb.append(buildDownloadSummary(task));
290 if (!downloaded.isEmpty()) {
291 sb.append(tr("Please restart JOSM to activate the downloaded modules."));
292 }
293 sb.append("</html>");
294 GuiHelper.runInEDTAndWait(() -> HelpAwareOptionPane.showOptionDialog(
295 pnlModulePreferences,
296 sb.toString(),
297 tr("Update modules"),
298 !failed.isEmpty() ? JOptionPane.WARNING_MESSAGE : JOptionPane.INFORMATION_MESSAGE,
299 HelpUtil.ht("/Preferences/Modules")
300 ));
301 }
302
303 protected void alertNothingToUpdate() {
304 GuiHelper.runInEDTAndWait(() -> HelpAwareOptionPane.showOptionDialog(
305 pnlModulePreferences,
306 tr("All installed modules are up to date. JOSM does not have to download newer versions."),
307 tr("Modules up to date"),
308 JOptionPane.INFORMATION_MESSAGE,
309 null // FIXME: provide help context
310 ));
311 }
312
313 @Override
314 public void actionPerformed(ActionEvent e) {
315 final List<ModuleInformation> toUpdate = model.getSelectedModules();
316 // the async task for downloading modules
317 final ModuleDownloadTask moduleDownloadTask = new ModuleDownloadTask(
318 pnlModulePreferences,
319 toUpdate,
320 tr("Update modules")
321 );
322 // the async task for downloading module information
323 final ReadRemoteModuleInformationTask moduleInfoDownloadTask =
324 new ReadRemoteModuleInformationTask(OdPreferenceSetting.getModuleSites());
325
326 // to be run asynchronously after the module download
327 //
328 final Runnable moduleDownloadContinuation = () -> {
329 if (moduleDownloadTask.isCanceled())
330 return;
331 notifyDownloadResults(moduleDownloadTask);
332 model.refreshLocalModuleVersion(moduleDownloadTask.getDownloadedModules());
333 model.clearPendingModules(moduleDownloadTask.getDownloadedModules());
334 GuiHelper.runInEDT(() -> pnlModulePreferences.refreshView());
335 };
336
337 // to be run asynchronously after the module list download
338 //
339 final Runnable moduleInfoDownloadContinuation = () -> {
340 if (moduleInfoDownloadTask.isCanceled())
341 return;
342 model.updateAvailableModules(moduleInfoDownloadTask.getAvailableModules());
343 // select modules which actually have to be updated
344 //
345 Iterator<ModuleInformation> it = toUpdate.iterator();
346 while (it.hasNext()) {
347 ModuleInformation pi = it.next();
348 if (!pi.isUpdateRequired()) {
349 it.remove();
350 }
351 }
352 if (toUpdate.isEmpty()) {
353 alertNothingToUpdate();
354 return;
355 }
356 moduleDownloadTask.setModulesToDownload(toUpdate);
357 MainApplication.worker.submit(moduleDownloadTask);
358 MainApplication.worker.submit(moduleDownloadContinuation);
359 };
360
361 MainApplication.worker.submit(moduleInfoDownloadTask);
362 MainApplication.worker.submit(moduleInfoDownloadContinuation);
363 }
364 }
365
366 /**
367 * The action for configuring the module download sites
368 *
369 */
370 class ConfigureSitesAction extends AbstractAction {
371 ConfigureSitesAction() {
372 putValue(NAME, tr("Configure sites..."));
373 putValue(SHORT_DESCRIPTION, tr("Configure the list of sites where modules are downloaded from"));
374 new ImageProvider("dialogs", "settings").getResource().attachImageIcon(this);
375 }
376
377 @Override
378 public void actionPerformed(ActionEvent e) {
379 configureSites();
380 }
381 }
382
383 /**
384 * Listens to the activation of the module preferences tab. On activation it
385 * reloads module information from the local file system.
386 *
387 */
388 class ModulePreferenceActivationListener implements ChangeListener {
389 private Component pane;
390 ModulePreferenceActivationListener(Component preferencesPane) {
391 pane = preferencesPane;
392 }
393
394 @Override
395 public void stateChanged(ChangeEvent e) {
396 JTabbedPane tp = (JTabbedPane) e.getSource();
397 if (tp.getSelectedComponent() == pane) {
398 readLocalModuleInformation();
399 modulePreferencesActivated = true;
400 }
401 }
402 }
403
404 /**
405 * Applies the current filter condition in the filter text field to the
406 * model
407 */
408 class SearchFieldAdapter implements DocumentListener {
409 public void filter() {
410 String expr = tfFilter.getText().trim();
411 if (expr.equals("")) {
412 expr = null;
413 }
414 model.filterDisplayedModules(expr);
415 pnlModulePreferences.refreshView();
416 }
417
418 @Override
419 public void changedUpdate(DocumentEvent arg0) {
420 filter();
421 }
422
423 @Override
424 public void insertUpdate(DocumentEvent arg0) {
425 filter();
426 }
427
428 @Override
429 public void removeUpdate(DocumentEvent arg0) {
430 filter();
431 }
432 }
433
434 private static class ModuleConfigurationSitesPanel extends JPanel {
435
436 private DefaultListModel<String> model;
437
438 protected void build() {
439 setLayout(new GridBagLayout());
440 add(new JLabel(tr("Add Open Data Module description URL.")), GBC.eol());
441 model = new DefaultListModel<>();
442 for (String s : OdPreferenceSetting.getModuleSites()) {
443 model.addElement(s);
444 }
445 final JList<String> list = new JList<>(model);
446 add(new JScrollPane(list), GBC.std().fill());
447 JPanel buttons = new JPanel(new GridBagLayout());
448 buttons.add(new JButton(new AbstractAction(tr("Add")) {
449 @Override
450 public void actionPerformed(ActionEvent e) {
451 String s = JOptionPane.showInputDialog(
452 JOptionPane.getFrameForComponent(ModuleConfigurationSitesPanel.this),
453 tr("Add Open Data Module description URL."),
454 tr("Enter URL"),
455 JOptionPane.QUESTION_MESSAGE
456 );
457 if (s != null) {
458 model.addElement(s);
459 }
460 }
461 }), GBC.eol().fill(GBC.HORIZONTAL));
462 buttons.add(new JButton(new AbstractAction(tr("Edit")) {
463 @Override
464 public void actionPerformed(ActionEvent e) {
465 if (list.getSelectedValue() == null) {
466 JOptionPane.showMessageDialog(
467 JOptionPane.getFrameForComponent(ModuleConfigurationSitesPanel.this),
468 tr("Please select an entry."),
469 tr("Warning"),
470 JOptionPane.WARNING_MESSAGE
471 );
472 return;
473 }
474 String s = (String) JOptionPane.showInputDialog(
475 MainApplication.getMainFrame(),
476 tr("Edit Open Data Module description URL."),
477 tr("Open Data Module description URL"),
478 JOptionPane.QUESTION_MESSAGE,
479 null,
480 null,
481 list.getSelectedValue()
482 );
483 if (s != null) {
484 model.setElementAt(s, list.getSelectedIndex());
485 }
486 }
487 }), GBC.eol().fill(GBC.HORIZONTAL));
488 buttons.add(new JButton(new AbstractAction(tr("Delete")) {
489 @Override
490 public void actionPerformed(ActionEvent event) {
491 if (list.getSelectedValue() == null) {
492 JOptionPane.showMessageDialog(
493 JOptionPane.getFrameForComponent(ModuleConfigurationSitesPanel.this),
494 tr("Please select an entry."),
495 tr("Warning"),
496 JOptionPane.WARNING_MESSAGE
497 );
498 return;
499 }
500 model.removeElement(list.getSelectedValue());
501 }
502 }), GBC.eol().fill(GBC.HORIZONTAL));
503 add(buttons, GBC.eol());
504 }
505
506 ModuleConfigurationSitesPanel() {
507 build();
508 }
509
510 public List<String> getUpdateSites() {
511 if (model.getSize() == 0) return Collections.emptyList();
512 List<String> ret = new ArrayList<>(model.getSize());
513 for (int i = 0; i < model.getSize(); i++) {
514 ret.add(model.get(i));
515 }
516 return ret;
517 }
518 }
519
520 @Override
521 public boolean isExpert() {
522 return false;
523 }
524
525 @Override
526 public TabPreferenceSetting getTabPreferenceSetting(PreferenceTabbedPane gui) {
527 return gui.getSetting(OdPreferenceSetting.class);
528 }
529}
Note: See TracBrowser for help on using the repository browser.