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