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

Revision 5140, 20.4 KB checked in by simon04, 8 weeks ago (diff)

fix #7030 - manual plugin update does not clear "you should update" status

  • Property svn:eol-style set to native
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.GridBagConstraints;
9import java.awt.GridBagLayout;
10import java.awt.GridLayout;
11import java.awt.Insets;
12import java.awt.event.ActionEvent;
13import java.awt.event.ComponentAdapter;
14import java.awt.event.ComponentEvent;
15import java.util.ArrayList;
16import java.util.Collection;
17import java.util.Collections;
18import java.util.Iterator;
19import java.util.LinkedList;
20import java.util.List;
21
22import javax.swing.AbstractAction;
23import javax.swing.BorderFactory;
24import javax.swing.DefaultListModel;
25import javax.swing.JButton;
26import javax.swing.JLabel;
27import javax.swing.JList;
28import javax.swing.JOptionPane;
29import javax.swing.JPanel;
30import javax.swing.JScrollPane;
31import javax.swing.JTabbedPane;
32import javax.swing.JTextField;
33import javax.swing.SwingUtilities;
34import javax.swing.UIManager;
35import javax.swing.event.DocumentEvent;
36import javax.swing.event.DocumentListener;
37
38import org.openstreetmap.josm.Main;
39import org.openstreetmap.josm.data.Version;
40import org.openstreetmap.josm.gui.HelpAwareOptionPane;
41import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
42import org.openstreetmap.josm.gui.help.HelpUtil;
43import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane.PreferencePanel;
44import org.openstreetmap.josm.gui.preferences.plugin.PluginListPanel;
45import org.openstreetmap.josm.gui.preferences.plugin.PluginPreferencesModel;
46import org.openstreetmap.josm.gui.preferences.plugin.PluginUpdatePolicyPanel;
47import org.openstreetmap.josm.gui.widgets.SelectAllOnFocusGainedDecorator;
48import org.openstreetmap.josm.plugins.PluginDownloadTask;
49import org.openstreetmap.josm.plugins.PluginInformation;
50import org.openstreetmap.josm.plugins.ReadLocalPluginInformationTask;
51import org.openstreetmap.josm.plugins.ReadRemotePluginInformationTask;
52import org.openstreetmap.josm.tools.GBC;
53import org.openstreetmap.josm.tools.ImageProvider;
54
55public class PluginPreference extends DefaultTabPreferenceSetting {
56    public static class Factory implements PreferenceSettingFactory {
57        public PreferenceSetting createPreferenceSetting() {
58            return new PluginPreference();
59        }
60    }
61   
62    private PluginPreference() {
63        super("plugin", tr("Plugins"), tr("Configure available plugins."));
64    }
65
66    public static String buildDownloadSummary(PluginDownloadTask task) {
67        Collection<PluginInformation> downloaded = task.getDownloadedPlugins();
68        Collection<PluginInformation> failed = task.getFailedPlugins();
69        StringBuilder sb = new StringBuilder();
70        if (! downloaded.isEmpty()) {
71            sb.append(trn(
72                    "The following plugin has been downloaded <strong>successfully</strong>:",
73                    "The following {0} plugins have been downloaded <strong>successfully</strong>:",
74                    downloaded.size(),
75                    downloaded.size()
76            ));
77            sb.append("<ul>");
78            for(PluginInformation pi: downloaded) {
79                sb.append("<li>").append(pi.name).append(" (").append(pi.version).append(")").append("</li>");
80            }
81            sb.append("</ul>");
82        }
83        if (! failed.isEmpty()) {
84            sb.append(trn(
85                    "Downloading the following plugin has <strong>failed</strong>:",
86                    "Downloading the following {0} plugins has <strong>failed</strong>:",
87                    failed.size(),
88                    failed.size()
89            ));
90            sb.append("<ul>");
91            for(PluginInformation pi: failed) {
92                sb.append("<li>").append(pi.name).append("</li>");
93            }
94            sb.append("</ul>");
95        }
96        return sb.toString();
97    }
98
99    private JTextField tfFilter;
100    private PluginListPanel pnlPluginPreferences;
101    private PluginPreferencesModel model;
102    private JScrollPane spPluginPreferences;
103    private PluginUpdatePolicyPanel pnlPluginUpdatePolicy;
104
105    /**
106     * is set to true if this preference pane has been selected
107     * by the user
108     */
109    private boolean pluginPreferencesActivated = false;
110
111    protected JPanel buildSearchFieldPanel() {
112        JPanel pnl  = new JPanel(new GridBagLayout());
113        pnl.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
114        GridBagConstraints gc = new GridBagConstraints();
115
116        gc.anchor = GridBagConstraints.NORTHWEST;
117        gc.fill = GridBagConstraints.HORIZONTAL;
118        gc.weightx = 0.0;
119        gc.insets = new Insets(0,0,0,3);
120        pnl.add(new JLabel(tr("Search:")), gc);
121
122        gc.gridx = 1;
123        gc.weightx = 1.0;
124        pnl.add(tfFilter = new JTextField(), gc);
125        tfFilter.setToolTipText(tr("Enter a search expression"));
126        SelectAllOnFocusGainedDecorator.decorate(tfFilter);
127        tfFilter.getDocument().addDocumentListener(new SearchFieldAdapter());
128        return pnl;
129    }
130
131    protected JPanel buildActionPanel() {
132        JPanel pnl = new JPanel(new GridLayout(1,3));
133
134        pnl.add(new JButton(new DownloadAvailablePluginsAction()));
135        pnl.add(new JButton(new UpdateSelectedPluginsAction()));
136        pnl.add(new JButton(new ConfigureSitesAction()));
137        return pnl;
138    }
139
140    protected JPanel buildPluginListPanel() {
141        JPanel pnl = new JPanel(new BorderLayout());
142        pnl.add(buildSearchFieldPanel(), BorderLayout.NORTH);
143        model  = new PluginPreferencesModel();
144        spPluginPreferences = new JScrollPane(pnlPluginPreferences = new PluginListPanel(model));
145        spPluginPreferences.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
146        spPluginPreferences.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
147        spPluginPreferences.getVerticalScrollBar().addComponentListener(
148                new ComponentAdapter(){
149                    @Override
150                    public void componentShown(ComponentEvent e) {
151                        spPluginPreferences.setBorder(UIManager.getBorder("ScrollPane.border"));
152                    }
153                    @Override
154                    public void componentHidden(ComponentEvent e) {
155                        spPluginPreferences.setBorder(null);
156                    }
157                }
158        );
159
160        pnl.add(spPluginPreferences, BorderLayout.CENTER);
161        pnl.add(buildActionPanel(), BorderLayout.SOUTH);
162        return pnl;
163    }
164
165    protected JPanel buildContentPanel() {
166        JPanel pnl = new JPanel(new BorderLayout());
167        JTabbedPane tpPluginPreferences = new JTabbedPane();
168        tpPluginPreferences.add(buildPluginListPanel());
169        tpPluginPreferences.add(pnlPluginUpdatePolicy  =new PluginUpdatePolicyPanel());
170        tpPluginPreferences.setTitleAt(0, tr("Plugins"));
171        tpPluginPreferences.setTitleAt(1, tr("Plugin update policy"));
172
173        pnl.add(tpPluginPreferences, BorderLayout.CENTER);
174        return pnl;
175    }
176
177    public void addGui(final PreferenceTabbedPane gui) {
178        GridBagConstraints gc = new GridBagConstraints();
179        gc.weightx = 1.0;
180        gc.weighty = 1.0;
181        gc.anchor = GridBagConstraints.NORTHWEST;
182        gc.fill = GridBagConstraints.BOTH;
183        PreferencePanel plugins = gui.createPreferenceTab(this);
184        plugins.add(buildContentPanel(), gc);
185        readLocalPluginInformation();
186        pluginPreferencesActivated = true;
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 != null ? model.getPluginsScheduledForUpdateOrDownload() : null;
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                            Main.pref.putInteger("pluginmanager.version", Version.getInstance().getVersion()); // fix #7030
289                        }
290                    });
291                }
292            };
293            Main.worker.submit(task);
294            Main.worker.submit(continuation);
295        }
296    }
297
298    /**
299     * The action for downloading the list of available plugins
300     *
301     */
302    class UpdateSelectedPluginsAction extends AbstractAction {
303        public UpdateSelectedPluginsAction() {
304            putValue(NAME,tr("Update plugins"));
305            putValue(SHORT_DESCRIPTION, tr("Update the selected plugins"));
306            putValue(SMALL_ICON, ImageProvider.get("dialogs", "refresh"));
307        }
308
309        protected void notifyDownloadResults(PluginDownloadTask task) {
310            Collection<PluginInformation> downloaded = task.getDownloadedPlugins();
311            Collection<PluginInformation> failed = task.getFailedPlugins();
312            StringBuilder sb = new StringBuilder();
313            sb.append("<html>");
314            sb.append(buildDownloadSummary(task));
315            if (!downloaded.isEmpty()) {
316                sb.append(tr("Please restart JOSM to activate the downloaded plugins."));
317            }
318            sb.append("</html>");
319            HelpAwareOptionPane.showOptionDialog(
320                    pnlPluginPreferences,
321                    sb.toString(),
322                    tr("Update plugins"),
323                    !failed.isEmpty() ? JOptionPane.WARNING_MESSAGE : JOptionPane.INFORMATION_MESSAGE,
324                            HelpUtil.ht("/Preferences/Plugins")
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     * Applies the current filter condition in the filter text field to the
412     * model
413     */
414    class SearchFieldAdapter implements DocumentListener {
415        public void filter() {
416            String expr = tfFilter.getText().trim();
417            if (expr.equals("")) {
418                expr = null;
419            }
420            model.filterDisplayedPlugins(expr);
421            pnlPluginPreferences.refreshView();
422        }
423
424        public void changedUpdate(DocumentEvent arg0) {
425            filter();
426        }
427
428        public void insertUpdate(DocumentEvent arg0) {
429            filter();
430        }
431
432        public void removeUpdate(DocumentEvent arg0) {
433            filter();
434        }
435    }
436
437    static private class PluginConfigurationSitesPanel extends JPanel {
438
439        private DefaultListModel model;
440
441        protected void build() {
442            setLayout(new GridBagLayout());
443            add(new JLabel(tr("Add JOSM Plugin description URL.")), GBC.eol());
444            model = new DefaultListModel();
445            for (String s : Main.pref.getPluginSites()) {
446                model.addElement(s);
447            }
448            final JList list = new JList(model);
449            add(new JScrollPane(list), GBC.std().fill());
450            JPanel buttons = new JPanel(new GridBagLayout());
451            buttons.add(new JButton(new AbstractAction(tr("Add")){
452                public void actionPerformed(ActionEvent e) {
453                    String s = JOptionPane.showInputDialog(
454                            JOptionPane.getFrameForComponent(PluginConfigurationSitesPanel.this),
455                            tr("Add JOSM Plugin description URL."),
456                            tr("Enter URL"),
457                            JOptionPane.QUESTION_MESSAGE
458                    );
459                    if (s != null) {
460                        model.addElement(s);
461                    }
462                }
463            }), GBC.eol().fill(GBC.HORIZONTAL));
464            buttons.add(new JButton(new AbstractAction(tr("Edit")){
465                public void actionPerformed(ActionEvent e) {
466                    if (list.getSelectedValue() == null) {
467                        JOptionPane.showMessageDialog(
468                                JOptionPane.getFrameForComponent(PluginConfigurationSitesPanel.this),
469                                tr("Please select an entry."),
470                                tr("Warning"),
471                                JOptionPane.WARNING_MESSAGE
472                        );
473                        return;
474                    }
475                    String s = (String)JOptionPane.showInputDialog(
476                            Main.parent,
477                            tr("Edit JOSM Plugin description URL."),
478                            tr("JOSM Plugin description URL"),
479                            JOptionPane.QUESTION_MESSAGE,
480                            null,
481                            null,
482                            list.getSelectedValue()
483                    );
484                    if (s != null) {
485                        model.setElementAt(s, list.getSelectedIndex());
486                    }
487                }
488            }), GBC.eol().fill(GBC.HORIZONTAL));
489            buttons.add(new JButton(new AbstractAction(tr("Delete")){
490                public void actionPerformed(ActionEvent event) {
491                    if (list.getSelectedValue() == null) {
492                        JOptionPane.showMessageDialog(
493                                JOptionPane.getFrameForComponent(PluginConfigurationSitesPanel.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        public PluginConfigurationSitesPanel() {
507            build();
508        }
509
510        public List<String> getUpdateSites() {
511            if (model.getSize() == 0) return Collections.emptyList();
512            List<String> ret = new ArrayList<String>(model.getSize());
513            for (int i=0; i< model.getSize();i++){
514                ret.add((String)model.get(i));
515            }
516            return ret;
517        }
518    }
519}
Note: See TracBrowser for help on using the repository browser.