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