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.Color; 007import java.awt.Component; 008import java.awt.Dimension; 009import java.awt.Font; 010import java.awt.Point; 011import java.awt.Rectangle; 012import java.awt.event.ActionEvent; 013import java.awt.event.InputEvent; 014import java.awt.event.KeyEvent; 015import java.awt.event.MouseEvent; 016import java.beans.PropertyChangeEvent; 017import java.beans.PropertyChangeListener; 018import java.util.ArrayList; 019import java.util.Arrays; 020import java.util.Collections; 021import java.util.List; 022import java.util.concurrent.CopyOnWriteArrayList; 023 024import javax.swing.AbstractAction; 025import javax.swing.DefaultCellEditor; 026import javax.swing.DefaultListSelectionModel; 027import javax.swing.ImageIcon; 028import javax.swing.JCheckBox; 029import javax.swing.JComponent; 030import javax.swing.JLabel; 031import javax.swing.JTable; 032import javax.swing.JViewport; 033import javax.swing.KeyStroke; 034import javax.swing.ListSelectionModel; 035import javax.swing.UIManager; 036import javax.swing.event.ListDataEvent; 037import javax.swing.event.ListSelectionEvent; 038import javax.swing.event.ListSelectionListener; 039import javax.swing.event.TableModelEvent; 040import javax.swing.event.TableModelListener; 041import javax.swing.table.AbstractTableModel; 042import javax.swing.table.DefaultTableCellRenderer; 043import javax.swing.table.TableCellRenderer; 044import javax.swing.table.TableModel; 045 046import org.openstreetmap.josm.Main; 047import org.openstreetmap.josm.actions.MergeLayerAction; 048import org.openstreetmap.josm.gui.MapFrame; 049import org.openstreetmap.josm.gui.MapView; 050import org.openstreetmap.josm.gui.SideButton; 051import org.openstreetmap.josm.gui.dialogs.layer.ActivateLayerAction; 052import org.openstreetmap.josm.gui.dialogs.layer.DeleteLayerAction; 053import org.openstreetmap.josm.gui.dialogs.layer.DuplicateAction; 054import org.openstreetmap.josm.gui.dialogs.layer.IEnabledStateUpdating; 055import org.openstreetmap.josm.gui.dialogs.layer.LayerVisibilityAction; 056import org.openstreetmap.josm.gui.dialogs.layer.MergeAction; 057import org.openstreetmap.josm.gui.dialogs.layer.MoveDownAction; 058import org.openstreetmap.josm.gui.dialogs.layer.MoveUpAction; 059import org.openstreetmap.josm.gui.dialogs.layer.ShowHideLayerAction; 060import org.openstreetmap.josm.gui.layer.JumpToMarkerActions; 061import org.openstreetmap.josm.gui.layer.Layer; 062import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent; 063import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener; 064import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent; 065import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent; 066import org.openstreetmap.josm.gui.layer.MainLayerManager; 067import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent; 068import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener; 069import org.openstreetmap.josm.gui.layer.NativeScaleLayer; 070import org.openstreetmap.josm.gui.util.GuiHelper; 071import org.openstreetmap.josm.gui.widgets.DisableShortcutsOnFocusGainedTextField; 072import org.openstreetmap.josm.gui.widgets.JosmTextField; 073import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher; 074import org.openstreetmap.josm.tools.ImageProvider; 075import org.openstreetmap.josm.tools.InputMapUtils; 076import org.openstreetmap.josm.tools.MultikeyActionsHandler; 077import org.openstreetmap.josm.tools.MultikeyShortcutAction.MultikeyInfo; 078import org.openstreetmap.josm.tools.Shortcut; 079 080/** 081 * This is a toggle dialog which displays the list of layers. Actions allow to 082 * change the ordering of the layers, to hide/show layers, to activate layers, 083 * and to delete layers. 084 * <p> 085 * Support for multiple {@link LayerListDialog} is currently not complete but intended for the future. 086 * @since 17 087 */ 088public class LayerListDialog extends ToggleDialog { 089 /** the unique instance of the dialog */ 090 private static volatile LayerListDialog instance; 091 092 /** 093 * Creates the instance of the dialog. It's connected to the map frame <code>mapFrame</code> 094 * 095 * @param mapFrame the map frame 096 */ 097 public static void createInstance(MapFrame mapFrame) { 098 if (instance != null) 099 throw new IllegalStateException("Dialog was already created"); 100 instance = new LayerListDialog(mapFrame); 101 } 102 103 /** 104 * Replies the instance of the dialog 105 * 106 * @return the instance of the dialog 107 * @throws IllegalStateException if the dialog is not created yet 108 * @see #createInstance(MapFrame) 109 */ 110 public static LayerListDialog getInstance() { 111 if (instance == null) 112 throw new IllegalStateException("Dialog not created yet. Invoke createInstance() first"); 113 return instance; 114 } 115 116 /** the model for the layer list */ 117 private final LayerListModel model; 118 119 /** the list of layers (technically its a JTable, but appears like a list) */ 120 private final LayerList layerList; 121 122 private final ActivateLayerAction activateLayerAction; 123 private final ShowHideLayerAction showHideLayerAction; 124 125 //TODO This duplicates ShowHide actions functionality 126 /** stores which layer index to toggle and executes the ShowHide action if the layer is present */ 127 private final class ToggleLayerIndexVisibility extends AbstractAction { 128 private final int layerIndex; 129 130 ToggleLayerIndexVisibility(int layerIndex) { 131 this.layerIndex = layerIndex; 132 } 133 134 @Override 135 public void actionPerformed(ActionEvent e) { 136 final Layer l = model.getLayer(model.getRowCount() - layerIndex - 1); 137 if (l != null) { 138 l.toggleVisible(); 139 } 140 } 141 } 142 143 private final transient Shortcut[] visibilityToggleShortcuts = new Shortcut[10]; 144 private final ToggleLayerIndexVisibility[] visibilityToggleActions = new ToggleLayerIndexVisibility[10]; 145 146 /** 147 * The {@link MainLayerManager} this list is for. 148 */ 149 private final transient MainLayerManager layerManager; 150 151 /** 152 * registers (shortcut to toggle right hand side toggle dialogs)+(number keys) shortcuts 153 * to toggle the visibility of the first ten layers. 154 */ 155 private void createVisibilityToggleShortcuts() { 156 for (int i = 0; i < 10; i++) { 157 final int i1 = i + 1; 158 /* POSSIBLE SHORTCUTS: 1,2,3,4,5,6,7,8,9,0=10 */ 159 visibilityToggleShortcuts[i] = Shortcut.registerShortcut("subwindow:layers:toggleLayer" + i1, 160 tr("Toggle visibility of layer: {0}", i1), KeyEvent.VK_0 + (i1 % 10), Shortcut.ALT); 161 visibilityToggleActions[i] = new ToggleLayerIndexVisibility(i); 162 Main.registerActionShortcut(visibilityToggleActions[i], visibilityToggleShortcuts[i]); 163 } 164 } 165 166 /** 167 * Creates a layer list and attach it to the given mapView. 168 * @param mapFrame map frame 169 */ 170 protected LayerListDialog(MapFrame mapFrame) { 171 this(mapFrame.mapView.getLayerManager()); 172 } 173 174 /** 175 * Creates a layer list and attach it to the given mapView. 176 * @param layerManager The layer manager this list is for 177 * @since 10467 178 */ 179 public LayerListDialog(MainLayerManager layerManager) { 180 super(tr("Layers"), "layerlist", tr("Open a list of all loaded layers."), 181 Shortcut.registerShortcut("subwindow:layers", tr("Toggle: {0}", tr("Layers")), KeyEvent.VK_L, 182 Shortcut.ALT_SHIFT), 100, true); 183 this.layerManager = layerManager; 184 185 // create the models 186 // 187 DefaultListSelectionModel selectionModel = new DefaultListSelectionModel(); 188 selectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 189 model = new LayerListModel(layerManager, selectionModel); 190 191 // create the list control 192 // 193 layerList = new LayerList(model); 194 layerList.setSelectionModel(selectionModel); 195 layerList.addMouseListener(new PopupMenuHandler()); 196 layerList.setBackground(UIManager.getColor("Button.background")); 197 layerList.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE); 198 layerList.putClientProperty("JTable.autoStartsEdit", Boolean.FALSE); 199 layerList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 200 layerList.setTableHeader(null); 201 layerList.setShowGrid(false); 202 layerList.setIntercellSpacing(new Dimension(0, 0)); 203 layerList.getColumnModel().getColumn(0).setCellRenderer(new ActiveLayerCellRenderer()); 204 layerList.getColumnModel().getColumn(0).setCellEditor(new DefaultCellEditor(new ActiveLayerCheckBox())); 205 layerList.getColumnModel().getColumn(0).setMaxWidth(12); 206 layerList.getColumnModel().getColumn(0).setPreferredWidth(12); 207 layerList.getColumnModel().getColumn(0).setResizable(false); 208 209 layerList.getColumnModel().getColumn(1).setCellRenderer(new NativeScaleLayerCellRenderer()); 210 layerList.getColumnModel().getColumn(1).setCellEditor(new DefaultCellEditor(new NativeScaleLayerCheckBox())); 211 layerList.getColumnModel().getColumn(1).setMaxWidth(12); 212 layerList.getColumnModel().getColumn(1).setPreferredWidth(12); 213 layerList.getColumnModel().getColumn(1).setResizable(false); 214 215 layerList.getColumnModel().getColumn(2).setCellRenderer(new LayerVisibleCellRenderer()); 216 layerList.getColumnModel().getColumn(2).setCellEditor(new LayerVisibleCellEditor(new LayerVisibleCheckBox())); 217 layerList.getColumnModel().getColumn(2).setMaxWidth(16); 218 layerList.getColumnModel().getColumn(2).setPreferredWidth(16); 219 layerList.getColumnModel().getColumn(2).setResizable(false); 220 221 layerList.getColumnModel().getColumn(3).setCellRenderer(new LayerNameCellRenderer()); 222 layerList.getColumnModel().getColumn(3).setCellEditor(new LayerNameCellEditor(new DisableShortcutsOnFocusGainedTextField())); 223 // Disable some default JTable shortcuts to use JOSM ones (see #5678, #10458) 224 for (KeyStroke ks : new KeyStroke[] { 225 KeyStroke.getKeyStroke(KeyEvent.VK_C, GuiHelper.getMenuShortcutKeyMaskEx()), 226 KeyStroke.getKeyStroke(KeyEvent.VK_V, GuiHelper.getMenuShortcutKeyMaskEx()), 227 KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.SHIFT_DOWN_MASK), 228 KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.SHIFT_DOWN_MASK), 229 KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.SHIFT_DOWN_MASK), 230 KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.SHIFT_DOWN_MASK), 231 KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.CTRL_DOWN_MASK), 232 KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.CTRL_DOWN_MASK), 233 KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.CTRL_DOWN_MASK), 234 KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.CTRL_DOWN_MASK), 235 KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0), 236 KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0), 237 KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), 238 KeyStroke.getKeyStroke(KeyEvent.VK_F8, 0), 239 }) { 240 layerList.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(ks, new Object()); 241 } 242 243 // init the model 244 // 245 model.populate(); 246 model.setSelectedLayer(layerManager.getActiveLayer()); 247 model.addLayerListModelListener( 248 new LayerListModelListener() { 249 @Override 250 public void makeVisible(int row, Layer layer) { 251 layerList.scrollToVisible(row, 0); 252 layerList.repaint(); 253 } 254 255 @Override 256 public void refresh() { 257 layerList.repaint(); 258 } 259 } 260 ); 261 262 // -- move up action 263 MoveUpAction moveUpAction = new MoveUpAction(model); 264 adaptTo(moveUpAction, model); 265 adaptTo(moveUpAction, selectionModel); 266 267 // -- move down action 268 MoveDownAction moveDownAction = new MoveDownAction(model); 269 adaptTo(moveDownAction, model); 270 adaptTo(moveDownAction, selectionModel); 271 272 // -- activate action 273 activateLayerAction = new ActivateLayerAction(model); 274 activateLayerAction.updateEnabledState(); 275 MultikeyActionsHandler.getInstance().addAction(activateLayerAction); 276 adaptTo(activateLayerAction, selectionModel); 277 278 JumpToMarkerActions.initialize(); 279 280 // -- show hide action 281 showHideLayerAction = new ShowHideLayerAction(model); 282 MultikeyActionsHandler.getInstance().addAction(showHideLayerAction); 283 adaptTo(showHideLayerAction, selectionModel); 284 285 LayerVisibilityAction visibilityAction = new LayerVisibilityAction(model); 286 adaptTo(visibilityAction, selectionModel); 287 SideButton visibilityButton = new SideButton(visibilityAction, false); 288 visibilityAction.setCorrespondingSideButton(visibilityButton); 289 290 // -- delete layer action 291 DeleteLayerAction deleteLayerAction = new DeleteLayerAction(model); 292 layerList.getActionMap().put("deleteLayer", deleteLayerAction); 293 adaptTo(deleteLayerAction, selectionModel); 294 getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put( 295 KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "delete" 296 ); 297 getActionMap().put("delete", deleteLayerAction); 298 299 // Activate layer on Enter key press 300 InputMapUtils.addEnterAction(layerList, new AbstractAction() { 301 @Override 302 public void actionPerformed(ActionEvent e) { 303 activateLayerAction.actionPerformed(null); 304 layerList.requestFocus(); 305 } 306 }); 307 308 // Show/Activate layer on Enter key press 309 InputMapUtils.addSpacebarAction(layerList, showHideLayerAction); 310 311 createLayout(layerList, true, Arrays.asList( 312 new SideButton(moveUpAction, false), 313 new SideButton(moveDownAction, false), 314 new SideButton(activateLayerAction, false), 315 visibilityButton, 316 new SideButton(deleteLayerAction, false) 317 )); 318 319 createVisibilityToggleShortcuts(); 320 } 321 322 /** 323 * Gets the layer manager this dialog is for. 324 * @return The layer manager. 325 * @since 10288 326 */ 327 public MainLayerManager getLayerManager() { 328 return layerManager; 329 } 330 331 @Override 332 public void showNotify() { 333 layerManager.addActiveLayerChangeListener(activateLayerAction); 334 layerManager.addLayerChangeListener(model, true); 335 layerManager.addAndFireActiveLayerChangeListener(model); 336 model.populate(); 337 } 338 339 @Override 340 public void hideNotify() { 341 layerManager.removeLayerChangeListener(model, true); 342 layerManager.removeActiveLayerChangeListener(model); 343 layerManager.removeActiveLayerChangeListener(activateLayerAction); 344 } 345 346 /** 347 * Returns the layer list model. 348 * @return the layer list model 349 */ 350 public LayerListModel getModel() { 351 return model; 352 } 353 354 /** 355 * Wires <code>listener</code> to <code>listSelectionModel</code> in such a way, that 356 * <code>listener</code> receives a {@link IEnabledStateUpdating#updateEnabledState()} 357 * on every {@link ListSelectionEvent}. 358 * 359 * @param listener the listener 360 * @param listSelectionModel the source emitting {@link ListSelectionEvent}s 361 */ 362 protected void adaptTo(final IEnabledStateUpdating listener, ListSelectionModel listSelectionModel) { 363 listSelectionModel.addListSelectionListener( 364 new ListSelectionListener() { 365 @Override 366 public void valueChanged(ListSelectionEvent e) { 367 listener.updateEnabledState(); 368 } 369 } 370 ); 371 } 372 373 /** 374 * Wires <code>listener</code> to <code>listModel</code> in such a way, that 375 * <code>listener</code> receives a {@link IEnabledStateUpdating#updateEnabledState()} 376 * on every {@link ListDataEvent}. 377 * 378 * @param listener the listener 379 * @param listModel the source emitting {@link ListDataEvent}s 380 */ 381 protected void adaptTo(final IEnabledStateUpdating listener, LayerListModel listModel) { 382 listModel.addTableModelListener( 383 new TableModelListener() { 384 @Override 385 public void tableChanged(TableModelEvent e) { 386 listener.updateEnabledState(); 387 } 388 } 389 ); 390 } 391 392 @Override 393 public void destroy() { 394 for (int i = 0; i < 10; i++) { 395 Main.unregisterActionShortcut(visibilityToggleActions[i], visibilityToggleShortcuts[i]); 396 } 397 MultikeyActionsHandler.getInstance().removeAction(activateLayerAction); 398 MultikeyActionsHandler.getInstance().removeAction(showHideLayerAction); 399 JumpToMarkerActions.unregisterActions(); 400 super.destroy(); 401 instance = null; 402 } 403 404 private static class ActiveLayerCheckBox extends JCheckBox { 405 ActiveLayerCheckBox() { 406 setHorizontalAlignment(javax.swing.SwingConstants.CENTER); 407 ImageIcon blank = ImageProvider.get("dialogs/layerlist", "blank"); 408 ImageIcon active = ImageProvider.get("dialogs/layerlist", "active"); 409 setIcon(blank); 410 setSelectedIcon(active); 411 setRolloverIcon(blank); 412 setRolloverSelectedIcon(active); 413 setPressedIcon(ImageProvider.get("dialogs/layerlist", "active-pressed")); 414 } 415 } 416 417 private static class LayerVisibleCheckBox extends JCheckBox { 418 private final ImageIcon iconEye; 419 private final ImageIcon iconEyeTranslucent; 420 private boolean isTranslucent; 421 422 /** 423 * Constructs a new {@code LayerVisibleCheckBox}. 424 */ 425 LayerVisibleCheckBox() { 426 setHorizontalAlignment(javax.swing.SwingConstants.RIGHT); 427 iconEye = ImageProvider.get("dialogs/layerlist", "eye"); 428 iconEyeTranslucent = ImageProvider.get("dialogs/layerlist", "eye-translucent"); 429 setIcon(ImageProvider.get("dialogs/layerlist", "eye-off")); 430 setPressedIcon(ImageProvider.get("dialogs/layerlist", "eye-pressed")); 431 setSelectedIcon(iconEye); 432 isTranslucent = false; 433 } 434 435 public void setTranslucent(boolean isTranslucent) { 436 if (this.isTranslucent == isTranslucent) return; 437 if (isTranslucent) { 438 setSelectedIcon(iconEyeTranslucent); 439 } else { 440 setSelectedIcon(iconEye); 441 } 442 this.isTranslucent = isTranslucent; 443 } 444 445 public void updateStatus(Layer layer) { 446 boolean visible = layer.isVisible(); 447 setSelected(visible); 448 setTranslucent(layer.getOpacity() < 1.0); 449 setToolTipText(visible ? 450 tr("layer is currently visible (click to hide layer)") : 451 tr("layer is currently hidden (click to show layer)")); 452 } 453 } 454 455 private static class NativeScaleLayerCheckBox extends JCheckBox { 456 NativeScaleLayerCheckBox() { 457 setHorizontalAlignment(javax.swing.SwingConstants.CENTER); 458 ImageIcon blank = ImageProvider.get("dialogs/layerlist", "blank"); 459 ImageIcon active = ImageProvider.get("dialogs/layerlist", "scale"); 460 setIcon(blank); 461 setSelectedIcon(active); 462 } 463 } 464 465 private static class ActiveLayerCellRenderer implements TableCellRenderer { 466 private final JCheckBox cb; 467 468 /** 469 * Constructs a new {@code ActiveLayerCellRenderer}. 470 */ 471 ActiveLayerCellRenderer() { 472 cb = new ActiveLayerCheckBox(); 473 } 474 475 @Override 476 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { 477 boolean active = value != null && (Boolean) value; 478 cb.setSelected(active); 479 cb.setToolTipText(active ? tr("this layer is the active layer") : tr("this layer is not currently active (click to activate)")); 480 return cb; 481 } 482 } 483 484 private static class LayerVisibleCellRenderer implements TableCellRenderer { 485 private final LayerVisibleCheckBox cb; 486 487 /** 488 * Constructs a new {@code LayerVisibleCellRenderer}. 489 */ 490 LayerVisibleCellRenderer() { 491 this.cb = new LayerVisibleCheckBox(); 492 } 493 494 @Override 495 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { 496 if (value != null) { 497 cb.updateStatus((Layer) value); 498 } 499 return cb; 500 } 501 } 502 503 private static class LayerVisibleCellEditor extends DefaultCellEditor { 504 private final LayerVisibleCheckBox cb; 505 506 LayerVisibleCellEditor(LayerVisibleCheckBox cb) { 507 super(cb); 508 this.cb = cb; 509 } 510 511 @Override 512 public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { 513 cb.updateStatus((Layer) value); 514 return cb; 515 } 516 } 517 518 private static class NativeScaleLayerCellRenderer implements TableCellRenderer { 519 private final JCheckBox cb; 520 521 /** 522 * Constructs a new {@code ActiveLayerCellRenderer}. 523 */ 524 NativeScaleLayerCellRenderer() { 525 cb = new NativeScaleLayerCheckBox(); 526 } 527 528 @Override 529 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { 530 Layer layer = (Layer) value; 531 if (layer instanceof NativeScaleLayer) { 532 boolean active = ((NativeScaleLayer) layer) == Main.map.mapView.getNativeScaleLayer(); 533 cb.setSelected(active); 534 cb.setToolTipText(active 535 ? tr("scale follows native resolution of this layer") 536 : tr("scale follows native resolution of another layer (click to set this layer)") 537 ); 538 } else { 539 cb.setSelected(false); 540 cb.setToolTipText(tr("this layer has no native resolution")); 541 } 542 return cb; 543 } 544 } 545 546 private class LayerNameCellRenderer extends DefaultTableCellRenderer { 547 548 protected boolean isActiveLayer(Layer layer) { 549 return getLayerManager().getActiveLayer() == layer; 550 } 551 552 @Override 553 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { 554 if (value == null) 555 return this; 556 Layer layer = (Layer) value; 557 JLabel label = (JLabel) super.getTableCellRendererComponent(table, 558 layer.getName(), isSelected, hasFocus, row, column); 559 if (isActiveLayer(layer)) { 560 label.setFont(label.getFont().deriveFont(Font.BOLD)); 561 } 562 if (Main.pref.getBoolean("dialog.layer.colorname", true)) { 563 Color c = layer.getColor(false); 564 if (c != null) { 565 Color oc = null; 566 for (Layer l : model.getLayers()) { 567 oc = l.getColor(false); 568 if (oc != null) { 569 if (oc.equals(c)) { 570 oc = null; 571 } else { 572 break; 573 } 574 } 575 } 576 /* not more than one color, don't use coloring */ 577 if (oc == null) { 578 c = null; 579 } 580 } 581 if (c == null) { 582 c = UIManager.getColor(isSelected ? "Table.selectionForeground" : "Table.foreground"); 583 } 584 label.setForeground(c); 585 } 586 label.setIcon(layer.getIcon()); 587 label.setToolTipText(layer.getToolTipText()); 588 return label; 589 } 590 } 591 592 private static class LayerNameCellEditor extends DefaultCellEditor { 593 LayerNameCellEditor(DisableShortcutsOnFocusGainedTextField tf) { 594 super(tf); 595 } 596 597 @Override 598 public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { 599 JosmTextField tf = (JosmTextField) super.getTableCellEditorComponent(table, value, isSelected, row, column); 600 tf.setText(value == null ? "" : ((Layer) value).getName()); 601 return tf; 602 } 603 } 604 605 class PopupMenuHandler extends PopupMenuLauncher { 606 @Override 607 public void showMenu(MouseEvent evt) { 608 menu = new LayerListPopup(getModel().getSelectedLayers()); 609 super.showMenu(evt); 610 } 611 } 612 613 /** 614 * Observer interface to be implemented by views using {@link LayerListModel}. 615 */ 616 public interface LayerListModelListener { 617 618 /** 619 * Fired when a layer is made visible. 620 * @param index the layer index 621 * @param layer the layer 622 */ 623 void makeVisible(int index, Layer layer); 624 625 626 /** 627 * Fired when something has changed in the layer list model. 628 */ 629 void refresh(); 630 } 631 632 /** 633 * The layer list model. The model manages a list of layers and provides methods for 634 * moving layers up and down, for toggling their visibility, and for activating a layer. 635 * 636 * The model is a {@link TableModel} and it provides a {@link ListSelectionModel}. It expects 637 * to be configured with a {@link DefaultListSelectionModel}. The selection model is used 638 * to update the selection state of views depending on messages sent to the model. 639 * 640 * The model manages a list of {@link LayerListModelListener} which are mainly notified if 641 * the model requires views to make a specific list entry visible. 642 * 643 * It also listens to {@link PropertyChangeEvent}s of every {@link Layer} it manages, in particular to 644 * the properties {@link Layer#VISIBLE_PROP} and {@link Layer#NAME_PROP}. 645 */ 646 public static final class LayerListModel extends AbstractTableModel 647 implements LayerChangeListener, ActiveLayerChangeListener, PropertyChangeListener { 648 /** manages list selection state*/ 649 private final DefaultListSelectionModel selectionModel; 650 private final CopyOnWriteArrayList<LayerListModelListener> listeners; 651 private LayerList layerList; 652 private final MainLayerManager layerManager; 653 654 /** 655 * constructor 656 * @param layerManager The layer manager to use for the list. 657 * @param selectionModel the list selection model 658 */ 659 LayerListModel(MainLayerManager layerManager, DefaultListSelectionModel selectionModel) { 660 this.layerManager = layerManager; 661 this.selectionModel = selectionModel; 662 listeners = new CopyOnWriteArrayList<>(); 663 } 664 665 void setLayerList(LayerList layerList) { 666 this.layerList = layerList; 667 } 668 669 /** 670 * The layer manager this model is for. 671 * @return The layer manager. 672 */ 673 public MainLayerManager getLayerManager() { 674 return layerManager; 675 } 676 677 /** 678 * Adds a listener to this model 679 * 680 * @param listener the listener 681 */ 682 public void addLayerListModelListener(LayerListModelListener listener) { 683 if (listener != null) { 684 listeners.addIfAbsent(listener); 685 } 686 } 687 688 /** 689 * removes a listener from this model 690 * @param listener the listener 691 */ 692 public void removeLayerListModelListener(LayerListModelListener listener) { 693 listeners.remove(listener); 694 } 695 696 /** 697 * Fires a make visible event to listeners 698 * 699 * @param index the index of the row to make visible 700 * @param layer the layer at this index 701 * @see LayerListModelListener#makeVisible(int, Layer) 702 */ 703 protected void fireMakeVisible(int index, Layer layer) { 704 for (LayerListModelListener listener : listeners) { 705 listener.makeVisible(index, layer); 706 } 707 } 708 709 /** 710 * Fires a refresh event to listeners of this model 711 * 712 * @see LayerListModelListener#refresh() 713 */ 714 protected void fireRefresh() { 715 for (LayerListModelListener listener : listeners) { 716 listener.refresh(); 717 } 718 } 719 720 /** 721 * Populates the model with the current layers managed by {@link MapView}. 722 */ 723 public void populate() { 724 for (Layer layer: getLayers()) { 725 // make sure the model is registered exactly once 726 layer.removePropertyChangeListener(this); 727 layer.addPropertyChangeListener(this); 728 } 729 fireTableDataChanged(); 730 } 731 732 /** 733 * Marks <code>layer</code> as selected layer. Ignored, if layer is null. 734 * 735 * @param layer the layer. 736 */ 737 public void setSelectedLayer(Layer layer) { 738 if (layer == null) 739 return; 740 int idx = getLayers().indexOf(layer); 741 if (idx >= 0) { 742 selectionModel.setSelectionInterval(idx, idx); 743 } 744 ensureSelectedIsVisible(); 745 } 746 747 /** 748 * Replies the list of currently selected layers. Never null, but may be empty. 749 * 750 * @return the list of currently selected layers. Never null, but may be empty. 751 */ 752 public List<Layer> getSelectedLayers() { 753 List<Layer> selected = new ArrayList<>(); 754 List<Layer> layers = getLayers(); 755 for (int i = 0; i < layers.size(); i++) { 756 if (selectionModel.isSelectedIndex(i)) { 757 selected.add(layers.get(i)); 758 } 759 } 760 return selected; 761 } 762 763 /** 764 * Replies a the list of indices of the selected rows. Never null, but may be empty. 765 * 766 * @return the list of indices of the selected rows. Never null, but may be empty. 767 */ 768 public List<Integer> getSelectedRows() { 769 List<Integer> selected = new ArrayList<>(); 770 for (int i = 0; i < getLayers().size(); i++) { 771 if (selectionModel.isSelectedIndex(i)) { 772 selected.add(i); 773 } 774 } 775 return selected; 776 } 777 778 /** 779 * Invoked if a layer managed by {@link MapView} is removed 780 * 781 * @param layer the layer which is removed 782 */ 783 private void onRemoveLayer(Layer layer) { 784 if (layer == null) 785 return; 786 layer.removePropertyChangeListener(this); 787 final int size = getRowCount(); 788 final List<Integer> rows = getSelectedRows(); 789 790 if (rows.isEmpty() && size > 0) { 791 selectionModel.setSelectionInterval(size-1, size-1); 792 } 793 fireTableDataChanged(); 794 fireRefresh(); 795 ensureActiveSelected(); 796 } 797 798 /** 799 * Invoked when a layer managed by {@link MapView} is added 800 * 801 * @param layer the layer 802 */ 803 private void onAddLayer(Layer layer) { 804 if (layer == null) 805 return; 806 layer.addPropertyChangeListener(this); 807 fireTableDataChanged(); 808 int idx = getLayers().indexOf(layer); 809 if (layerList != null) { 810 layerList.setRowHeight(idx, Math.max(16, layer.getIcon().getIconHeight())); 811 } 812 selectionModel.setSelectionInterval(idx, idx); 813 ensureSelectedIsVisible(); 814 } 815 816 /** 817 * Replies the first layer. Null if no layers are present 818 * 819 * @return the first layer. Null if no layers are present 820 */ 821 public Layer getFirstLayer() { 822 if (getRowCount() == 0) 823 return null; 824 return getLayers().get(0); 825 } 826 827 /** 828 * Replies the layer at position <code>index</code> 829 * 830 * @param index the index 831 * @return the layer at position <code>index</code>. Null, 832 * if index is out of range. 833 */ 834 public Layer getLayer(int index) { 835 if (index < 0 || index >= getRowCount()) 836 return null; 837 return getLayers().get(index); 838 } 839 840 /** 841 * Replies true if the currently selected layers can move up by one position 842 * 843 * @return true if the currently selected layers can move up by one position 844 */ 845 public boolean canMoveUp() { 846 List<Integer> sel = getSelectedRows(); 847 return !sel.isEmpty() && sel.get(0) > 0; 848 } 849 850 /** 851 * Move up the currently selected layers by one position 852 * 853 */ 854 public void moveUp() { 855 if (!canMoveUp()) 856 return; 857 List<Integer> sel = getSelectedRows(); 858 List<Layer> layers = getLayers(); 859 for (int row : sel) { 860 Layer l1 = layers.get(row); 861 Layer l2 = layers.get(row-1); 862 Main.map.mapView.moveLayer(l2, row); 863 Main.map.mapView.moveLayer(l1, row-1); 864 } 865 fireTableDataChanged(); 866 selectionModel.clearSelection(); 867 for (int row : sel) { 868 selectionModel.addSelectionInterval(row-1, row-1); 869 } 870 ensureSelectedIsVisible(); 871 } 872 873 /** 874 * Replies true if the currently selected layers can move down by one position 875 * 876 * @return true if the currently selected layers can move down by one position 877 */ 878 public boolean canMoveDown() { 879 List<Integer> sel = getSelectedRows(); 880 return !sel.isEmpty() && sel.get(sel.size()-1) < getLayers().size()-1; 881 } 882 883 /** 884 * Move down the currently selected layers by one position 885 */ 886 public void moveDown() { 887 if (!canMoveDown()) 888 return; 889 List<Integer> sel = getSelectedRows(); 890 Collections.reverse(sel); 891 List<Layer> layers = getLayers(); 892 for (int row : sel) { 893 Layer l1 = layers.get(row); 894 Layer l2 = layers.get(row+1); 895 Main.map.mapView.moveLayer(l1, row+1); 896 Main.map.mapView.moveLayer(l2, row); 897 } 898 fireTableDataChanged(); 899 selectionModel.clearSelection(); 900 for (int row : sel) { 901 selectionModel.addSelectionInterval(row+1, row+1); 902 } 903 ensureSelectedIsVisible(); 904 } 905 906 /** 907 * Make sure the first of the selected layers is visible in the views of this model. 908 */ 909 protected void ensureSelectedIsVisible() { 910 int index = selectionModel.getMinSelectionIndex(); 911 if (index < 0) 912 return; 913 List<Layer> layers = getLayers(); 914 if (index >= layers.size()) 915 return; 916 Layer layer = layers.get(index); 917 fireMakeVisible(index, layer); 918 } 919 920 /** 921 * Replies a list of layers which are possible merge targets for <code>source</code> 922 * 923 * @param source the source layer 924 * @return a list of layers which are possible merge targets 925 * for <code>source</code>. Never null, but can be empty. 926 */ 927 public List<Layer> getPossibleMergeTargets(Layer source) { 928 List<Layer> targets = new ArrayList<>(); 929 if (source == null) { 930 return targets; 931 } 932 for (Layer target : getLayers()) { 933 if (source == target) { 934 continue; 935 } 936 if (target.isMergable(source) && source.isMergable(target)) { 937 targets.add(target); 938 } 939 } 940 return targets; 941 } 942 943 /** 944 * Replies the list of layers currently managed by {@link MapView}. 945 * Never null, but can be empty. 946 * 947 * @return the list of layers currently managed by {@link MapView}. 948 * Never null, but can be empty. 949 */ 950 public List<Layer> getLayers() { 951 return getLayerManager().getLayers(); 952 } 953 954 /** 955 * Ensures that at least one layer is selected in the layer dialog 956 * 957 */ 958 protected void ensureActiveSelected() { 959 List<Layer> layers = getLayers(); 960 if (layers.isEmpty()) 961 return; 962 final Layer activeLayer = getActiveLayer(); 963 if (activeLayer != null) { 964 // there's an active layer - select it and make it visible 965 int idx = layers.indexOf(activeLayer); 966 selectionModel.setSelectionInterval(idx, idx); 967 ensureSelectedIsVisible(); 968 } else { 969 // no active layer - select the first one and make it visible 970 selectionModel.setSelectionInterval(0, 0); 971 ensureSelectedIsVisible(); 972 } 973 } 974 975 /** 976 * Replies the active layer. null, if no active layer is available 977 * 978 * @return the active layer. null, if no active layer is available 979 */ 980 protected Layer getActiveLayer() { 981 return getLayerManager().getActiveLayer(); 982 } 983 984 /** 985 * Replies the scale layer. null, if no active layer is available. 986 * 987 * @return the scale layer. null, if no active layer is available 988 * @deprecated Deprecated since it is unused in JOSM and does not really belong here. Can be removed soon (August 2016). 989 * You can directly query MapView. 990 */ 991 @Deprecated 992 protected NativeScaleLayer getNativeScaleLayer() { 993 return Main.isDisplayingMapView() ? Main.map.mapView.getNativeScaleLayer() : null; 994 } 995 996 /* ------------------------------------------------------------------------------ */ 997 /* Interface TableModel */ 998 /* ------------------------------------------------------------------------------ */ 999 1000 @Override 1001 public int getRowCount() { 1002 List<Layer> layers = getLayers(); 1003 return layers == null ? 0 : layers.size(); 1004 } 1005 1006 @Override 1007 public int getColumnCount() { 1008 return 4; 1009 } 1010 1011 @Override 1012 public Object getValueAt(int row, int col) { 1013 List<Layer> layers = getLayers(); 1014 if (row >= 0 && row < layers.size()) { 1015 switch (col) { 1016 case 0: return layers.get(row) == getActiveLayer(); 1017 case 1: 1018 case 2: 1019 case 3: return layers.get(row); 1020 default: // Do nothing 1021 } 1022 } 1023 return null; 1024 } 1025 1026 @Override 1027 public boolean isCellEditable(int row, int col) { 1028 if (col == 0 && getActiveLayer() == getLayers().get(row)) 1029 return false; 1030 return true; 1031 } 1032 1033 @Override 1034 public void setValueAt(Object value, int row, int col) { 1035 List<Layer> layers = getLayers(); 1036 if (row < layers.size()) { 1037 Layer l = layers.get(row); 1038 switch (col) { 1039 case 0: 1040 getLayerManager().setActiveLayer(l); 1041 l.setVisible(true); 1042 break; 1043 case 1: 1044 NativeScaleLayer oldLayer = Main.map.mapView.getNativeScaleLayer(); 1045 if (oldLayer == l) { 1046 Main.map.mapView.setNativeScaleLayer(null); 1047 } else if (l instanceof NativeScaleLayer) { 1048 Main.map.mapView.setNativeScaleLayer((NativeScaleLayer) l); 1049 if (oldLayer != null) { 1050 int idx = getLayers().indexOf(oldLayer); 1051 if (idx >= 0) { 1052 fireTableCellUpdated(idx, col); 1053 } 1054 } 1055 } 1056 break; 1057 case 2: 1058 l.setVisible((Boolean) value); 1059 break; 1060 case 3: 1061 l.rename((String) value); 1062 break; 1063 default: throw new RuntimeException(); 1064 } 1065 fireTableCellUpdated(row, col); 1066 } 1067 } 1068 1069 /* ------------------------------------------------------------------------------ */ 1070 /* Interface ActiveLayerChangeListener */ 1071 /* ------------------------------------------------------------------------------ */ 1072 @Override 1073 public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) { 1074 Layer oldLayer = e.getPreviousActiveLayer(); 1075 if (oldLayer != null) { 1076 int idx = getLayers().indexOf(oldLayer); 1077 if (idx >= 0) { 1078 fireTableRowsUpdated(idx, idx); 1079 } 1080 } 1081 1082 Layer newLayer = getActiveLayer(); 1083 if (newLayer != null) { 1084 int idx = getLayers().indexOf(newLayer); 1085 if (idx >= 0) { 1086 fireTableRowsUpdated(idx, idx); 1087 } 1088 } 1089 ensureActiveSelected(); 1090 } 1091 1092 /* ------------------------------------------------------------------------------ */ 1093 /* Interface LayerChangeListener */ 1094 /* ------------------------------------------------------------------------------ */ 1095 @Override 1096 public void layerAdded(LayerAddEvent e) { 1097 onAddLayer(e.getAddedLayer()); 1098 } 1099 1100 @Override 1101 public void layerRemoving(LayerRemoveEvent e) { 1102 onRemoveLayer(e.getRemovedLayer()); 1103 } 1104 1105 @Override 1106 public void layerOrderChanged(LayerOrderChangeEvent e) { 1107 // ignored for now, since only we change layer order. 1108 } 1109 1110 /* ------------------------------------------------------------------------------ */ 1111 /* Interface PropertyChangeListener */ 1112 /* ------------------------------------------------------------------------------ */ 1113 @Override 1114 public void propertyChange(PropertyChangeEvent evt) { 1115 if (evt.getSource() instanceof Layer) { 1116 Layer layer = (Layer) evt.getSource(); 1117 final int idx = getLayers().indexOf(layer); 1118 if (idx < 0) 1119 return; 1120 fireRefresh(); 1121 } 1122 } 1123 } 1124 1125 /** 1126 * This component displays a list of layers and provides the methods needed by {@link LayerListModel}. 1127 */ 1128 static class LayerList extends JTable { 1129 1130 LayerList(LayerListModel dataModel) { 1131 super(dataModel); 1132 dataModel.setLayerList(this); 1133 } 1134 1135 public void scrollToVisible(int row, int col) { 1136 if (!(getParent() instanceof JViewport)) 1137 return; 1138 JViewport viewport = (JViewport) getParent(); 1139 Rectangle rect = getCellRect(row, col, true); 1140 Point pt = viewport.getViewPosition(); 1141 rect.setLocation(rect.x - pt.x, rect.y - pt.y); 1142 viewport.scrollRectToVisible(rect); 1143 } 1144 } 1145 1146 /** 1147 * Creates a {@link ShowHideLayerAction} in the context of this {@link LayerListDialog}. 1148 * 1149 * @return the action 1150 */ 1151 public ShowHideLayerAction createShowHideLayerAction() { 1152 return new ShowHideLayerAction(model); 1153 } 1154 1155 /** 1156 * Creates a {@link DeleteLayerAction} in the context of this {@link LayerListDialog}. 1157 * 1158 * @return the action 1159 */ 1160 public DeleteLayerAction createDeleteLayerAction() { 1161 return new DeleteLayerAction(model); 1162 } 1163 1164 /** 1165 * Creates a {@link ActivateLayerAction} for <code>layer</code> in the context of this {@link LayerListDialog}. 1166 * 1167 * @param layer the layer 1168 * @return the action 1169 */ 1170 public ActivateLayerAction createActivateLayerAction(Layer layer) { 1171 return new ActivateLayerAction(layer, model); 1172 } 1173 1174 /** 1175 * Creates a {@link MergeLayerAction} for <code>layer</code> in the context of this {@link LayerListDialog}. 1176 * 1177 * @param layer the layer 1178 * @return the action 1179 */ 1180 public MergeAction createMergeLayerAction(Layer layer) { 1181 return new MergeAction(layer, model); 1182 } 1183 1184 /** 1185 * Creates a {@link DuplicateAction} for <code>layer</code> in the context of this {@link LayerListDialog}. 1186 * 1187 * @param layer the layer 1188 * @return the action 1189 */ 1190 public DuplicateAction createDuplicateLayerAction(Layer layer) { 1191 return new DuplicateAction(layer, model); 1192 } 1193 1194 /** 1195 * Returns the layer at given index, or {@code null}. 1196 * @param index the index 1197 * @return the layer at given index, or {@code null} if index out of range 1198 */ 1199 public static Layer getLayerForIndex(int index) { 1200 List<Layer> layers = Main.getLayerManager().getLayers(); 1201 1202 if (index < layers.size() && index >= 0) 1203 return layers.get(index); 1204 else 1205 return null; 1206 } 1207 1208 /** 1209 * Returns a list of info on all layers of a given class. 1210 * @param layerClass The layer class. This is not {@code Class<? extends Layer>} on purpose, 1211 * to allow asking for layers implementing some interface 1212 * @return list of info on all layers assignable from {@code layerClass} 1213 */ 1214 public static List<MultikeyInfo> getLayerInfoByClass(Class<?> layerClass) { 1215 List<MultikeyInfo> result = new ArrayList<>(); 1216 1217 List<Layer> layers = Main.getLayerManager().getLayers(); 1218 1219 int index = 0; 1220 for (Layer l: layers) { 1221 if (layerClass.isAssignableFrom(l.getClass())) { 1222 result.add(new MultikeyInfo(index, l.getName())); 1223 } 1224 index++; 1225 } 1226 1227 return result; 1228 } 1229 1230 /** 1231 * Determines if a layer is valid (contained in global layer list). 1232 * @param l the layer 1233 * @return {@code true} if layer {@code l} is contained in current layer list 1234 */ 1235 public static boolean isLayerValid(Layer l) { 1236 if (l == null) 1237 return false; 1238 1239 return Main.getLayerManager().containsLayer(l); 1240 } 1241 1242 /** 1243 * Returns info about layer. 1244 * @param l the layer 1245 * @return info about layer {@code l} 1246 */ 1247 public static MultikeyInfo getLayerInfo(Layer l) { 1248 if (l == null) 1249 return null; 1250 1251 int index = Main.getLayerManager().getLayers().indexOf(l); 1252 if (index < 0) 1253 return null; 1254 1255 return new MultikeyInfo(index, l.getName()); 1256 } 1257}