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