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