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