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