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