001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.dialogs;
003
004import static org.openstreetmap.josm.tools.I18n.marktr;
005import static org.openstreetmap.josm.tools.I18n.tr;
006
007import java.awt.Component;
008import java.awt.Dimension;
009import java.awt.Font;
010import java.awt.GridBagLayout;
011import java.awt.Insets;
012import java.awt.Point;
013import java.awt.Rectangle;
014import java.awt.event.ActionEvent;
015import java.awt.event.ActionListener;
016import java.awt.event.KeyEvent;
017import java.awt.event.MouseEvent;
018import java.io.BufferedInputStream;
019import java.io.BufferedReader;
020import java.io.File;
021import java.io.IOException;
022import java.io.InputStream;
023import java.io.InputStreamReader;
024import java.nio.charset.StandardCharsets;
025import java.nio.file.Files;
026import java.nio.file.StandardCopyOption;
027import java.util.ArrayList;
028import java.util.Arrays;
029import java.util.Collection;
030import java.util.List;
031
032import javax.swing.AbstractAction;
033import javax.swing.DefaultButtonModel;
034import javax.swing.DefaultListSelectionModel;
035import javax.swing.ImageIcon;
036import javax.swing.JCheckBox;
037import javax.swing.JFileChooser;
038import javax.swing.JLabel;
039import javax.swing.JMenu;
040import javax.swing.JPanel;
041import javax.swing.JPopupMenu;
042import javax.swing.JScrollPane;
043import javax.swing.JTabbedPane;
044import javax.swing.JTable;
045import javax.swing.JViewport;
046import javax.swing.ListSelectionModel;
047import javax.swing.SingleSelectionModel;
048import javax.swing.SwingConstants;
049import javax.swing.SwingUtilities;
050import javax.swing.UIManager;
051import javax.swing.border.EmptyBorder;
052import javax.swing.event.ChangeEvent;
053import javax.swing.event.ChangeListener;
054import javax.swing.event.ListSelectionEvent;
055import javax.swing.event.ListSelectionListener;
056import javax.swing.filechooser.FileFilter;
057import javax.swing.table.AbstractTableModel;
058import javax.swing.table.DefaultTableCellRenderer;
059import javax.swing.table.TableCellRenderer;
060import javax.swing.table.TableModel;
061
062import org.openstreetmap.josm.Main;
063import org.openstreetmap.josm.actions.ExtensionFileFilter;
064import org.openstreetmap.josm.actions.JosmAction;
065import org.openstreetmap.josm.actions.PreferencesAction;
066import org.openstreetmap.josm.gui.ExtendedDialog;
067import org.openstreetmap.josm.gui.PleaseWaitRunnable;
068import org.openstreetmap.josm.gui.SideButton;
069import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
070import org.openstreetmap.josm.gui.mappaint.MapPaintStyles.MapPaintSylesUpdateListener;
071import org.openstreetmap.josm.gui.mappaint.StyleSetting;
072import org.openstreetmap.josm.gui.mappaint.StyleSource;
073import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource;
074import org.openstreetmap.josm.gui.preferences.SourceEntry;
075import org.openstreetmap.josm.gui.preferences.map.MapPaintPreference;
076import org.openstreetmap.josm.gui.util.FileFilterAllFiles;
077import org.openstreetmap.josm.gui.util.GuiHelper;
078import org.openstreetmap.josm.gui.widgets.AbstractFileChooser;
079import org.openstreetmap.josm.gui.widgets.FileChooserManager;
080import org.openstreetmap.josm.gui.widgets.HtmlPanel;
081import org.openstreetmap.josm.gui.widgets.JosmTextArea;
082import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
083import org.openstreetmap.josm.tools.GBC;
084import org.openstreetmap.josm.tools.ImageOverlay;
085import org.openstreetmap.josm.tools.ImageProvider;
086import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
087import org.openstreetmap.josm.tools.InputMapUtils;
088import org.openstreetmap.josm.tools.Shortcut;
089import org.openstreetmap.josm.tools.Utils;
090
091/**
092 * Dialog to configure the map painting style.
093 * @since 3843
094 */
095public class MapPaintDialog extends ToggleDialog {
096
097    protected StylesTable tblStyles;
098    protected StylesModel model;
099    protected final DefaultListSelectionModel selectionModel = new DefaultListSelectionModel();
100
101    protected OnOffAction onoffAction;
102    protected ReloadAction reloadAction;
103    protected MoveUpDownAction upAction;
104    protected MoveUpDownAction downAction;
105    protected JCheckBox cbWireframe;
106
107    /**
108     * Action that opens the map paint preferences.
109     */
110    public static final JosmAction PREFERENCE_ACTION = PreferencesAction.forPreferenceSubTab(
111            tr("Map paint preferences"), null, MapPaintPreference.class, /* ICON */ "dialogs/mappaintpreference");
112
113    /**
114     * Constructs a new {@code MapPaintDialog}.
115     */
116    public MapPaintDialog() {
117        super(tr("Map Paint Styles"), "mapstyle", tr("configure the map painting style"),
118                Shortcut.registerShortcut("subwindow:mappaint", tr("Toggle: {0}", tr("MapPaint")),
119                        KeyEvent.VK_M, Shortcut.ALT_SHIFT), 150, false, MapPaintPreference.class);
120        build();
121    }
122
123    protected void build() {
124        model = new StylesModel();
125
126        cbWireframe = new JCheckBox();
127        JLabel wfLabel = new JLabel(tr("Wireframe View"), ImageProvider.get("dialogs/mappaint", "wireframe_small"), JLabel.HORIZONTAL);
128        wfLabel.setFont(wfLabel.getFont().deriveFont(Font.PLAIN));
129        wfLabel.setLabelFor(cbWireframe);
130
131        cbWireframe.setModel(new DefaultButtonModel() {
132            @Override
133            public void setSelected(boolean b) {
134                super.setSelected(b);
135                tblStyles.setEnabled(!b);
136                onoffAction.updateEnabledState();
137                upAction.updateEnabledState();
138                downAction.updateEnabledState();
139            }
140        });
141        cbWireframe.addActionListener(new ActionListener() {
142            @Override
143            public void actionPerformed(ActionEvent e) {
144                Main.main.menu.wireFrameToggleAction.actionPerformed(null);
145            }
146        });
147        cbWireframe.setBorder(new EmptyBorder(new Insets(1, 1, 1, 1)));
148
149        tblStyles = new StylesTable(model);
150        tblStyles.setSelectionModel(selectionModel);
151        tblStyles.addMouseListener(new PopupMenuHandler());
152        tblStyles.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
153        tblStyles.setBackground(UIManager.getColor("Panel.background"));
154        tblStyles.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
155        tblStyles.setTableHeader(null);
156        tblStyles.getColumnModel().getColumn(0).setMaxWidth(1);
157        tblStyles.getColumnModel().getColumn(0).setResizable(false);
158        tblStyles.getColumnModel().getColumn(0).setCellRenderer(new MyCheckBoxRenderer());
159        tblStyles.getColumnModel().getColumn(1).setCellRenderer(new StyleSourceRenderer());
160        tblStyles.setShowGrid(false);
161        tblStyles.setIntercellSpacing(new Dimension(0, 0));
162
163        JPanel p = new JPanel(new GridBagLayout());
164        p.add(cbWireframe, GBC.std(0, 0));
165        p.add(wfLabel, GBC.std(1, 0).weight(1, 0));
166        p.add(tblStyles, GBC.std(0, 1).span(2).fill());
167
168        reloadAction = new ReloadAction();
169        onoffAction = new OnOffAction();
170        upAction = new MoveUpDownAction(false);
171        downAction = new MoveUpDownAction(true);
172        selectionModel.addListSelectionListener(onoffAction);
173        selectionModel.addListSelectionListener(reloadAction);
174        selectionModel.addListSelectionListener(upAction);
175        selectionModel.addListSelectionListener(downAction);
176
177        // Toggle style on Enter and Spacebar
178        InputMapUtils.addEnterAction(tblStyles, onoffAction);
179        InputMapUtils.addSpacebarAction(tblStyles, onoffAction);
180
181        createLayout(p, true, Arrays.asList(
182                new SideButton(onoffAction, false),
183                new SideButton(upAction, false),
184                new SideButton(downAction, false),
185                new SideButton(PREFERENCE_ACTION, false)
186        ));
187    }
188
189    protected static class StylesTable extends JTable {
190
191        public StylesTable(TableModel dm) {
192            super(dm);
193        }
194
195        public void scrollToVisible(int row, int col) {
196            if (!(getParent() instanceof JViewport))
197                return;
198            JViewport viewport = (JViewport) getParent();
199            Rectangle rect = getCellRect(row, col, true);
200            Point pt = viewport.getViewPosition();
201            rect.setLocation(rect.x - pt.x, rect.y - pt.y);
202            viewport.scrollRectToVisible(rect);
203        }
204    }
205
206    @Override
207    public void showNotify() {
208        MapPaintStyles.addMapPaintSylesUpdateListener(model);
209        Main.main.menu.wireFrameToggleAction.addButtonModel(cbWireframe.getModel());
210    }
211
212    @Override
213    public void hideNotify() {
214        Main.main.menu.wireFrameToggleAction.removeButtonModel(cbWireframe.getModel());
215        MapPaintStyles.removeMapPaintSylesUpdateListener(model);
216    }
217
218    protected class StylesModel extends AbstractTableModel implements MapPaintSylesUpdateListener {
219
220        private final Class<?>[] columnClasses = {Boolean.class, StyleSource.class};
221
222        private transient List<StyleSource> data = new ArrayList<>();
223
224        /**
225         * Constructs a new {@code StylesModel}.
226         */
227        public StylesModel() {
228            data = new ArrayList<>(MapPaintStyles.getStyles().getStyleSources());
229        }
230
231        private StyleSource getRow(int i) {
232            return data.get(i);
233        }
234
235        @Override
236        public int getColumnCount() {
237            return 2;
238        }
239
240        @Override
241        public int getRowCount() {
242            return data.size();
243        }
244
245        @Override
246        public Object getValueAt(int row, int column) {
247            if (column == 0)
248                return getRow(row).active;
249            else
250                return getRow(row);
251        }
252
253        @Override
254        public boolean isCellEditable(int row, int column) {
255            return column == 0;
256        }
257
258        @Override
259        public Class<?> getColumnClass(int column) {
260            return columnClasses[column];
261        }
262
263        @Override
264        public void setValueAt(Object aValue, int row, int column) {
265            if (row < 0 || row >= getRowCount() || aValue == null)
266                return;
267            if (column == 0) {
268                MapPaintStyles.toggleStyleActive(row);
269            }
270        }
271
272        /**
273         * Make sure the first of the selected entry is visible in the
274         * views of this model.
275         */
276        public void ensureSelectedIsVisible() {
277            int index = selectionModel.getMinSelectionIndex();
278            if (index < 0)
279                return;
280            if (index >= getRowCount())
281                return;
282            tblStyles.scrollToVisible(index, 0);
283            tblStyles.repaint();
284        }
285
286        @Override
287        public void mapPaintStylesUpdated() {
288            data = new ArrayList<>(MapPaintStyles.getStyles().getStyleSources());
289            fireTableDataChanged();
290            tblStyles.repaint();
291        }
292
293        @Override
294        public void mapPaintStyleEntryUpdated(int idx) {
295            data = new ArrayList<>(MapPaintStyles.getStyles().getStyleSources());
296            fireTableRowsUpdated(idx, idx);
297            tblStyles.repaint();
298        }
299    }
300
301    private class MyCheckBoxRenderer extends JCheckBox implements TableCellRenderer {
302
303        /**
304         * Constructs a new {@code MyCheckBoxRenderer}.
305         */
306        MyCheckBoxRenderer() {
307            setHorizontalAlignment(SwingConstants.CENTER);
308            setVerticalAlignment(SwingConstants.CENTER);
309        }
310
311        @Override
312        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
313            if (value == null)
314                return this;
315            boolean b = (Boolean) value;
316            setSelected(b);
317            setEnabled(!cbWireframe.isSelected());
318            return this;
319        }
320    }
321
322    private class StyleSourceRenderer extends DefaultTableCellRenderer {
323        @Override
324        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
325            if (value == null)
326                return this;
327            StyleSource s = (StyleSource) value;
328            JLabel label = (JLabel) super.getTableCellRendererComponent(table,
329                    s.getDisplayString(), isSelected, hasFocus, row, column);
330            label.setIcon(s.getIcon());
331            label.setToolTipText(s.getToolTipText());
332            label.setEnabled(!cbWireframe.isSelected());
333            return label;
334        }
335    }
336
337    protected class OnOffAction extends AbstractAction implements ListSelectionListener {
338        /**
339         * Constructs a new {@code OnOffAction}.
340         */
341        public OnOffAction() {
342            putValue(NAME, tr("On/Off"));
343            putValue(SHORT_DESCRIPTION, tr("Turn selected styles on or off"));
344            new ImageProvider("apply").getResource().attachImageIcon(this, true);
345            updateEnabledState();
346        }
347
348        protected void updateEnabledState() {
349            setEnabled(!cbWireframe.isSelected() && tblStyles.getSelectedRowCount() > 0);
350        }
351
352        @Override
353        public void valueChanged(ListSelectionEvent e) {
354            updateEnabledState();
355        }
356
357        @Override
358        public void actionPerformed(ActionEvent e) {
359            int[] pos = tblStyles.getSelectedRows();
360            MapPaintStyles.toggleStyleActive(pos);
361            selectionModel.clearSelection();
362            for (int p: pos) {
363                selectionModel.addSelectionInterval(p, p);
364            }
365        }
366    }
367
368    /**
369     * The action to move down the currently selected entries in the list.
370     */
371    protected class MoveUpDownAction extends AbstractAction implements ListSelectionListener {
372
373        private final int increment;
374
375        /**
376         * Constructs a new {@code MoveUpDownAction}.
377         * @param isDown {@code true} to move the entry down, {@code false} to move it up
378         */
379        public MoveUpDownAction(boolean isDown) {
380            increment = isDown ? 1 : -1;
381            putValue(NAME, isDown ? tr("Down") : tr("Up"));
382            new ImageProvider("dialogs", isDown ? "down" : "up").getResource().attachImageIcon(this, true);
383            putValue(SHORT_DESCRIPTION, isDown ? tr("Move the selected entry one row down.") : tr("Move the selected entry one row up."));
384            updateEnabledState();
385        }
386
387        public void updateEnabledState() {
388            int[] sel = tblStyles.getSelectedRows();
389            setEnabled(!cbWireframe.isSelected() && MapPaintStyles.canMoveStyles(sel, increment));
390        }
391
392        @Override
393        public void actionPerformed(ActionEvent e) {
394            int[] sel = tblStyles.getSelectedRows();
395            MapPaintStyles.moveStyles(sel, increment);
396
397            selectionModel.clearSelection();
398            for (int row: sel) {
399                selectionModel.addSelectionInterval(row + increment, row + increment);
400            }
401            model.ensureSelectedIsVisible();
402        }
403
404        @Override
405        public void valueChanged(ListSelectionEvent e) {
406            updateEnabledState();
407        }
408    }
409
410    protected class ReloadAction extends AbstractAction implements ListSelectionListener {
411        /**
412         * Constructs a new {@code ReloadAction}.
413         */
414        public ReloadAction() {
415            putValue(NAME, tr("Reload from file"));
416            putValue(SHORT_DESCRIPTION, tr("reload selected styles from file"));
417            new ImageProvider("dialogs", "refresh").getResource().attachImageIcon(this);
418            setEnabled(getEnabledState());
419        }
420
421        protected boolean getEnabledState() {
422            if (cbWireframe.isSelected())
423                return false;
424            int[] pos = tblStyles.getSelectedRows();
425            if (pos.length == 0)
426                return false;
427            for (int i : pos) {
428                if (!model.getRow(i).isLocal())
429                    return false;
430            }
431            return true;
432        }
433
434        @Override
435        public void valueChanged(ListSelectionEvent e) {
436            setEnabled(getEnabledState());
437        }
438
439        @Override
440        public void actionPerformed(ActionEvent e) {
441            final int[] rows = tblStyles.getSelectedRows();
442            MapPaintStyles.reloadStyles(rows);
443            Main.worker.submit(new Runnable() {
444                @Override
445                public void run() {
446                    SwingUtilities.invokeLater(new Runnable() {
447                        @Override
448                        public void run() {
449                            selectionModel.clearSelection();
450                            for (int r: rows) {
451                                selectionModel.addSelectionInterval(r, r);
452                            }
453                        }
454                    });
455                }
456            });
457        }
458    }
459
460    protected class SaveAsAction extends AbstractAction {
461
462        /**
463         * Constructs a new {@code SaveAsAction}.
464         */
465        public SaveAsAction() {
466            putValue(NAME, tr("Save as..."));
467            putValue(SHORT_DESCRIPTION, tr("Save a copy of this Style to file and add it to the list"));
468            new ImageProvider("copy").getResource().attachImageIcon(this);
469            setEnabled(tblStyles.getSelectedRows().length == 1);
470        }
471
472        @Override
473        public void actionPerformed(ActionEvent e) {
474            int sel = tblStyles.getSelectionModel().getLeadSelectionIndex();
475            if (sel < 0 || sel >= model.getRowCount())
476                return;
477            final StyleSource s = model.getRow(sel);
478
479            FileChooserManager fcm = new FileChooserManager(false, "mappaint.clone-style.lastDirectory", System.getProperty("user.home"));
480            String suggestion = fcm.getInitialDirectory() + File.separator + s.getFileNamePart();
481
482            FileFilter ff;
483            if (s instanceof MapCSSStyleSource) {
484                ff = new ExtensionFileFilter("mapcss,css,zip", "mapcss", tr("Map paint style file (*.mapcss, *.zip)"));
485            } else {
486                ff = new ExtensionFileFilter("xml,zip", "xml", tr("Map paint style file (*.xml, *.zip)"));
487            }
488            fcm.createFileChooser(false, null, Arrays.asList(ff, FileFilterAllFiles.getInstance()), ff, JFileChooser.FILES_ONLY)
489                    .getFileChooser().setSelectedFile(new File(suggestion));
490            AbstractFileChooser fc = fcm.openFileChooser();
491            if (fc == null)
492                return;
493            Main.worker.submit(new SaveToFileTask(s, fc.getSelectedFile()));
494        }
495
496        private class SaveToFileTask extends PleaseWaitRunnable {
497            private final StyleSource s;
498            private final File file;
499
500            private boolean canceled;
501            private boolean error;
502
503            SaveToFileTask(StyleSource s, File file) {
504                super(tr("Reloading style sources"));
505                this.s = s;
506                this.file = file;
507            }
508
509            @Override
510            protected void cancel() {
511                canceled = true;
512            }
513
514            @Override
515            protected void realRun() {
516                getProgressMonitor().indeterminateSubTask(
517                        tr("Save style ''{0}'' as ''{1}''", s.getDisplayString(), file.getPath()));
518                try {
519                    InputStream in = s.getSourceInputStream();
520                    try (InputStream bis = new BufferedInputStream(in)) {
521                        Files.copy(bis, file.toPath(), StandardCopyOption.REPLACE_EXISTING);
522                    } finally {
523                        s.closeSourceInputStream(in);
524                    }
525                } catch (IOException e) {
526                    Main.warn(e);
527                    error = true;
528                }
529            }
530
531            @Override
532            protected void finish() {
533                SwingUtilities.invokeLater(new Runnable() {
534                    @Override
535                    public void run() {
536                        if (!error && !canceled) {
537                            SourceEntry se = new SourceEntry(s);
538                            se.url = file.getPath();
539                            MapPaintStyles.addStyle(se);
540                            tblStyles.getSelectionModel().setSelectionInterval(model.getRowCount() - 1, model.getRowCount() - 1);
541                            model.ensureSelectedIsVisible();
542                        }
543                    }
544                });
545            }
546        }
547    }
548
549    /**
550     * Displays information about selected paint style in a new dialog.
551     */
552    protected class InfoAction extends AbstractAction {
553
554        private boolean errorsTabLoaded;
555        private boolean warningsTabLoaded;
556        private boolean sourceTabLoaded;
557
558        /**
559         * Constructs a new {@code InfoAction}.
560         */
561        public InfoAction() {
562            putValue(NAME, tr("Info"));
563            putValue(SHORT_DESCRIPTION, tr("view meta information, error log and source definition"));
564            new ImageProvider("info").getResource().attachImageIcon(this);
565            setEnabled(tblStyles.getSelectedRows().length == 1);
566        }
567
568        @Override
569        public void actionPerformed(ActionEvent e) {
570            int sel = tblStyles.getSelectionModel().getLeadSelectionIndex();
571            if (sel < 0 || sel >= model.getRowCount())
572                return;
573            final StyleSource s = model.getRow(sel);
574            ExtendedDialog info = new ExtendedDialog(Main.parent, tr("Map Style info"), new String[] {tr("Close")});
575            info.setPreferredSize(new Dimension(600, 400));
576            info.setButtonIcons(new String[] {"ok.png"});
577
578            final JTabbedPane tabs = new JTabbedPane();
579
580            JLabel lblInfo = new JLabel(tr("Info"));
581            lblInfo.setLabelFor(tabs.add("Info", buildInfoPanel(s)));
582            lblInfo.setFont(lblInfo.getFont().deriveFont(Font.PLAIN));
583            tabs.setTabComponentAt(0, lblInfo);
584
585            final JPanel pErrors = addErrorOrWarningTab(tabs, lblInfo,
586                    s.getErrors(), marktr("Errors"), 1, ImageProvider.get("misc", "error"));
587            final JPanel pWarnings = addErrorOrWarningTab(tabs, lblInfo,
588                    s.getWarnings(), marktr("Warnings"), 2, ImageProvider.get("warning-small"));
589
590            final JPanel pSource = new JPanel(new GridBagLayout());
591            JLabel lblSource = new JLabel(tr("Source"));
592            lblSource.setLabelFor(tabs.add("Source", pSource));
593            lblSource.setFont(lblSource.getFont().deriveFont(Font.PLAIN));
594            tabs.setTabComponentAt(3, lblSource);
595
596            tabs.getModel().addChangeListener(new ChangeListener() {
597                @Override
598                public void stateChanged(ChangeEvent e) {
599                    if (!errorsTabLoaded && ((SingleSelectionModel) e.getSource()).getSelectedIndex() == 1) {
600                        errorsTabLoaded = true;
601                        buildErrorsOrWarningPanel(s.getErrors(), pErrors);
602                    }
603                    if (!warningsTabLoaded && ((SingleSelectionModel) e.getSource()).getSelectedIndex() == 2) {
604                        warningsTabLoaded = true;
605                        buildErrorsOrWarningPanel(s.getWarnings(), pWarnings);
606                    }
607                    if (!sourceTabLoaded && ((SingleSelectionModel) e.getSource()).getSelectedIndex() == 3) {
608                        sourceTabLoaded = true;
609                        buildSourcePanel(s, pSource);
610                    }
611                }
612            });
613            info.setContent(tabs, false);
614            info.showDialog();
615        }
616
617        private JPanel addErrorOrWarningTab(final JTabbedPane tabs, JLabel lblInfo,
618                Collection<?> items, String title, int pos, ImageIcon icon) {
619            final JPanel pErrors = new JPanel(new GridBagLayout());
620            tabs.add(title, pErrors);
621            if (items.isEmpty()) {
622                JLabel lblErrors = new JLabel(tr(title));
623                lblErrors.setLabelFor(pErrors);
624                lblErrors.setFont(lblInfo.getFont().deriveFont(Font.PLAIN));
625                lblErrors.setEnabled(false);
626                tabs.setTabComponentAt(pos, lblErrors);
627                tabs.setEnabledAt(pos, false);
628            } else {
629                JLabel lblErrors = new JLabel(tr(title), icon, JLabel.HORIZONTAL);
630                lblErrors.setLabelFor(pErrors);
631                tabs.setTabComponentAt(pos, lblErrors);
632            }
633            return pErrors;
634        }
635
636        private JPanel buildInfoPanel(StyleSource s) {
637            JPanel p = new JPanel(new GridBagLayout());
638            StringBuilder text = new StringBuilder("<table cellpadding=3>");
639            text.append(tableRow(tr("Title:"), s.getDisplayString()));
640            if (s.url.startsWith("http://") || s.url.startsWith("https://")) {
641                text.append(tableRow(tr("URL:"), s.url));
642            } else if (s.url.startsWith("resource://")) {
643                text.append(tableRow(tr("Built-in Style, internal path:"), s.url));
644            } else {
645                text.append(tableRow(tr("Path:"), s.url));
646            }
647            if (s.icon != null) {
648                text.append(tableRow(tr("Icon:"), s.icon));
649            }
650            if (s.getBackgroundColorOverride() != null) {
651                text.append(tableRow(tr("Background:"), Utils.toString(s.getBackgroundColorOverride())));
652            }
653            text.append(tableRow(tr("Style is currently active?"), s.active ? tr("Yes") : tr("No")))
654                .append("</table>");
655            p.add(new JScrollPane(new HtmlPanel(text.toString())), GBC.eol().fill(GBC.BOTH));
656            return p;
657        }
658
659        private String tableRow(String firstColumn, String secondColumn) {
660            return "<tr><td><b>" + firstColumn + "</b></td><td>" + secondColumn + "</td></tr>";
661        }
662
663        private void buildSourcePanel(StyleSource s, JPanel p) {
664            JosmTextArea txtSource = new JosmTextArea();
665            txtSource.setFont(GuiHelper.getMonospacedFont(txtSource));
666            txtSource.setEditable(false);
667            p.add(new JScrollPane(txtSource), GBC.std().fill());
668
669            try {
670                InputStream is = s.getSourceInputStream();
671                try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
672                    String line;
673                    while ((line = reader.readLine()) != null) {
674                        txtSource.append(line + '\n');
675                    }
676                } finally {
677                    s.closeSourceInputStream(is);
678                }
679            } catch (IOException ex) {
680                txtSource.append("<ERROR: failed to read file!>");
681            }
682        }
683
684        private <T> void buildErrorsOrWarningPanel(Collection<T> items, JPanel p) {
685            JosmTextArea txtErrors = new JosmTextArea();
686            txtErrors.setFont(GuiHelper.getMonospacedFont(txtErrors));
687            txtErrors.setEditable(false);
688            p.add(new JScrollPane(txtErrors), GBC.std().fill());
689            for (T t : items) {
690                txtErrors.append(t.toString() + '\n');
691            }
692        }
693    }
694
695    class PopupMenuHandler extends PopupMenuLauncher {
696        @Override
697        public void launch(MouseEvent evt) {
698            if (cbWireframe.isSelected())
699                return;
700            super.launch(evt);
701        }
702
703        @Override
704        protected void showMenu(MouseEvent evt) {
705            menu = new MapPaintPopup();
706            super.showMenu(evt);
707        }
708    }
709
710    /**
711     * The popup menu displayed when right-clicking a map paint entry
712     */
713    public class MapPaintPopup extends JPopupMenu {
714        /**
715         * Constructs a new {@code MapPaintPopup}.
716         */
717        public MapPaintPopup() {
718            add(reloadAction);
719            add(new SaveAsAction());
720
721            JMenu setMenu = new JMenu(tr("Style settings"));
722            setMenu.setIcon(new ImageProvider("preference").setMaxSize(ImageSizes.POPUPMENU).addOverlay(
723                new ImageOverlay(new ImageProvider("dialogs/mappaint", "pencil"), 0.5, 0.5, 1.0, 1.0)).get());
724            setMenu.setToolTipText(tr("Customize the style"));
725            add(setMenu);
726
727            int sel = tblStyles.getSelectionModel().getLeadSelectionIndex();
728            StyleSource style = null;
729            if (sel >= 0 && sel < model.getRowCount()) {
730                style = model.getRow(sel);
731            }
732            if (style == null || style.settings.isEmpty()) {
733                setMenu.setEnabled(false);
734            } else {
735                for (StyleSetting s : style.settings) {
736                    s.addMenuEntry(setMenu);
737                }
738            }
739
740            addSeparator();
741            add(new InfoAction());
742        }
743    }
744}