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