001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.dialogs; 003 004import static org.openstreetmap.josm.tools.I18n.marktr; 005import static org.openstreetmap.josm.tools.I18n.tr; 006 007import java.awt.Component; 008import java.awt.Dimension; 009import java.awt.Font; 010import java.awt.GridBagLayout; 011import java.awt.Insets; 012import java.awt.event.ActionEvent; 013import java.awt.event.KeyEvent; 014import java.awt.event.MouseEvent; 015import java.io.BufferedReader; 016import java.io.File; 017import java.io.IOException; 018import java.io.InputStream; 019import java.io.InputStreamReader; 020import java.nio.charset.StandardCharsets; 021import java.nio.file.Files; 022import java.nio.file.StandardCopyOption; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.Collection; 026import java.util.List; 027 028import javax.swing.AbstractAction; 029import javax.swing.DefaultListSelectionModel; 030import javax.swing.ImageIcon; 031import javax.swing.JCheckBox; 032import javax.swing.JFileChooser; 033import javax.swing.JLabel; 034import javax.swing.JMenu; 035import javax.swing.JPanel; 036import javax.swing.JScrollPane; 037import javax.swing.JTabbedPane; 038import javax.swing.JTable; 039import javax.swing.JToggleButton.ToggleButtonModel; 040import javax.swing.ListSelectionModel; 041import javax.swing.SingleSelectionModel; 042import javax.swing.SwingConstants; 043import javax.swing.SwingUtilities; 044import javax.swing.UIManager; 045import javax.swing.border.EmptyBorder; 046import javax.swing.event.ListSelectionEvent; 047import javax.swing.event.ListSelectionListener; 048import javax.swing.filechooser.FileFilter; 049import javax.swing.table.AbstractTableModel; 050import javax.swing.table.DefaultTableCellRenderer; 051import javax.swing.table.TableCellRenderer; 052 053import org.openstreetmap.josm.actions.ExtensionFileFilter; 054import org.openstreetmap.josm.actions.JosmAction; 055import org.openstreetmap.josm.actions.PreferencesAction; 056import org.openstreetmap.josm.data.preferences.sources.SourceEntry; 057import org.openstreetmap.josm.gui.ExtendedDialog; 058import org.openstreetmap.josm.gui.MainApplication; 059import org.openstreetmap.josm.gui.PleaseWaitRunnable; 060import org.openstreetmap.josm.gui.SideButton; 061import org.openstreetmap.josm.gui.mappaint.MapPaintStyles; 062import org.openstreetmap.josm.gui.mappaint.MapPaintStyles.MapPaintStylesUpdateListener; 063import org.openstreetmap.josm.gui.mappaint.StyleSettingGroupGui; 064import org.openstreetmap.josm.gui.mappaint.StyleSource; 065import org.openstreetmap.josm.gui.mappaint.loader.MapPaintStyleLoader; 066import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource; 067import org.openstreetmap.josm.gui.preferences.map.MapPaintPreference; 068import org.openstreetmap.josm.gui.util.FileFilterAllFiles; 069import org.openstreetmap.josm.gui.util.GuiHelper; 070import org.openstreetmap.josm.gui.util.StayOpenPopupMenu; 071import org.openstreetmap.josm.gui.util.TableHelper; 072import org.openstreetmap.josm.gui.widgets.AbstractFileChooser; 073import org.openstreetmap.josm.gui.widgets.FileChooserManager; 074import org.openstreetmap.josm.gui.widgets.HtmlPanel; 075import org.openstreetmap.josm.gui.widgets.JosmTextArea; 076import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher; 077import org.openstreetmap.josm.gui.widgets.ScrollableTable; 078import org.openstreetmap.josm.tools.ColorHelper; 079import org.openstreetmap.josm.tools.GBC; 080import org.openstreetmap.josm.tools.ImageOverlay; 081import org.openstreetmap.josm.tools.ImageProvider; 082import org.openstreetmap.josm.tools.ImageProvider.ImageSizes; 083import org.openstreetmap.josm.tools.InputMapUtils; 084import org.openstreetmap.josm.tools.Logging; 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 ScrollableTable 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.forPreferenceTab( 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("Windows: {0}", tr("Map Paint Styles")), 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 ToggleButtonModel() { 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 -> MainApplication.getMenu().wireFrameToggleAction.actionPerformed(null)); 139 cbWireframe.setBorder(new EmptyBorder(new Insets(1, 1, 1, 1))); 140 141 tblStyles = new ScrollableTable(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 @Override 182 public void showNotify() { 183 MapPaintStyles.addMapPaintStylesUpdateListener(model); 184 model.mapPaintStylesUpdated(); 185 MainApplication.getMenu().wireFrameToggleAction.addButtonModel(cbWireframe.getModel()); 186 } 187 188 @Override 189 public void hideNotify() { 190 MainApplication.getMenu().wireFrameToggleAction.removeButtonModel(cbWireframe.getModel()); 191 MapPaintStyles.removeMapPaintStylesUpdateListener(model); 192 } 193 194 protected class StylesModel extends AbstractTableModel implements MapPaintStylesUpdateListener { 195 196 private final Class<?>[] columnClasses = {Boolean.class, StyleSource.class}; 197 198 private transient List<StyleSource> data = new ArrayList<>(); 199 200 /** 201 * Constructs a new {@code StylesModel}. 202 */ 203 public StylesModel() { 204 data = new ArrayList<>(MapPaintStyles.getStyles().getStyleSources()); 205 } 206 207 private StyleSource getRow(int i) { 208 return data.get(i); 209 } 210 211 @Override 212 public int getColumnCount() { 213 return 2; 214 } 215 216 @Override 217 public int getRowCount() { 218 return data.size(); 219 } 220 221 @Override 222 public Object getValueAt(int row, int column) { 223 if (column == 0) 224 return getRow(row).active; 225 else 226 return getRow(row); 227 } 228 229 @Override 230 public boolean isCellEditable(int row, int column) { 231 return column == 0; 232 } 233 234 @Override 235 public Class<?> getColumnClass(int column) { 236 return columnClasses[column]; 237 } 238 239 @Override 240 public void setValueAt(Object aValue, int row, int column) { 241 if (row < 0 || row >= getRowCount() || aValue == null) 242 return; 243 if (column == 0) { 244 MapPaintStyles.toggleStyleActive(row); 245 } 246 } 247 248 /** 249 * Make sure the first of the selected entry is visible in the 250 * views of this model. 251 */ 252 public void ensureSelectedIsVisible() { 253 int index = selectionModel.getMinSelectionIndex(); 254 if (index < 0) 255 return; 256 if (index >= getRowCount()) 257 return; 258 tblStyles.scrollToVisible(index, 0); 259 tblStyles.repaint(); 260 } 261 262 @Override 263 public void mapPaintStylesUpdated() { 264 data = new ArrayList<>(MapPaintStyles.getStyles().getStyleSources()); 265 fireTableDataChanged(); 266 tblStyles.repaint(); 267 } 268 269 @Override 270 public void mapPaintStyleEntryUpdated(int idx) { 271 data = new ArrayList<>(MapPaintStyles.getStyles().getStyleSources()); 272 fireTableRowsUpdated(idx, idx); 273 tblStyles.repaint(); 274 } 275 } 276 277 private class MyCheckBoxRenderer extends JCheckBox implements TableCellRenderer { 278 279 /** 280 * Constructs a new {@code MyCheckBoxRenderer}. 281 */ 282 MyCheckBoxRenderer() { 283 setHorizontalAlignment(SwingConstants.CENTER); 284 setVerticalAlignment(SwingConstants.CENTER); 285 } 286 287 @Override 288 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { 289 if (value == null) 290 return this; 291 boolean b = (Boolean) value; 292 setSelected(b); 293 setEnabled(!cbWireframe.isSelected()); 294 return this; 295 } 296 } 297 298 private class StyleSourceRenderer extends DefaultTableCellRenderer { 299 @Override 300 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { 301 if (value == null) 302 return this; 303 StyleSource s = (StyleSource) value; 304 JLabel label = (JLabel) super.getTableCellRendererComponent(table, 305 s.getDisplayString(), isSelected, hasFocus, row, column); 306 label.setIcon(s.getIcon()); 307 label.setToolTipText(s.getToolTipText()); 308 label.setEnabled(!cbWireframe.isSelected()); 309 return label; 310 } 311 } 312 313 protected class OnOffAction extends AbstractAction implements ListSelectionListener { 314 /** 315 * Constructs a new {@code OnOffAction}. 316 */ 317 public OnOffAction() { 318 putValue(NAME, tr("On/Off")); 319 putValue(SHORT_DESCRIPTION, tr("Turn selected styles on or off")); 320 new ImageProvider("apply").getResource().attachImageIcon(this, true); 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 TableHelper.setSelectedIndices(selectionModel, Arrays.stream(pos)); 338 } 339 } 340 341 /** 342 * The action to move down the currently selected entries in the list. 343 */ 344 protected class MoveUpDownAction extends AbstractAction implements ListSelectionListener { 345 346 private final int increment; 347 348 /** 349 * Constructs a new {@code MoveUpDownAction}. 350 * @param isDown {@code true} to move the entry down, {@code false} to move it up 351 */ 352 public MoveUpDownAction(boolean isDown) { 353 increment = isDown ? 1 : -1; 354 putValue(NAME, isDown ? tr("Down") : tr("Up")); 355 new ImageProvider("dialogs", isDown ? "down" : "up").getResource().attachImageIcon(this, true); 356 putValue(SHORT_DESCRIPTION, isDown ? tr("Move the selected entry one row down.") : tr("Move the selected entry one row up.")); 357 updateEnabledState(); 358 } 359 360 public void updateEnabledState() { 361 int[] sel = tblStyles.getSelectedRows(); 362 setEnabled(!cbWireframe.isSelected() && MapPaintStyles.canMoveStyles(sel, increment)); 363 } 364 365 @Override 366 public void actionPerformed(ActionEvent e) { 367 int[] sel = tblStyles.getSelectedRows(); 368 MapPaintStyles.moveStyles(sel, increment); 369 TableHelper.setSelectedIndices(selectionModel, Arrays.stream(sel).map(row -> row + increment)); 370 model.ensureSelectedIsVisible(); 371 } 372 373 @Override 374 public void valueChanged(ListSelectionEvent e) { 375 updateEnabledState(); 376 } 377 } 378 379 protected class ReloadAction extends AbstractAction implements ListSelectionListener { 380 /** 381 * Constructs a new {@code ReloadAction}. 382 */ 383 public ReloadAction() { 384 putValue(NAME, tr("Reload from file")); 385 putValue(SHORT_DESCRIPTION, tr("reload selected styles from file")); 386 new ImageProvider("dialogs", "refresh").getResource().attachImageIcon(this); 387 setEnabled(getEnabledState()); 388 } 389 390 protected boolean getEnabledState() { 391 if (cbWireframe.isSelected()) 392 return false; 393 int[] pos = tblStyles.getSelectedRows(); 394 return pos.length > 0 && Arrays.stream(pos).allMatch(i -> model.getRow(i).isLocal()); 395 } 396 397 @Override 398 public void valueChanged(ListSelectionEvent e) { 399 setEnabled(getEnabledState()); 400 } 401 402 @Override 403 public void actionPerformed(ActionEvent e) { 404 final int[] rows = tblStyles.getSelectedRows(); 405 MapPaintStyleLoader.reloadStyles(rows); 406 MainApplication.worker.submit(() -> SwingUtilities.invokeLater(() -> 407 TableHelper.setSelectedIndices(selectionModel, Arrays.stream(rows)))); 408 } 409 } 410 411 protected class SaveAsAction extends AbstractAction { 412 413 /** 414 * Constructs a new {@code SaveAsAction}. 415 */ 416 public SaveAsAction() { 417 putValue(NAME, tr("Save as...")); 418 putValue(SHORT_DESCRIPTION, tr("Save a copy of this Style to file and add it to the list")); 419 new ImageProvider("copy").getResource().attachImageIcon(this); 420 setEnabled(tblStyles.getSelectedRows().length == 1); 421 } 422 423 @Override 424 public void actionPerformed(ActionEvent e) { 425 int sel = tblStyles.getSelectionModel().getLeadSelectionIndex(); 426 if (sel < 0 || sel >= model.getRowCount()) 427 return; 428 final StyleSource s = model.getRow(sel); 429 430 FileChooserManager fcm = new FileChooserManager(false, "mappaint.clone-style.lastDirectory", Utils.getSystemProperty("user.home")); 431 String suggestion = fcm.getInitialDirectory() + File.separator + s.getFileNamePart(); 432 433 FileFilter ff; 434 if (s instanceof MapCSSStyleSource) { 435 ff = new ExtensionFileFilter("mapcss,css,zip", "mapcss", tr("Map paint style file (*.mapcss, *.zip)")); 436 } else { 437 ff = new ExtensionFileFilter("xml,zip", "xml", tr("Map paint style file (*.xml, *.zip)")); 438 } 439 fcm.createFileChooser(false, null, Arrays.asList(ff, FileFilterAllFiles.getInstance()), ff, JFileChooser.FILES_ONLY) 440 .getFileChooser().setSelectedFile(new File(suggestion)); 441 AbstractFileChooser fc = fcm.openFileChooser(); 442 if (fc == null) 443 return; 444 MainApplication.worker.submit(new SaveToFileTask(s, fc.getSelectedFile())); 445 } 446 447 private class SaveToFileTask extends PleaseWaitRunnable { 448 private final StyleSource s; 449 private final File file; 450 451 private boolean canceled; 452 private boolean error; 453 454 SaveToFileTask(StyleSource s, File file) { 455 super(tr("Reloading style sources")); 456 this.s = s; 457 this.file = file; 458 } 459 460 @Override 461 protected void cancel() { 462 canceled = true; 463 } 464 465 @Override 466 protected void realRun() { 467 getProgressMonitor().indeterminateSubTask( 468 tr("Save style ''{0}'' as ''{1}''", s.getDisplayString(), file.getPath())); 469 try { 470 try (InputStream in = s.getSourceInputStream()) { 471 Files.copy(in, file.toPath(), StandardCopyOption.REPLACE_EXISTING); 472 } 473 } catch (IOException e) { 474 Logging.warn(e); 475 error = true; 476 } 477 } 478 479 @Override 480 protected void finish() { 481 SwingUtilities.invokeLater(() -> { 482 if (!error && !canceled) { 483 SourceEntry se = new SourceEntry(s); 484 se.url = file.getPath(); 485 MapPaintStyles.addStyle(se); 486 tblStyles.getSelectionModel().setSelectionInterval(model.getRowCount() - 1, model.getRowCount() - 1); 487 model.ensureSelectedIsVisible(); 488 } 489 }); 490 } 491 } 492 } 493 494 /** 495 * Displays information about selected paint style in a new dialog. 496 */ 497 protected class InfoAction extends AbstractAction { 498 499 private boolean errorsTabLoaded; 500 private boolean warningsTabLoaded; 501 private boolean sourceTabLoaded; 502 503 /** 504 * Constructs a new {@code InfoAction}. 505 */ 506 public InfoAction() { 507 putValue(NAME, tr("Info")); 508 putValue(SHORT_DESCRIPTION, tr("view meta information, error log and source definition")); 509 new ImageProvider("info").getResource().attachImageIcon(this); 510 setEnabled(tblStyles.getSelectedRows().length == 1); 511 } 512 513 @Override 514 public void actionPerformed(ActionEvent e) { 515 int sel = tblStyles.getSelectionModel().getLeadSelectionIndex(); 516 if (sel < 0 || sel >= model.getRowCount()) 517 return; 518 final StyleSource s = model.getRow(sel); 519 ExtendedDialog info = new ExtendedDialog(MainApplication.getMainFrame(), tr("Map Style info"), tr("Close")); 520 info.setPreferredSize(new Dimension(600, 400)); 521 info.setButtonIcons("ok"); 522 523 final JTabbedPane tabs = new JTabbedPane(); 524 525 JLabel lblInfo = new JLabel(tr("Info")); 526 lblInfo.setLabelFor(tabs.add("Info", buildInfoPanel(s))); 527 lblInfo.setFont(lblInfo.getFont().deriveFont(Font.PLAIN)); 528 tabs.setTabComponentAt(0, lblInfo); 529 530 final JPanel pErrors = addErrorOrWarningTab(tabs, lblInfo, 531 s.getErrors(), marktr("Errors"), 1, ImageProvider.get("misc", "error")); 532 final JPanel pWarnings = addErrorOrWarningTab(tabs, lblInfo, 533 s.getWarnings(), marktr("Warnings"), 2, ImageProvider.get("warning-small")); 534 535 final JPanel pSource = new JPanel(new GridBagLayout()); 536 JLabel lblSource = new JLabel(tr("Source")); 537 lblSource.setLabelFor(tabs.add("Source", pSource)); 538 lblSource.setFont(lblSource.getFont().deriveFont(Font.PLAIN)); 539 tabs.setTabComponentAt(3, lblSource); 540 541 tabs.getModel().addChangeListener(e1 -> { 542 if (!errorsTabLoaded && ((SingleSelectionModel) e1.getSource()).getSelectedIndex() == 1) { 543 errorsTabLoaded = true; 544 buildErrorsOrWarningPanel(s.getErrors(), pErrors); 545 } 546 if (!warningsTabLoaded && ((SingleSelectionModel) e1.getSource()).getSelectedIndex() == 2) { 547 warningsTabLoaded = true; 548 buildErrorsOrWarningPanel(s.getWarnings(), pWarnings); 549 } 550 if (!sourceTabLoaded && ((SingleSelectionModel) e1.getSource()).getSelectedIndex() == 3) { 551 sourceTabLoaded = true; 552 buildSourcePanel(s, pSource); 553 } 554 }); 555 info.setContent(tabs, false); 556 info.showDialog(); 557 } 558 559 private JPanel addErrorOrWarningTab(final JTabbedPane tabs, JLabel lblInfo, 560 Collection<?> items, String title, int pos, ImageIcon icon) { 561 final JPanel pErrors = new JPanel(new GridBagLayout()); 562 tabs.add(title, pErrors); 563 if (items.isEmpty()) { 564 JLabel lblErrors = new JLabel(tr(title)); 565 lblErrors.setLabelFor(pErrors); 566 lblErrors.setFont(lblInfo.getFont().deriveFont(Font.PLAIN)); 567 lblErrors.setEnabled(false); 568 tabs.setTabComponentAt(pos, lblErrors); 569 tabs.setEnabledAt(pos, false); 570 } else { 571 JLabel lblErrors = new JLabel(tr(title), icon, JLabel.HORIZONTAL); 572 lblErrors.setLabelFor(pErrors); 573 tabs.setTabComponentAt(pos, lblErrors); 574 } 575 return pErrors; 576 } 577 578 private JPanel buildInfoPanel(StyleSource s) { 579 JPanel p = new JPanel(new GridBagLayout()); 580 StringBuilder text = new StringBuilder("<table cellpadding=3>"); 581 text.append(tableRow(tr("Title:"), s.getDisplayString())); 582 if (s.url.startsWith("http://") || s.url.startsWith("https://")) { 583 text.append(tableRow(tr("URL:"), s.url)); 584 } else if (s.url.startsWith("resource://")) { 585 text.append(tableRow(tr("Built-in Style, internal path:"), s.url)); 586 } else { 587 text.append(tableRow(tr("Path:"), s.url)); 588 } 589 if (s.icon != null) { 590 text.append(tableRow(tr("Icon:"), s.icon)); 591 } 592 if (s.getBackgroundColorOverride() != null) { 593 text.append(tableRow(tr("Background:"), ColorHelper.color2html(s.getBackgroundColorOverride()))); 594 } 595 text.append(tableRow(tr("Style is currently active?"), s.active ? tr("Yes") : tr("No"))) 596 .append("</table>"); 597 p.add(new JScrollPane(new HtmlPanel(text.toString())), GBC.eol().fill(GBC.BOTH)); 598 return p; 599 } 600 601 private String tableRow(String firstColumn, String secondColumn) { 602 return "<tr><td><b>" + firstColumn + "</b></td><td>" + secondColumn + "</td></tr>"; 603 } 604 605 private void buildSourcePanel(StyleSource s, JPanel p) { 606 JosmTextArea txtSource = new JosmTextArea(); 607 txtSource.setFont(GuiHelper.getMonospacedFont(txtSource)); 608 txtSource.setEditable(false); 609 p.add(new JScrollPane(txtSource), GBC.std().fill()); 610 611 try (BufferedReader reader = new BufferedReader(new InputStreamReader(s.getSourceInputStream(), StandardCharsets.UTF_8))) { 612 reader.lines().forEach(line -> txtSource.append(line + '\n')); 613 } catch (IOException ex) { 614 Logging.error(ex); 615 txtSource.append("<ERROR: failed to read file!>"); 616 } 617 txtSource.setCaretPosition(0); 618 } 619 620 private <T> void buildErrorsOrWarningPanel(Collection<T> items, JPanel p) { 621 JosmTextArea txtErrors = new JosmTextArea(); 622 txtErrors.setFont(GuiHelper.getMonospacedFont(txtErrors)); 623 txtErrors.setEditable(false); 624 p.add(new JScrollPane(txtErrors), GBC.std().fill()); 625 for (T t : items) { 626 txtErrors.append(t.toString() + '\n'); 627 } 628 txtErrors.setCaretPosition(0); 629 } 630 } 631 632 class PopupMenuHandler extends PopupMenuLauncher { 633 @Override 634 public void launch(MouseEvent evt) { 635 if (cbWireframe.isSelected()) 636 return; 637 super.launch(evt); 638 } 639 640 @Override 641 protected void showMenu(MouseEvent evt) { 642 menu = new MapPaintPopup(); 643 super.showMenu(evt); 644 } 645 } 646 647 /** 648 * The popup menu displayed when right-clicking a map paint entry 649 */ 650 public class MapPaintPopup extends StayOpenPopupMenu { 651 /** 652 * Constructs a new {@code MapPaintPopup}. 653 */ 654 public MapPaintPopup() { 655 add(reloadAction); 656 add(new SaveAsAction()); 657 658 JMenu setMenu = new JMenu(tr("Style settings")); 659 setMenu.setIcon(new ImageProvider("dialogs/mapstyle").setMaxSize(ImageSizes.MENU).addOverlay( 660 new ImageOverlay(new ImageProvider("preference"), 0.25, 0.25, 1.0, 1.0)).get()); 661 setMenu.setToolTipText(tr("Customize the style")); 662 add(setMenu); 663 664 final int sel = tblStyles.getSelectionModel().getLeadSelectionIndex(); 665 final StyleSource style = sel >= 0 && sel < model.getRowCount() ? model.getRow(sel) : null; 666 if (style == null || Utils.isEmpty(style.settings)) { 667 setMenu.setEnabled(false); 668 } else { 669 // Add settings groups 670 style.settingGroups.forEach((group, settings) -> new StyleSettingGroupGui(group, settings).addMenuEntry(setMenu)); 671 // Add settings not in groups 672 style.settings.stream() 673 .filter(s -> style.settingGroups.values().stream().flatMap(List::stream).noneMatch(s::equals)) 674 .forEach(s -> s.getStyleSettingGui().addMenuEntry(setMenu)); 675 } 676 677 addSeparator(); 678 add(new InfoAction()); 679 } 680 } 681}