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