source: josm/trunk/src/org/openstreetmap/josm/gui/preferences/PluginPreference.java@ 3530

Last change on this file since 3530 was 3530, checked in by stoecker, 14 years ago

fix array preferences

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