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