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