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