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