001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.preferences.plugin;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.GridBagConstraints;
007import java.awt.GridBagLayout;
008import java.awt.Insets;
009import java.awt.Rectangle;
010import java.awt.event.MouseAdapter;
011import java.awt.event.MouseEvent;
012import java.util.List;
013
014import javax.swing.JLabel;
015import javax.swing.SwingConstants;
016import javax.swing.SwingUtilities;
017import javax.swing.event.HyperlinkEvent.EventType;
018
019import org.openstreetmap.josm.gui.widgets.HtmlPanel;
020import org.openstreetmap.josm.gui.widgets.VerticallyScrollablePanel;
021import org.openstreetmap.josm.plugins.PluginInformation;
022import org.openstreetmap.josm.tools.OpenBrowser;
023
024/**
025 * A panel displaying the list of known plugins.
026 */
027public class PluginListPanel extends VerticallyScrollablePanel {
028    static final class PluginCheckBoxMouseAdapter extends MouseAdapter {
029        private final PluginCheckBox cbPlugin;
030
031        PluginCheckBoxMouseAdapter(PluginCheckBox cbPlugin) {
032            this.cbPlugin = cbPlugin;
033        }
034
035        @Override
036        public void mouseClicked(MouseEvent e) {
037            cbPlugin.doClick();
038        }
039    }
040
041    private transient PluginPreferencesModel model;
042
043    /**
044     * Constructs a new {@code PluginListPanel} with a default model.
045     */
046    public PluginListPanel() {
047        this(new PluginPreferencesModel());
048    }
049
050    /**
051     * Constructs a new {@code PluginListPanel} with a given model.
052     * @param model The plugin model
053     */
054    public PluginListPanel(PluginPreferencesModel model) {
055        this.model = model;
056        setLayout(new GridBagLayout());
057    }
058
059    protected static String formatPluginRemoteVersion(PluginInformation pi) {
060        StringBuilder sb = new StringBuilder();
061        if (pi.version == null || pi.version.trim().isEmpty()) {
062            sb.append(tr("unknown"));
063        } else {
064            sb.append(pi.version);
065            if (pi.oldmode) {
066                sb.append('*');
067            }
068        }
069        return sb.toString();
070    }
071
072    protected static String formatPluginLocalVersion(PluginInformation pi) {
073        if (pi == null)
074            return tr("unknown");
075        if (pi.localversion == null || pi.localversion.trim().isEmpty())
076            return tr("unknown");
077        return pi.localversion;
078    }
079
080    protected static String formatCheckboxTooltipText(PluginInformation pi) {
081        if (pi == null)
082            return "";
083        if (pi.downloadlink == null)
084            return tr("Plugin bundled with JOSM");
085        else
086            return pi.downloadlink;
087    }
088
089    /**
090     * Displays a message when the plugin list is empty.
091     */
092    public void displayEmptyPluginListInformation() {
093        GridBagConstraints gbc = new GridBagConstraints();
094        gbc.gridx = 0;
095        gbc.anchor = GridBagConstraints.CENTER;
096        gbc.fill = GridBagConstraints.BOTH;
097        gbc.insets = new Insets(40, 0, 40, 0);
098        gbc.weightx = 1.0;
099        gbc.weighty = 1.0;
100
101        HtmlPanel hint = new HtmlPanel();
102        hint.setText(
103                "<html>"
104                + tr("Please click on <strong>Download list</strong> to download and display a list of available plugins.")
105                + "</html>"
106        );
107        add(hint, gbc);
108    }
109
110    /**
111     * Refreshes the list.
112     */
113    public void refreshView() {
114        final Rectangle visibleRect = getVisibleRect();
115        List<PluginInformation> displayedPlugins = model.getDisplayedPlugins();
116        removeAll();
117
118        GridBagConstraints gbc = new GridBagConstraints();
119        gbc.gridx = 0;
120        gbc.anchor = GridBagConstraints.NORTHWEST;
121        gbc.fill = GridBagConstraints.HORIZONTAL;
122        gbc.weightx = 1.0;
123
124        if (displayedPlugins.isEmpty()) {
125            displayEmptyPluginListInformation();
126            return;
127        }
128
129        int row = -1;
130        for (final PluginInformation pi : displayedPlugins) {
131            boolean selected = model.isSelectedPlugin(pi.getName());
132            String remoteversion = formatPluginRemoteVersion(pi);
133            String localversion = formatPluginLocalVersion(model.getPluginInformation(pi.getName()));
134
135            final PluginCheckBox cbPlugin = new PluginCheckBox(pi, selected, this, model);
136            String pluginText = tr("{0}: Version {1} (local: {2})", pi.getName(), remoteversion, localversion);
137            if (pi.requires != null && !pi.requires.isEmpty()) {
138                pluginText += tr(" (requires: {0})", pi.requires);
139            }
140            JLabel lblPlugin = new JLabel(
141                    pluginText,
142                    pi.getScaledIcon(),
143                    SwingConstants.LEFT);
144            lblPlugin.addMouseListener(new PluginCheckBoxMouseAdapter(cbPlugin));
145
146            gbc.gridx = 0;
147            gbc.gridy = ++row;
148            gbc.insets = new Insets(5, 5, 0, 5);
149            gbc.weighty = 0.0;
150            gbc.weightx = 0.0;
151            add(cbPlugin, gbc);
152
153            gbc.gridx = 1;
154            gbc.weightx = 1.0;
155            add(lblPlugin, gbc);
156
157            HtmlPanel description = new HtmlPanel();
158            description.setText(pi.getDescriptionAsHtml());
159            description.getEditorPane().addHyperlinkListener(e -> {
160                if (e.getEventType() == EventType.ACTIVATED) {
161                    OpenBrowser.displayUrl(e.getURL().toString());
162                }
163            });
164            lblPlugin.setLabelFor(description);
165
166            gbc.gridx = 1;
167            gbc.gridy = ++row;
168            gbc.insets = new Insets(3, 25, 5, 5);
169            gbc.weighty = 1.0;
170            add(description, gbc);
171        }
172        revalidate();
173        repaint();
174        SwingUtilities.invokeLater(() -> scrollRectToVisible(visibleRect));
175    }
176}