001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.actions; 003 004import static java.awt.GridBagConstraints.HORIZONTAL; 005import static javax.swing.SwingConstants.CENTER; 006import static org.openstreetmap.josm.gui.help.HelpUtil.ht; 007import static org.openstreetmap.josm.tools.I18n.tr; 008import static org.openstreetmap.josm.tools.Utils.getSystemProperty; 009 010import java.awt.Color; 011import java.awt.Dimension; 012import java.awt.FlowLayout; 013import java.awt.Font; 014import java.awt.GridBagLayout; 015import java.awt.event.ActionEvent; 016import java.awt.event.KeyEvent; 017import java.io.BufferedReader; 018import java.io.File; 019import java.io.IOException; 020import java.io.InputStream; 021import java.io.InputStreamReader; 022import java.nio.charset.StandardCharsets; 023import java.util.Map.Entry; 024 025import javax.swing.AbstractAction; 026import javax.swing.Action; 027import javax.swing.BorderFactory; 028import javax.swing.JButton; 029import javax.swing.JLabel; 030import javax.swing.JPanel; 031import javax.swing.JScrollPane; 032import javax.swing.JTabbedPane; 033import javax.swing.JTextArea; 034 035import org.openstreetmap.josm.data.Preferences; 036import org.openstreetmap.josm.data.Version; 037import org.openstreetmap.josm.gui.ExtendedDialog; 038import org.openstreetmap.josm.gui.MainApplication; 039import org.openstreetmap.josm.gui.util.GuiHelper; 040import org.openstreetmap.josm.gui.widgets.JMultilineLabel; 041import org.openstreetmap.josm.gui.widgets.JosmTextArea; 042import org.openstreetmap.josm.gui.widgets.UrlLabel; 043import org.openstreetmap.josm.plugins.PluginHandler; 044import org.openstreetmap.josm.spi.preferences.Config; 045import org.openstreetmap.josm.tools.GBC; 046import org.openstreetmap.josm.tools.ImageProvider; 047import org.openstreetmap.josm.tools.ImageProvider.ImageSizes; 048import org.openstreetmap.josm.tools.Logging; 049import org.openstreetmap.josm.tools.OpenBrowser; 050import org.openstreetmap.josm.tools.Shortcut; 051import org.openstreetmap.josm.tools.Utils; 052 053/** 054 * Nice about screen. 055 * 056 * The REVISION resource is read and if present, it shows the revision information of the jar-file. 057 * 058 * @author imi 059 */ 060public final class AboutAction extends JosmAction { 061 062 /** 063 * Constructs a new {@code AboutAction}. 064 */ 065 public AboutAction() { 066 super(tr("About"), "logo", tr("Display the about screen."), 067 Shortcut.registerShortcut("system:about", tr("Help: {0}", tr("About")), 068 KeyEvent.VK_F1, Shortcut.SHIFT), true, false); 069 } 070 071 JPanel buildAboutPanel() { 072 final JTabbedPane about = new JTabbedPane(); 073 074 Version version = Version.getInstance(); 075 076 JosmTextArea readme = new JosmTextArea(); 077 readme.setFont(GuiHelper.getMonospacedFont(readme)); 078 readme.setEditable(false); 079 setTextFromResourceFile(readme, "/README"); 080 readme.setCaretPosition(0); 081 082 JosmTextArea revision = new JosmTextArea(); 083 revision.setFont(GuiHelper.getMonospacedFont(revision)); 084 revision.setEditable(false); 085 revision.setText(version.getReleaseAttributes()); 086 revision.setCaretPosition(0); 087 088 JosmTextArea contribution = new JosmTextArea(); 089 contribution.setEditable(false); 090 setTextFromResourceFile(contribution, "/CONTRIBUTION"); 091 contribution.setCaretPosition(0); 092 093 JosmTextArea license = new JosmTextArea(); 094 license.setEditable(false); 095 setTextFromResourceFile(license, "/LICENSE"); 096 license.setCaretPosition(0); 097 098 JPanel info = new JPanel(new GridBagLayout()); 099 final JMultilineLabel label = new JMultilineLabel("<html>" + 100 "<h1>" + "JOSM – " + tr("Java OpenStreetMap Editor") + "</h1>" + 101 "<p style='font-size:75%'></p>" + 102 "<p>" + tr("Version {0}", version.getVersionString()) + "</p>" + 103 "<p style='font-size:50%'></p>" + 104 "<p>" + tr("Last change at {0}", version.getTime()) + "</p>" + 105 "<p style='font-size:50%'></p>" + 106 "<p>" + tr("Java Version {0}", getSystemProperty("java.version")) + "</p>" + 107 "<p style='font-size:50%'></p>" + 108 "</html>"); 109 info.add(label, GBC.eol().fill(HORIZONTAL).insets(10, 0, 0, 10)); 110 info.add(new JLabel(tr("Homepage")), GBC.std().insets(10, 0, 10, 0)); 111 info.add(new UrlLabel(Config.getUrls().getJOSMWebsite(), 2), GBC.eol()); 112 info.add(new JLabel(tr("Translations")), GBC.std().insets(10, 0, 10, 0)); 113 info.add(new UrlLabel("https://translations.launchpad.net/josm", 2), GBC.eol()); 114 info.add(new JLabel(tr("Follow us on")), GBC.std().insets(10, 10, 10, 0)); 115 JPanel logos = new JPanel(new FlowLayout()); 116 logos.add(createImageLink("OpenStreetMap", /* ICON(dialogs/about/) */ "openstreetmap", 117 "https://www.openstreetmap.org/user/josmeditor/diary")); 118 logos.add(createImageLink("Twitter", /* ICON(dialogs/about/) */ "twitter-square", "https://twitter.com/josmeditor")); 119 logos.add(createImageLink("Facebook", /* ICON(dialogs/about/) */ "facebook-square", "https://www.facebook.com/josmeditor")); 120 logos.add(createImageLink("GitHub", /* ICON(dialogs/about/) */ "github-square", "https://github.com/JOSM")); 121 info.add(logos, GBC.eol().insets(0, 10, 0, 0)); 122 info.add(GBC.glue(0, 5), GBC.eol()); 123 124 JPanel inst = new JPanel(new GridBagLayout()); 125 inst.add(new JLabel(tr("Preferences are stored in {0}", getPathToPreferences())), GBC.eol().insets(0, 0, 0, 10)); 126 inst.add(new JLabel(tr("Symbolic names for directories and the actual paths:")), 127 GBC.eol().insets(0, 0, 0, 10)); 128 for (Entry<String, String> entry : ShowStatusReportAction.getAnonimicDirectorySymbolMap().entrySet()) { 129 addInstallationLine(inst, entry.getValue(), entry.getKey()); 130 } 131 132 about.addTab(tr("Info"), info); 133 about.addTab(tr("Readme"), createScrollPane(readme)); 134 about.addTab(tr("Revision"), createScrollPane(revision)); 135 about.addTab(tr("Contribution"), createScrollPane(contribution)); 136 about.addTab(tr("License"), createScrollPane(license)); 137 about.addTab(tr("Plugins"), new JScrollPane(PluginHandler.getInfoPanel())); 138 about.addTab(tr("Installation Details"), inst); 139 140 // Get the list of Launchpad contributors using customary msgid “translator-credits” 141 String translators = tr("translator-credits"); 142 if (!Utils.isEmpty(translators) && !"translator-credits".equals(translators)) { 143 about.addTab(tr("Translators"), createScrollPane(new JosmTextArea(translators))); 144 } 145 146 // Intermediate panel to allow proper optionPane resizing 147 JPanel panel = new JPanel(new GridBagLayout()); 148 panel.setPreferredSize(new Dimension(890, 300)); 149 panel.add(new JLabel("", ImageProvider.get("logo.svg", ImageSizes.ABOUT_LOGO), CENTER), GBC.std().insets(0, 5, 0, 0)); 150 panel.add(about, GBC.std().fill()); 151 return panel; 152 } 153 154 private static String getPathToPreferences() { 155 File preferenceFile = Preferences.main().getPreferenceFile(); 156 try { 157 return ShowStatusReportAction.paramCleanup(preferenceFile.getAbsolutePath()); 158 } catch (SecurityException e) { 159 Logging.warn(e); 160 return ShowStatusReportAction.paramCleanup(preferenceFile.getPath()); 161 } 162 } 163 164 @Override 165 public void actionPerformed(ActionEvent e) { 166 JPanel panel = buildAboutPanel(); 167 168 GuiHelper.prepareResizeableOptionPane(panel, panel.getPreferredSize()); 169 ExtendedDialog dlg = new ExtendedDialog(MainApplication.getMainFrame(), tr("About JOSM..."), tr("OK"), tr("Report bug")); 170 int ret = dlg.setButtonIcons("ok", "bug") 171 .configureContextsensitiveHelp(ht("Action/About"), true) 172 .setContent(panel, false) 173 .showDialog().getValue(); 174 if (2 == ret) { 175 MainApplication.getMenu().reportbug.actionPerformed(null); 176 } 177 GuiHelper.destroyComponents(panel, false); 178 dlg.dispose(); 179 } 180 181 private static class OpenDirAction extends AbstractAction { 182 final String dir; 183 184 OpenDirAction(String dir) { 185 putValue(Action.NAME, "..."); 186 this.dir = dir; 187 try { 188 setEnabled(dir != null && new File(dir).isDirectory()); 189 } catch (SecurityException e) { 190 setEnabled(false); 191 Logging.warn(e); 192 } 193 } 194 195 @Override 196 public void actionPerformed(ActionEvent e) { 197 OpenBrowser.displayUrl(new File(dir).toURI()); 198 } 199 } 200 201 /** 202 * Add line to installation details showing symbolic name used in status report and actual directory. 203 * @param inst the panel 204 * @param dir the actual path represented by a symbol 205 * @param source source for symbol 206 */ 207 private static void addInstallationLine(JPanel inst, String dir, String source) { 208 if (source == null) 209 return; 210 JLabel symbol = new JLabel(source); 211 symbol.setFont(GuiHelper.getMonospacedFont(symbol)); 212 JosmTextArea dirLabel = new JosmTextArea(); 213 if (!Utils.isEmpty(dir)) { 214 dirLabel.setText(dir); 215 dirLabel.setEditable(false); 216 } else { 217 dirLabel.setText("<" + tr("unset") + ">"); 218 dirLabel.setFont(dirLabel.getFont().deriveFont(Font.ITALIC)); 219 dirLabel.setEditable(false); 220 } 221 inst.add(symbol, GBC.std().insets(5, 0, 0, 0)); 222 inst.add(GBC.glue(10, 0), GBC.std()); 223 dirLabel.setFont(GuiHelper.getMonospacedFont(dirLabel)); 224 dirLabel.setOpaque(false); 225 inst.add(dirLabel, GBC.std().fill(HORIZONTAL)); 226 JButton btn = new JButton(new OpenDirAction(dir)); 227 btn.setToolTipText(tr("Open directory")); 228 inst.add(btn, GBC.eol().insets(0, 0, 5, 0)); 229 } 230 231 private static JLabel createImageLink(String tooltip, String icon, final String link) { 232 return new UrlLabel(link, tooltip, ImageProvider.get("dialogs/about", icon, ImageSizes.LARGEICON)); 233 } 234 235 /** 236 * Reads the contents of the resource file that is described by the {@code filePath}-attribute and puts that text 237 * into the {@link JTextArea} given by the {@code ta}-attribute. 238 * @param ta the {@link JTextArea} to put the files contents into 239 * @param filePath the path where the resource file to read resides 240 */ 241 private void setTextFromResourceFile(JTextArea ta, String filePath) { 242 InputStream is = Utils.getResourceAsStream(getClass(), filePath); 243 if (is == null) { 244 displayErrorMessage(ta, tr("Failed to locate resource ''{0}''.", filePath)); 245 } else { 246 try (BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { 247 String line; 248 while ((line = br.readLine()) != null) { 249 ta.append(line+'\n'); 250 } 251 } catch (IOException e) { 252 Logging.warn(e); 253 displayErrorMessage(ta, tr("Failed to load resource ''{0}'', error is {1}.", filePath, e.toString())); 254 } 255 } 256 } 257 258 private static void displayErrorMessage(JTextArea ta, String msg) { 259 Logging.warn(msg); 260 ta.setForeground(new Color(200, 0, 0)); 261 ta.setText(msg); 262 } 263 264 private static JScrollPane createScrollPane(JosmTextArea area) { 265 area.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); 266 area.setOpaque(false); 267 JScrollPane sp = new JScrollPane(area); 268 sp.setBorder(null); 269 sp.setOpaque(false); 270 return sp; 271 } 272}