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