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