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