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