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