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}