001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.dialogs; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.Color; 007import java.awt.Component; 008import java.awt.Dimension; 009import java.awt.Font; 010import java.awt.Point; 011import java.awt.Rectangle; 012import java.awt.event.ActionEvent; 013import java.awt.event.InputEvent; 014import java.awt.event.KeyEvent; 015import java.awt.event.MouseEvent; 016import java.beans.PropertyChangeEvent; 017import java.beans.PropertyChangeListener; 018import java.lang.ref.WeakReference; 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.Collections; 022import java.util.List; 023import java.util.concurrent.CopyOnWriteArrayList; 024 025import javax.swing.AbstractAction; 026import javax.swing.Action; 027import javax.swing.DefaultCellEditor; 028import javax.swing.DefaultListSelectionModel; 029import javax.swing.ImageIcon; 030import javax.swing.JCheckBox; 031import javax.swing.JComponent; 032import javax.swing.JLabel; 033import javax.swing.JMenuItem; 034import javax.swing.JPopupMenu; 035import javax.swing.JSlider; 036import javax.swing.JTable; 037import javax.swing.JViewport; 038import javax.swing.KeyStroke; 039import javax.swing.ListSelectionModel; 040import javax.swing.UIManager; 041import javax.swing.event.ChangeEvent; 042import javax.swing.event.ChangeListener; 043import javax.swing.event.ListDataEvent; 044import javax.swing.event.ListSelectionEvent; 045import javax.swing.event.ListSelectionListener; 046import javax.swing.event.TableModelEvent; 047import javax.swing.event.TableModelListener; 048import javax.swing.table.AbstractTableModel; 049import javax.swing.table.DefaultTableCellRenderer; 050import javax.swing.table.TableCellRenderer; 051import javax.swing.table.TableModel; 052 053import org.openstreetmap.josm.Main; 054import org.openstreetmap.josm.actions.MergeLayerAction; 055import org.openstreetmap.josm.gui.MapFrame; 056import org.openstreetmap.josm.gui.MapView; 057import org.openstreetmap.josm.gui.SideButton; 058import org.openstreetmap.josm.gui.help.HelpUtil; 059import org.openstreetmap.josm.gui.layer.JumpToMarkerActions; 060import org.openstreetmap.josm.gui.layer.Layer; 061import org.openstreetmap.josm.gui.layer.Layer.LayerAction; 062import org.openstreetmap.josm.gui.layer.OsmDataLayer; 063import org.openstreetmap.josm.gui.util.GuiHelper; 064import org.openstreetmap.josm.gui.widgets.JosmTextField; 065import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher; 066import org.openstreetmap.josm.tools.CheckParameterUtil; 067import org.openstreetmap.josm.tools.ImageProvider; 068import org.openstreetmap.josm.tools.InputMapUtils; 069import org.openstreetmap.josm.tools.MultikeyActionsHandler; 070import org.openstreetmap.josm.tools.MultikeyShortcutAction; 071import org.openstreetmap.josm.tools.MultikeyShortcutAction.MultikeyInfo; 072import org.openstreetmap.josm.tools.Shortcut; 073 074/** 075 * This is a toggle dialog which displays the list of layers. Actions allow to 076 * change the ordering of the layers, to hide/show layers, to activate layers, 077 * and to delete layers. 078 * 079 */ 080public class LayerListDialog extends ToggleDialog { 081 /** the unique instance of the dialog */ 082 private static LayerListDialog instance; 083 084 /** 085 * Creates the instance of the dialog. It's connected to the map frame <code>mapFrame</code> 086 * 087 * @param mapFrame the map frame 088 */ 089 public static void createInstance(MapFrame mapFrame) { 090 if (instance != null) 091 throw new IllegalStateException("Dialog was already created"); 092 instance = new LayerListDialog(mapFrame); 093 } 094 095 /** 096 * Replies the instance of the dialog 097 * 098 * @return the instance of the dialog 099 * @throws IllegalStateException thrown, if the dialog is not created yet 100 * @see #createInstance(MapFrame) 101 */ 102 public static LayerListDialog getInstance() throws IllegalStateException { 103 if (instance == null) 104 throw new IllegalStateException("Dialog not created yet. Invoke createInstance() first"); 105 return instance; 106 } 107 108 /** the model for the layer list */ 109 private LayerListModel model; 110 111 /** the list of layers (technically its a JTable, but appears like a list) */ 112 private LayerList layerList; 113 114 private SideButton opacityButton; 115 116 ActivateLayerAction activateLayerAction; 117 ShowHideLayerAction showHideLayerAction; 118 119 //TODO This duplicates ShowHide actions functionality 120 /** stores which layer index to toggle and executes the ShowHide action if the layer is present */ 121 private final class ToggleLayerIndexVisibility extends AbstractAction { 122 int layerIndex = -1; 123 public ToggleLayerIndexVisibility(int layerIndex) { 124 this.layerIndex = layerIndex; 125 } 126 @Override 127 public void actionPerformed(ActionEvent e) { 128 final Layer l = model.getLayer(model.getRowCount() - layerIndex - 1); 129 if(l != null) { 130 l.toggleVisible(); 131 } 132 } 133 } 134 135 private final Shortcut[] visibilityToggleShortcuts = new Shortcut[10]; 136 private final ToggleLayerIndexVisibility[] visibilityToggleActions = new ToggleLayerIndexVisibility[10]; 137 /** 138 * registers (shortcut to toggle right hand side toggle dialogs)+(number keys) shortcuts 139 * to toggle the visibility of the first ten layers. 140 */ 141 private final void createVisibilityToggleShortcuts() { 142 final int[] k = { KeyEvent.VK_1, KeyEvent.VK_2, KeyEvent.VK_3, KeyEvent.VK_4, 143 KeyEvent.VK_5, KeyEvent.VK_6, KeyEvent.VK_7, KeyEvent.VK_8, 144 KeyEvent.VK_9, KeyEvent.VK_0 }; 145 146 for(int i=0; i < 10; i++) { 147 visibilityToggleShortcuts[i] = Shortcut.registerShortcut("subwindow:layers:toggleLayer" + (i+1), 148 tr("Toggle visibility of layer: {0}", (i+1)), k[i], Shortcut.ALT); 149 visibilityToggleActions[i] = new ToggleLayerIndexVisibility(i); 150 Main.registerActionShortcut(visibilityToggleActions[i], visibilityToggleShortcuts[i]); 151 } 152 } 153 154 /** 155 * Create an layer list and attach it to the given mapView. 156 */ 157 protected LayerListDialog(MapFrame mapFrame) { 158 super(tr("Layers"), "layerlist", tr("Open a list of all loaded layers."), 159 Shortcut.registerShortcut("subwindow:layers", tr("Toggle: {0}", tr("Layers")), KeyEvent.VK_L, 160 Shortcut.ALT_SHIFT), 100, true); 161 162 // create the models 163 // 164 DefaultListSelectionModel selectionModel = new DefaultListSelectionModel(); 165 selectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 166 model = new LayerListModel(selectionModel); 167 168 // create the list control 169 // 170 layerList = new LayerList(model); 171 layerList.setSelectionModel(selectionModel); 172 layerList.addMouseListener(new PopupMenuHandler()); 173 layerList.setBackground(UIManager.getColor("Button.background")); 174 layerList.putClientProperty("terminateEditOnFocusLost", true); 175 layerList.putClientProperty("JTable.autoStartsEdit", false); 176 layerList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 177 layerList.setTableHeader(null); 178 layerList.setShowGrid(false); 179 layerList.setIntercellSpacing(new Dimension(0, 0)); 180 layerList.getColumnModel().getColumn(0).setCellRenderer(new ActiveLayerCellRenderer()); 181 layerList.getColumnModel().getColumn(0).setCellEditor(new DefaultCellEditor(new ActiveLayerCheckBox())); 182 layerList.getColumnModel().getColumn(0).setMaxWidth(12); 183 layerList.getColumnModel().getColumn(0).setPreferredWidth(12); 184 layerList.getColumnModel().getColumn(0).setResizable(false); 185 layerList.getColumnModel().getColumn(1).setCellRenderer(new LayerVisibleCellRenderer()); 186 layerList.getColumnModel().getColumn(1).setCellEditor(new LayerVisibleCellEditor(new LayerVisibleCheckBox())); 187 layerList.getColumnModel().getColumn(1).setMaxWidth(16); 188 layerList.getColumnModel().getColumn(1).setPreferredWidth(16); 189 layerList.getColumnModel().getColumn(1).setResizable(false); 190 layerList.getColumnModel().getColumn(2).setCellRenderer(new LayerNameCellRenderer()); 191 layerList.getColumnModel().getColumn(2).setCellEditor(new LayerNameCellEditor(new JosmTextField())); 192 for (KeyStroke ks : new KeyStroke[] { 193 KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_MASK), 194 KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_MASK), 195 KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.SHIFT_MASK), 196 KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.SHIFT_MASK), 197 KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.SHIFT_MASK), 198 KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.SHIFT_MASK), 199 KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0), 200 KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0), 201 }) 202 { 203 layerList.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(ks, new Object()); 204 } 205 206 // init the model 207 // 208 final MapView mapView = mapFrame.mapView; 209 model.populate(); 210 model.setSelectedLayer(mapView.getActiveLayer()); 211 model.addLayerListModelListener( 212 new LayerListModelListener() { 213 @Override 214 public void makeVisible(int row, Layer layer) { 215 layerList.scrollToVisible(row, 0); 216 layerList.repaint(); 217 } 218 @Override 219 public void refresh() { 220 layerList.repaint(); 221 } 222 } 223 ); 224 225 // -- move up action 226 MoveUpAction moveUpAction = new MoveUpAction(); 227 adaptTo(moveUpAction, model); 228 adaptTo(moveUpAction,selectionModel); 229 230 // -- move down action 231 MoveDownAction moveDownAction = new MoveDownAction(); 232 adaptTo(moveDownAction, model); 233 adaptTo(moveDownAction,selectionModel); 234 235 // -- activate action 236 activateLayerAction = new ActivateLayerAction(); 237 activateLayerAction.updateEnabledState(); 238 MultikeyActionsHandler.getInstance().addAction(activateLayerAction); 239 adaptTo(activateLayerAction, selectionModel); 240 241 JumpToMarkerActions.initialize(); 242 243 // -- show hide action 244 showHideLayerAction = new ShowHideLayerAction(); 245 MultikeyActionsHandler.getInstance().addAction(showHideLayerAction); 246 adaptTo(showHideLayerAction, selectionModel); 247 248 //-- layer opacity action 249 LayerOpacityAction layerOpacityAction = new LayerOpacityAction(); 250 adaptTo(layerOpacityAction, selectionModel); 251 opacityButton = new SideButton(layerOpacityAction, false); 252 253 // -- merge layer action 254 MergeAction mergeLayerAction = new MergeAction(); 255 adaptTo(mergeLayerAction, model); 256 adaptTo(mergeLayerAction,selectionModel); 257 258 // -- duplicate layer action 259 DuplicateAction duplicateLayerAction = new DuplicateAction(); 260 adaptTo(duplicateLayerAction, model); 261 adaptTo(duplicateLayerAction, selectionModel); 262 263 //-- delete layer action 264 DeleteLayerAction deleteLayerAction = new DeleteLayerAction(); 265 layerList.getActionMap().put("deleteLayer", deleteLayerAction); 266 adaptTo(deleteLayerAction, selectionModel); 267 getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put( 268 KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0),"delete" 269 ); 270 getActionMap().put("delete", deleteLayerAction); 271 272 // Activate layer on Enter key press 273 InputMapUtils.addEnterAction(layerList, new AbstractAction() { 274 @Override 275 public void actionPerformed(ActionEvent e) { 276 activateLayerAction.actionPerformed(null); 277 layerList.requestFocus(); 278 } 279 }); 280 281 // Show/Activate layer on Enter key press 282 InputMapUtils.addSpacebarAction(layerList, showHideLayerAction); 283 284 createLayout(layerList, true, Arrays.asList(new SideButton[] { 285 new SideButton(moveUpAction, false), 286 new SideButton(moveDownAction, false), 287 new SideButton(activateLayerAction, false), 288 new SideButton(showHideLayerAction, false), 289 opacityButton, 290 new SideButton(mergeLayerAction, false), 291 new SideButton(duplicateLayerAction, false), 292 new SideButton(deleteLayerAction, false) 293 })); 294 295 createVisibilityToggleShortcuts(); 296 } 297 298 @Override 299 public void showNotify() { 300 MapView.addLayerChangeListener(activateLayerAction); 301 MapView.addLayerChangeListener(model); 302 model.populate(); 303 } 304 305 @Override 306 public void hideNotify() { 307 MapView.removeLayerChangeListener(model); 308 MapView.removeLayerChangeListener(activateLayerAction); 309 } 310 311 public LayerListModel getModel() { 312 return model; 313 } 314 315 protected interface IEnabledStateUpdating { 316 void updateEnabledState(); 317 } 318 319 /** 320 * Wires <code>listener</code> to <code>listSelectionModel</code> in such a way, that 321 * <code>listener</code> receives a {@link IEnabledStateUpdating#updateEnabledState()} 322 * on every {@link ListSelectionEvent}. 323 * 324 * @param listener the listener 325 * @param listSelectionModel the source emitting {@link ListSelectionEvent}s 326 */ 327 protected void adaptTo(final IEnabledStateUpdating listener, ListSelectionModel listSelectionModel) { 328 listSelectionModel.addListSelectionListener( 329 new ListSelectionListener() { 330 @Override 331 public void valueChanged(ListSelectionEvent e) { 332 listener.updateEnabledState(); 333 } 334 } 335 ); 336 } 337 338 /** 339 * Wires <code>listener</code> to <code>listModel</code> in such a way, that 340 * <code>listener</code> receives a {@link IEnabledStateUpdating#updateEnabledState()} 341 * on every {@link ListDataEvent}. 342 * 343 * @param listener the listener 344 * @param listModel the source emitting {@link ListDataEvent}s 345 */ 346 protected void adaptTo(final IEnabledStateUpdating listener, LayerListModel listModel) { 347 listModel.addTableModelListener( 348 new TableModelListener() { 349 350 @Override 351 public void tableChanged(TableModelEvent e) { 352 listener.updateEnabledState(); 353 } 354 } 355 ); 356 } 357 358 @Override 359 public void destroy() { 360 for(int i=0; i < 10; i++) { 361 Main.unregisterActionShortcut(visibilityToggleActions[i], visibilityToggleShortcuts[i]); 362 } 363 MultikeyActionsHandler.getInstance().removeAction(activateLayerAction); 364 MultikeyActionsHandler.getInstance().removeAction(showHideLayerAction); 365 JumpToMarkerActions.unregisterActions(); 366 super.destroy(); 367 instance = null; 368 } 369 370 /** 371 * The action to delete the currently selected layer 372 */ 373 public final class DeleteLayerAction extends AbstractAction implements IEnabledStateUpdating, LayerAction { 374 /** 375 * Creates a {@link DeleteLayerAction} which will delete the currently 376 * selected layers in the layer dialog. 377 * 378 */ 379 public DeleteLayerAction() { 380 putValue(SMALL_ICON,ImageProvider.get("dialogs", "delete")); 381 putValue(SHORT_DESCRIPTION, tr("Delete the selected layers.")); 382 putValue(NAME, tr("Delete")); 383 putValue("help", HelpUtil.ht("/Dialog/LayerList#DeleteLayer")); 384 updateEnabledState(); 385 } 386 387 @Override 388 public void actionPerformed(ActionEvent e) { 389 List<Layer> selectedLayers = getModel().getSelectedLayers(); 390 if (selectedLayers.isEmpty()) 391 return; 392 if (!Main.saveUnsavedModifications(selectedLayers, false)) 393 return; 394 for (Layer l: selectedLayers) { 395 Main.main.removeLayer(l); 396 } 397 } 398 399 @Override 400 public void updateEnabledState() { 401 setEnabled(! getModel().getSelectedLayers().isEmpty()); 402 } 403 404 @Override 405 public Component createMenuComponent() { 406 return new JMenuItem(this); 407 } 408 409 @Override 410 public boolean supportLayers(List<Layer> layers) { 411 return true; 412 } 413 414 @Override 415 public boolean equals(Object obj) { 416 return obj instanceof DeleteLayerAction; 417 } 418 419 @Override 420 public int hashCode() { 421 return getClass().hashCode(); 422 } 423 } 424 425 public final class ShowHideLayerAction extends AbstractAction implements IEnabledStateUpdating, LayerAction, MultikeyShortcutAction { 426 427 private WeakReference<Layer> lastLayer; 428 private Shortcut multikeyShortcut; 429 430 /** 431 * Creates a {@link ShowHideLayerAction} which will toggle the visibility of 432 * the currently selected layers 433 * 434 */ 435 public ShowHideLayerAction(boolean init) { 436 putValue(NAME, tr("Show/hide")); 437 putValue(SMALL_ICON, ImageProvider.get("dialogs", "showhide")); 438 putValue(SHORT_DESCRIPTION, tr("Toggle visible state of the selected layer.")); 439 putValue("help", HelpUtil.ht("/Dialog/LayerList#ShowHideLayer")); 440 multikeyShortcut = Shortcut.registerShortcut("core_multikey:showHideLayer", tr("Multikey: {0}", 441 tr("Show/hide layer")), KeyEvent.VK_S, Shortcut.SHIFT); 442 multikeyShortcut.setAccelerator(this); 443 if (init) { 444 updateEnabledState(); 445 } 446 } 447 448 /** 449 * Constructs a new {@code ShowHideLayerAction}. 450 */ 451 public ShowHideLayerAction() { 452 this(true); 453 } 454 455 @Override 456 public Shortcut getMultikeyShortcut() { 457 return multikeyShortcut; 458 } 459 460 @Override 461 public void actionPerformed(ActionEvent e) { 462 for(Layer l : model.getSelectedLayers()) { 463 l.toggleVisible(); 464 } 465 } 466 467 @Override 468 public void executeMultikeyAction(int index, boolean repeat) { 469 Layer l = LayerListDialog.getLayerForIndex(index); 470 if (l != null) { 471 l.toggleVisible(); 472 lastLayer = new WeakReference<>(l); 473 } else if (repeat && lastLayer != null) { 474 l = lastLayer.get(); 475 if (LayerListDialog.isLayerValid(l)) { 476 l.toggleVisible(); 477 } 478 } 479 } 480 481 @Override 482 public void updateEnabledState() { 483 setEnabled(!model.getSelectedLayers().isEmpty()); 484 } 485 486 @Override 487 public Component createMenuComponent() { 488 return new JMenuItem(this); 489 } 490 491 @Override 492 public boolean supportLayers(List<Layer> layers) { 493 return true; 494 } 495 496 @Override 497 public boolean equals(Object obj) { 498 return obj instanceof ShowHideLayerAction; 499 } 500 501 @Override 502 public int hashCode() { 503 return getClass().hashCode(); 504 } 505 506 @Override 507 public List<MultikeyInfo> getMultikeyCombinations() { 508 return LayerListDialog.getLayerInfoByClass(Layer.class); 509 } 510 511 @Override 512 public MultikeyInfo getLastMultikeyAction() { 513 if (lastLayer != null) 514 return LayerListDialog.getLayerInfo(lastLayer.get()); 515 return null; 516 } 517 } 518 519 public final class LayerOpacityAction extends AbstractAction implements IEnabledStateUpdating, LayerAction { 520 private Layer layer; 521 private JPopupMenu popup; 522 private JSlider slider = new JSlider(JSlider.VERTICAL); 523 524 /** 525 * Creates a {@link LayerOpacityAction} which allows to chenge the 526 * opacity of one or more layers. 527 * 528 * @param layer the layer. Must not be null. 529 * @exception IllegalArgumentException thrown, if layer is null 530 */ 531 public LayerOpacityAction(Layer layer) throws IllegalArgumentException { 532 this(); 533 putValue(NAME, tr("Opacity")); 534 CheckParameterUtil.ensureParameterNotNull(layer, "layer"); 535 this.layer = layer; 536 updateEnabledState(); 537 } 538 539 /** 540 * Creates a {@link ShowHideLayerAction} which will toggle the visibility of 541 * the currently selected layers 542 * 543 */ 544 public LayerOpacityAction() { 545 putValue(NAME, tr("Opacity")); 546 putValue(SHORT_DESCRIPTION, tr("Adjust opacity of the layer.")); 547 putValue(SMALL_ICON, ImageProvider.get("dialogs/layerlist", "transparency")); 548 updateEnabledState(); 549 550 popup = new JPopupMenu(); 551 slider.addChangeListener(new ChangeListener() { 552 @Override 553 public void stateChanged(ChangeEvent e) { 554 setOpacity((double)slider.getValue()/100); 555 } 556 }); 557 popup.add(slider); 558 } 559 560 private void setOpacity(double value) { 561 if (!isEnabled()) return; 562 if (layer != null) { 563 layer.setOpacity(value); 564 } else { 565 for(Layer layer: model.getSelectedLayers()) { 566 layer.setOpacity(value); 567 } 568 } 569 } 570 571 private double getOpacity() { 572 if (layer != null) 573 return layer.getOpacity(); 574 else { 575 double opacity = 0; 576 List<Layer> layers = model.getSelectedLayers(); 577 for(Layer layer: layers) { 578 opacity += layer.getOpacity(); 579 } 580 return opacity / layers.size(); 581 } 582 } 583 584 @Override 585 public void actionPerformed(ActionEvent e) { 586 slider.setValue((int)Math.round(getOpacity()*100)); 587 if (e.getSource() == opacityButton) { 588 popup.show(opacityButton, 0, opacityButton.getHeight()); 589 } else { 590 // Action can be trigger either by opacity button or by popup menu (in case toggle buttons are hidden). 591 // In that case, show it in the middle of screen (because opacityButton is not visible) 592 popup.show(Main.parent, Main.parent.getWidth() / 2, (Main.parent.getHeight() - popup.getHeight()) / 2); 593 } 594 } 595 596 @Override 597 public void updateEnabledState() { 598 if (layer == null) { 599 setEnabled(! getModel().getSelectedLayers().isEmpty()); 600 } else { 601 setEnabled(true); 602 } 603 } 604 605 @Override 606 public Component createMenuComponent() { 607 return new JMenuItem(this); 608 } 609 610 @Override 611 public boolean supportLayers(List<Layer> layers) { 612 return true; 613 } 614 615 @Override 616 public boolean equals(Object obj) { 617 return obj instanceof LayerOpacityAction; 618 } 619 620 @Override 621 public int hashCode() { 622 return getClass().hashCode(); 623 } 624 } 625 626 /** 627 * The action to activate the currently selected layer 628 */ 629 630 public final class ActivateLayerAction extends AbstractAction implements IEnabledStateUpdating, MapView.LayerChangeListener, MultikeyShortcutAction{ 631 private Layer layer; 632 private Shortcut multikeyShortcut; 633 634 public ActivateLayerAction(Layer layer) { 635 this(); 636 CheckParameterUtil.ensureParameterNotNull(layer, "layer"); 637 this.layer = layer; 638 putValue(NAME, tr("Activate")); 639 updateEnabledState(); 640 } 641 642 /** 643 * Constructs a new {@code ActivateLayerAction}. 644 */ 645 public ActivateLayerAction() { 646 putValue(NAME, tr("Activate")); 647 putValue(SMALL_ICON, ImageProvider.get("dialogs", "activate")); 648 putValue(SHORT_DESCRIPTION, tr("Activate the selected layer")); 649 multikeyShortcut = Shortcut.registerShortcut("core_multikey:activateLayer", tr("Multikey: {0}", 650 tr("Activate layer")), KeyEvent.VK_A, Shortcut.SHIFT); 651 multikeyShortcut.setAccelerator(this); 652 putValue("help", HelpUtil.ht("/Dialog/LayerList#ActivateLayer")); 653 } 654 655 @Override 656 public Shortcut getMultikeyShortcut() { 657 return multikeyShortcut; 658 } 659 660 @Override 661 public void actionPerformed(ActionEvent e) { 662 Layer toActivate; 663 if (layer != null) { 664 toActivate = layer; 665 } else { 666 toActivate = model.getSelectedLayers().get(0); 667 } 668 execute(toActivate); 669 } 670 671 private void execute(Layer layer) { 672 // model is going to be updated via LayerChangeListener 673 // and PropertyChangeEvents 674 Main.map.mapView.setActiveLayer(layer); 675 layer.setVisible(true); 676 } 677 678 protected boolean isActiveLayer(Layer layer) { 679 if (!Main.isDisplayingMapView()) return false; 680 return Main.map.mapView.getActiveLayer() == layer; 681 } 682 683 @Override 684 public void updateEnabledState() { 685 GuiHelper.runInEDTAndWait(new Runnable() { 686 @Override 687 public void run() { 688 if (layer == null) { 689 if (getModel().getSelectedLayers().size() != 1) { 690 setEnabled(false); 691 return; 692 } 693 Layer selectedLayer = getModel().getSelectedLayers().get(0); 694 setEnabled(!isActiveLayer(selectedLayer)); 695 } else { 696 setEnabled(!isActiveLayer(layer)); 697 } 698 } 699 }); 700 } 701 702 @Override 703 public void activeLayerChange(Layer oldLayer, Layer newLayer) { 704 updateEnabledState(); 705 } 706 @Override 707 public void layerAdded(Layer newLayer) { 708 updateEnabledState(); 709 } 710 @Override 711 public void layerRemoved(Layer oldLayer) { 712 updateEnabledState(); 713 } 714 715 @Override 716 public void executeMultikeyAction(int index, boolean repeat) { 717 Layer l = LayerListDialog.getLayerForIndex(index); 718 if (l != null) { 719 execute(l); 720 } 721 } 722 723 @Override 724 public List<MultikeyInfo> getMultikeyCombinations() { 725 return LayerListDialog.getLayerInfoByClass(Layer.class); 726 } 727 728 @Override 729 public MultikeyInfo getLastMultikeyAction() { 730 return null; // Repeating action doesn't make much sense for activating 731 } 732 } 733 734 /** 735 * The action to merge the currently selected layer into another layer. 736 */ 737 public final class MergeAction extends AbstractAction implements IEnabledStateUpdating { 738 private Layer layer; 739 740 public MergeAction(Layer layer) throws IllegalArgumentException { 741 this(); 742 CheckParameterUtil.ensureParameterNotNull(layer, "layer"); 743 this.layer = layer; 744 putValue(NAME, tr("Merge")); 745 updateEnabledState(); 746 } 747 748 /** 749 * Constructs a new {@code MergeAction}. 750 */ 751 public MergeAction() { 752 putValue(NAME, tr("Merge")); 753 putValue(SMALL_ICON, ImageProvider.get("dialogs", "mergedown")); 754 putValue(SHORT_DESCRIPTION, tr("Merge this layer into another layer")); 755 putValue("help", HelpUtil.ht("/Dialog/LayerList#MergeLayer")); 756 updateEnabledState(); 757 } 758 759 @Override 760 public void actionPerformed(ActionEvent e) { 761 if (layer != null) { 762 Main.main.menu.merge.merge(layer); 763 } else { 764 if (getModel().getSelectedLayers().size() == 1) { 765 Layer selectedLayer = getModel().getSelectedLayers().get(0); 766 Main.main.menu.merge.merge(selectedLayer); 767 } else { 768 Main.main.menu.merge.merge(getModel().getSelectedLayers()); 769 } 770 } 771 } 772 773 protected boolean isActiveLayer(Layer layer) { 774 if (!Main.isDisplayingMapView()) return false; 775 return Main.map.mapView.getActiveLayer() == layer; 776 } 777 778 @Override 779 public void updateEnabledState() { 780 if (layer == null) { 781 if (getModel().getSelectedLayers().isEmpty()) { 782 setEnabled(false); 783 } else if (getModel().getSelectedLayers().size() > 1) { 784 Layer firstLayer = getModel().getSelectedLayers().get(0); 785 for (Layer l: getModel().getSelectedLayers()) { 786 if (l != firstLayer && (!l.isMergable(firstLayer) || !firstLayer.isMergable(l))) { 787 setEnabled(false); 788 return; 789 } 790 } 791 setEnabled(true); 792 } else { 793 Layer selectedLayer = getModel().getSelectedLayers().get(0); 794 List<Layer> targets = getModel().getPossibleMergeTargets(selectedLayer); 795 setEnabled(!targets.isEmpty()); 796 } 797 } else { 798 List<Layer> targets = getModel().getPossibleMergeTargets(layer); 799 setEnabled(!targets.isEmpty()); 800 } 801 } 802 } 803 804 /** 805 * The action to merge the currently selected layer into another layer. 806 */ 807 public final class DuplicateAction extends AbstractAction implements IEnabledStateUpdating { 808 private Layer layer; 809 810 public DuplicateAction(Layer layer) throws IllegalArgumentException { 811 this(); 812 CheckParameterUtil.ensureParameterNotNull(layer, "layer"); 813 this.layer = layer; 814 updateEnabledState(); 815 } 816 817 /** 818 * Constructs a new {@code DuplicateAction}. 819 */ 820 public DuplicateAction() { 821 putValue(NAME, tr("Duplicate")); 822 putValue(SMALL_ICON, ImageProvider.get("dialogs", "duplicatelayer")); 823 putValue(SHORT_DESCRIPTION, tr("Duplicate this layer")); 824 putValue("help", HelpUtil.ht("/Dialog/LayerList#DuplicateLayer")); 825 updateEnabledState(); 826 } 827 828 private void duplicate(Layer layer) { 829 if (!Main.isDisplayingMapView()) 830 return; 831 832 List<String> layerNames = new ArrayList<>(); 833 for (Layer l: Main.map.mapView.getAllLayers()) { 834 layerNames.add(l.getName()); 835 } 836 if (layer instanceof OsmDataLayer) { 837 OsmDataLayer oldLayer = (OsmDataLayer)layer; 838 // Translators: "Copy of {layer name}" 839 String newName = tr("Copy of {0}", oldLayer.getName()); 840 int i = 2; 841 while (layerNames.contains(newName)) { 842 // Translators: "Copy {number} of {layer name}" 843 newName = tr("Copy {1} of {0}", oldLayer.getName(), i); 844 i++; 845 } 846 Main.main.addLayer(new OsmDataLayer(oldLayer.data.clone(), newName, null)); 847 } 848 } 849 850 @Override 851 public void actionPerformed(ActionEvent e) { 852 if (layer != null) { 853 duplicate(layer); 854 } else { 855 duplicate(getModel().getSelectedLayers().get(0)); 856 } 857 } 858 859 protected boolean isActiveLayer(Layer layer) { 860 if (!Main.isDisplayingMapView()) 861 return false; 862 return Main.map.mapView.getActiveLayer() == layer; 863 } 864 865 @Override 866 public void updateEnabledState() { 867 if (layer == null) { 868 if (getModel().getSelectedLayers().size() == 1) { 869 setEnabled(getModel().getSelectedLayers().get(0) instanceof OsmDataLayer); 870 } else { 871 setEnabled(false); 872 } 873 } else { 874 setEnabled(layer instanceof OsmDataLayer); 875 } 876 } 877 } 878 879 private static class ActiveLayerCheckBox extends JCheckBox { 880 public ActiveLayerCheckBox() { 881 setHorizontalAlignment(javax.swing.SwingConstants.CENTER); 882 ImageIcon blank = ImageProvider.get("dialogs/layerlist", "blank"); 883 ImageIcon active = ImageProvider.get("dialogs/layerlist", "active"); 884 setIcon(blank); 885 setSelectedIcon(active); 886 setRolloverIcon(blank); 887 setRolloverSelectedIcon(active); 888 setPressedIcon(ImageProvider.get("dialogs/layerlist", "active-pressed")); 889 } 890 } 891 892 private static class LayerVisibleCheckBox extends JCheckBox { 893 private final ImageIcon icon_eye; 894 private final ImageIcon icon_eye_translucent; 895 private boolean isTranslucent; 896 public LayerVisibleCheckBox() { 897 setHorizontalAlignment(javax.swing.SwingConstants.RIGHT); 898 icon_eye = ImageProvider.get("dialogs/layerlist", "eye"); 899 icon_eye_translucent = ImageProvider.get("dialogs/layerlist", "eye-translucent"); 900 setIcon(ImageProvider.get("dialogs/layerlist", "eye-off")); 901 setPressedIcon(ImageProvider.get("dialogs/layerlist", "eye-pressed")); 902 setSelectedIcon(icon_eye); 903 isTranslucent = false; 904 } 905 906 public void setTranslucent(boolean isTranslucent) { 907 if (this.isTranslucent == isTranslucent) return; 908 if (isTranslucent) { 909 setSelectedIcon(icon_eye_translucent); 910 } else { 911 setSelectedIcon(icon_eye); 912 } 913 this.isTranslucent = isTranslucent; 914 } 915 916 public void updateStatus(Layer layer) { 917 boolean visible = layer.isVisible(); 918 setSelected(visible); 919 setTranslucent(layer.getOpacity()<1.0); 920 setToolTipText(visible ? tr("layer is currently visible (click to hide layer)") : tr("layer is currently hidden (click to show layer)")); 921 } 922 } 923 924 private static class ActiveLayerCellRenderer implements TableCellRenderer { 925 JCheckBox cb; 926 public ActiveLayerCellRenderer() { 927 cb = new ActiveLayerCheckBox(); 928 } 929 930 @Override 931 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { 932 boolean active = value != null && (Boolean) value; 933 cb.setSelected(active); 934 cb.setToolTipText(active ? tr("this layer is the active layer") : tr("this layer is not currently active (click to activate)")); 935 return cb; 936 } 937 } 938 939 private static class LayerVisibleCellRenderer implements TableCellRenderer { 940 LayerVisibleCheckBox cb; 941 public LayerVisibleCellRenderer() { 942 this.cb = new LayerVisibleCheckBox(); 943 } 944 945 @Override 946 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { 947 if (value != null) { 948 cb.updateStatus((Layer)value); 949 } 950 return cb; 951 } 952 } 953 954 private static class LayerVisibleCellEditor extends DefaultCellEditor { 955 LayerVisibleCheckBox cb; 956 public LayerVisibleCellEditor(LayerVisibleCheckBox cb) { 957 super(cb); 958 this.cb = cb; 959 } 960 961 @Override 962 public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { 963 cb.updateStatus((Layer)value); 964 return cb; 965 } 966 } 967 968 private class LayerNameCellRenderer extends DefaultTableCellRenderer { 969 970 protected boolean isActiveLayer(Layer layer) { 971 if (!Main.isDisplayingMapView()) return false; 972 return Main.map.mapView.getActiveLayer() == layer; 973 } 974 975 @Override 976 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { 977 if (value == null) 978 return this; 979 Layer layer = (Layer)value; 980 JLabel label = (JLabel)super.getTableCellRendererComponent(table, 981 layer.getName(), isSelected, hasFocus, row, column); 982 if (isActiveLayer(layer)) { 983 label.setFont(label.getFont().deriveFont(Font.BOLD)); 984 } 985 if(Main.pref.getBoolean("dialog.layer.colorname", true)) { 986 Color c = layer.getColor(false); 987 if(c != null) { 988 Color oc = null; 989 for(Layer l : model.getLayers()) { 990 oc = l.getColor(false); 991 if(oc != null) { 992 if(oc.equals(c)) { 993 oc = null; 994 } else { 995 break; 996 } 997 } 998 } 999 /* not more than one color, don't use coloring */ 1000 if(oc == null) { 1001 c = null; 1002 } 1003 } 1004 if(c == null) { 1005 c = Main.pref.getUIColor(isSelected ? "Table.selectionForeground" : "Table.foreground"); 1006 } 1007 label.setForeground(c); 1008 } 1009 label.setIcon(layer.getIcon()); 1010 label.setToolTipText(layer.getToolTipText()); 1011 return label; 1012 } 1013 } 1014 1015 private static class LayerNameCellEditor extends DefaultCellEditor { 1016 public LayerNameCellEditor(JosmTextField tf) { 1017 super(tf); 1018 } 1019 1020 @Override 1021 public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { 1022 JosmTextField tf = (JosmTextField) super.getTableCellEditorComponent(table, value, isSelected, row, column); 1023 tf.setText(value == null ? "" : ((Layer) value).getName()); 1024 return tf; 1025 } 1026 } 1027 1028 class PopupMenuHandler extends PopupMenuLauncher { 1029 @Override public void showMenu(MouseEvent evt) { 1030 Layer layer = getModel().getLayer(layerList.getSelectedRow()); 1031 menu = new LayerListPopup(getModel().getSelectedLayers(), layer); 1032 super.showMenu(evt); 1033 } 1034 } 1035 1036 /** 1037 * The action to move up the currently selected entries in the list. 1038 */ 1039 class MoveUpAction extends AbstractAction implements IEnabledStateUpdating{ 1040 public MoveUpAction() { 1041 putValue(NAME, tr("Move up")); 1042 putValue(SMALL_ICON, ImageProvider.get("dialogs", "up")); 1043 putValue(SHORT_DESCRIPTION, tr("Move the selected layer one row up.")); 1044 updateEnabledState(); 1045 } 1046 1047 @Override 1048 public void updateEnabledState() { 1049 setEnabled(model.canMoveUp()); 1050 } 1051 1052 @Override 1053 public void actionPerformed(ActionEvent e) { 1054 model.moveUp(); 1055 } 1056 } 1057 1058 /** 1059 * The action to move down the currently selected entries in the list. 1060 */ 1061 class MoveDownAction extends AbstractAction implements IEnabledStateUpdating { 1062 public MoveDownAction() { 1063 putValue(NAME, tr("Move down")); 1064 putValue(SMALL_ICON, ImageProvider.get("dialogs", "down")); 1065 putValue(SHORT_DESCRIPTION, tr("Move the selected layer one row down.")); 1066 updateEnabledState(); 1067 } 1068 1069 @Override 1070 public void updateEnabledState() { 1071 setEnabled(model.canMoveDown()); 1072 } 1073 1074 @Override 1075 public void actionPerformed(ActionEvent e) { 1076 model.moveDown(); 1077 } 1078 } 1079 1080 /** 1081 * Observer interface to be implemented by views using {@link LayerListModel} 1082 * 1083 */ 1084 public interface LayerListModelListener { 1085 public void makeVisible(int index, Layer layer); 1086 public void refresh(); 1087 } 1088 1089 /** 1090 * The layer list model. The model manages a list of layers and provides methods for 1091 * moving layers up and down, for toggling their visibility, and for activating a layer. 1092 * 1093 * The model is a {@link TableModel} and it provides a {@link ListSelectionModel}. It expects 1094 * to be configured with a {@link DefaultListSelectionModel}. The selection model is used 1095 * to update the selection state of views depending on messages sent to the model. 1096 * 1097 * The model manages a list of {@link LayerListModelListener} which are mainly notified if 1098 * the model requires views to make a specific list entry visible. 1099 * 1100 * It also listens to {@link PropertyChangeEvent}s of every {@link Layer} it manages, in particular to 1101 * the properties {@link Layer#VISIBLE_PROP} and {@link Layer#NAME_PROP}. 1102 */ 1103 public final class LayerListModel extends AbstractTableModel implements MapView.LayerChangeListener, PropertyChangeListener { 1104 /** manages list selection state*/ 1105 private DefaultListSelectionModel selectionModel; 1106 private CopyOnWriteArrayList<LayerListModelListener> listeners; 1107 1108 /** 1109 * constructor 1110 * 1111 * @param selectionModel the list selection model 1112 */ 1113 private LayerListModel(DefaultListSelectionModel selectionModel) { 1114 this.selectionModel = selectionModel; 1115 listeners = new CopyOnWriteArrayList<>(); 1116 } 1117 1118 /** 1119 * Adds a listener to this model 1120 * 1121 * @param listener the listener 1122 */ 1123 public void addLayerListModelListener(LayerListModelListener listener) { 1124 if (listener != null) { 1125 listeners.addIfAbsent(listener); 1126 } 1127 } 1128 1129 /** 1130 * removes a listener from this model 1131 * @param listener the listener 1132 * 1133 */ 1134 public void removeLayerListModelListener(LayerListModelListener listener) { 1135 listeners.remove(listener); 1136 } 1137 1138 /** 1139 * Fires a make visible event to listeners 1140 * 1141 * @param index the index of the row to make visible 1142 * @param layer the layer at this index 1143 * @see LayerListModelListener#makeVisible(int, Layer) 1144 */ 1145 protected void fireMakeVisible(int index, Layer layer) { 1146 for (LayerListModelListener listener : listeners) { 1147 listener.makeVisible(index, layer); 1148 } 1149 } 1150 1151 /** 1152 * Fires a refresh event to listeners of this model 1153 * 1154 * @see LayerListModelListener#refresh() 1155 */ 1156 protected void fireRefresh() { 1157 for (LayerListModelListener listener : listeners) { 1158 listener.refresh(); 1159 } 1160 } 1161 1162 /** 1163 * Populates the model with the current layers managed by 1164 * {@link MapView}. 1165 * 1166 */ 1167 public void populate() { 1168 for (Layer layer: getLayers()) { 1169 // make sure the model is registered exactly once 1170 // 1171 layer.removePropertyChangeListener(this); 1172 layer.addPropertyChangeListener(this); 1173 } 1174 fireTableDataChanged(); 1175 } 1176 1177 /** 1178 * Marks <code>layer</code> as selected layer. Ignored, if 1179 * layer is null. 1180 * 1181 * @param layer the layer. 1182 */ 1183 public void setSelectedLayer(Layer layer) { 1184 if (layer == null) 1185 return; 1186 int idx = getLayers().indexOf(layer); 1187 if (idx >= 0) { 1188 selectionModel.setSelectionInterval(idx, idx); 1189 } 1190 ensureSelectedIsVisible(); 1191 } 1192 1193 /** 1194 * Replies the list of currently selected layers. Never null, but may 1195 * be empty. 1196 * 1197 * @return the list of currently selected layers. Never null, but may 1198 * be empty. 1199 */ 1200 public List<Layer> getSelectedLayers() { 1201 List<Layer> selected = new ArrayList<>(); 1202 for (int i=0; i<getLayers().size(); i++) { 1203 if (selectionModel.isSelectedIndex(i)) { 1204 selected.add(getLayers().get(i)); 1205 } 1206 } 1207 return selected; 1208 } 1209 1210 /** 1211 * Replies a the list of indices of the selected rows. Never null, 1212 * but may be empty. 1213 * 1214 * @return the list of indices of the selected rows. Never null, 1215 * but may be empty. 1216 */ 1217 public List<Integer> getSelectedRows() { 1218 List<Integer> selected = new ArrayList<>(); 1219 for (int i=0; i<getLayers().size();i++) { 1220 if (selectionModel.isSelectedIndex(i)) { 1221 selected.add(i); 1222 } 1223 } 1224 return selected; 1225 } 1226 1227 /** 1228 * Invoked if a layer managed by {@link MapView} is removed 1229 * 1230 * @param layer the layer which is removed 1231 */ 1232 protected void onRemoveLayer(Layer layer) { 1233 if (layer == null) 1234 return; 1235 layer.removePropertyChangeListener(this); 1236 final int size = getRowCount(); 1237 final List<Integer> rows = getSelectedRows(); 1238 GuiHelper.runInEDTAndWait(new Runnable() { 1239 @Override 1240 public void run() { 1241 if (rows.isEmpty() && size > 0) { 1242 selectionModel.setSelectionInterval(size-1, size-1); 1243 } 1244 fireTableDataChanged(); 1245 fireRefresh(); 1246 ensureActiveSelected(); 1247 } 1248 }); 1249 } 1250 1251 /** 1252 * Invoked when a layer managed by {@link MapView} is added 1253 * 1254 * @param layer the layer 1255 */ 1256 protected void onAddLayer(Layer layer) { 1257 if (layer == null) return; 1258 layer.addPropertyChangeListener(this); 1259 fireTableDataChanged(); 1260 int idx = getLayers().indexOf(layer); 1261 layerList.setRowHeight(idx, Math.max(16, layer.getIcon().getIconHeight())); 1262 selectionModel.setSelectionInterval(idx, idx); 1263 ensureSelectedIsVisible(); 1264 } 1265 1266 /** 1267 * Replies the first layer. Null if no layers are present 1268 * 1269 * @return the first layer. Null if no layers are present 1270 */ 1271 public Layer getFirstLayer() { 1272 if (getRowCount() == 0) return null; 1273 return getLayers().get(0); 1274 } 1275 1276 /** 1277 * Replies the layer at position <code>index</code> 1278 * 1279 * @param index the index 1280 * @return the layer at position <code>index</code>. Null, 1281 * if index is out of range. 1282 */ 1283 public Layer getLayer(int index) { 1284 if (index < 0 || index >= getRowCount()) 1285 return null; 1286 return getLayers().get(index); 1287 } 1288 1289 /** 1290 * Replies true if the currently selected layers can move up 1291 * by one position 1292 * 1293 * @return true if the currently selected layers can move up 1294 * by one position 1295 */ 1296 public boolean canMoveUp() { 1297 List<Integer> sel = getSelectedRows(); 1298 return !sel.isEmpty() && sel.get(0) > 0; 1299 } 1300 1301 /** 1302 * Move up the currently selected layers by one position 1303 * 1304 */ 1305 public void moveUp() { 1306 if (!canMoveUp()) return; 1307 List<Integer> sel = getSelectedRows(); 1308 for (int row : sel) { 1309 Layer l1 = getLayers().get(row); 1310 Layer l2 = getLayers().get(row-1); 1311 Main.map.mapView.moveLayer(l2,row); 1312 Main.map.mapView.moveLayer(l1, row-1); 1313 } 1314 fireTableDataChanged(); 1315 selectionModel.clearSelection(); 1316 for (int row : sel) { 1317 selectionModel.addSelectionInterval(row-1, row-1); 1318 } 1319 ensureSelectedIsVisible(); 1320 } 1321 1322 /** 1323 * Replies true if the currently selected layers can move down 1324 * by one position 1325 * 1326 * @return true if the currently selected layers can move down 1327 * by one position 1328 */ 1329 public boolean canMoveDown() { 1330 List<Integer> sel = getSelectedRows(); 1331 return !sel.isEmpty() && sel.get(sel.size()-1) < getLayers().size()-1; 1332 } 1333 1334 /** 1335 * Move down the currently selected layers by one position 1336 * 1337 */ 1338 public void moveDown() { 1339 if (!canMoveDown()) return; 1340 List<Integer> sel = getSelectedRows(); 1341 Collections.reverse(sel); 1342 for (int row : sel) { 1343 Layer l1 = getLayers().get(row); 1344 Layer l2 = getLayers().get(row+1); 1345 Main.map.mapView.moveLayer(l1, row+1); 1346 Main.map.mapView.moveLayer(l2, row); 1347 } 1348 fireTableDataChanged(); 1349 selectionModel.clearSelection(); 1350 for (int row : sel) { 1351 selectionModel.addSelectionInterval(row+1, row+1); 1352 } 1353 ensureSelectedIsVisible(); 1354 } 1355 1356 /** 1357 * Make sure the first of the selected layers is visible in the 1358 * views of this model. 1359 * 1360 */ 1361 protected void ensureSelectedIsVisible() { 1362 int index = selectionModel.getMinSelectionIndex(); 1363 if (index < 0) return; 1364 if (index >= getLayers().size()) return; 1365 Layer layer = getLayers().get(index); 1366 fireMakeVisible(index, layer); 1367 } 1368 1369 /** 1370 * Replies a list of layers which are possible merge targets 1371 * for <code>source</code> 1372 * 1373 * @param source the source layer 1374 * @return a list of layers which are possible merge targets 1375 * for <code>source</code>. Never null, but can be empty. 1376 */ 1377 public List<Layer> getPossibleMergeTargets(Layer source) { 1378 List<Layer> targets = new ArrayList<>(); 1379 if (source == null) 1380 return targets; 1381 for (Layer target : getLayers()) { 1382 if (source == target) { 1383 continue; 1384 } 1385 if (target.isMergable(source) && source.isMergable(target)) { 1386 targets.add(target); 1387 } 1388 } 1389 return targets; 1390 } 1391 1392 /** 1393 * Replies the list of layers currently managed by {@link MapView}. 1394 * Never null, but can be empty. 1395 * 1396 * @return the list of layers currently managed by {@link MapView}. 1397 * Never null, but can be empty. 1398 */ 1399 public List<Layer> getLayers() { 1400 if (!Main.isDisplayingMapView()) 1401 return Collections.<Layer>emptyList(); 1402 return Main.map.mapView.getAllLayersAsList(); 1403 } 1404 1405 /** 1406 * Ensures that at least one layer is selected in the layer dialog 1407 * 1408 */ 1409 protected void ensureActiveSelected() { 1410 if (getLayers().isEmpty()) 1411 return; 1412 final Layer activeLayer = getActiveLayer(); 1413 if (activeLayer != null) { 1414 // there's an active layer - select it and make it 1415 // visible 1416 int idx = getLayers().indexOf(activeLayer); 1417 selectionModel.setSelectionInterval(idx, idx); 1418 ensureSelectedIsVisible(); 1419 } else { 1420 // no active layer - select the first one and make 1421 // it visible 1422 selectionModel.setSelectionInterval(0, 0); 1423 ensureSelectedIsVisible(); 1424 } 1425 } 1426 1427 /** 1428 * Replies the active layer. null, if no active layer is available 1429 * 1430 * @return the active layer. null, if no active layer is available 1431 */ 1432 protected Layer getActiveLayer() { 1433 if (!Main.isDisplayingMapView()) return null; 1434 return Main.map.mapView.getActiveLayer(); 1435 } 1436 1437 /* ------------------------------------------------------------------------------ */ 1438 /* Interface TableModel */ 1439 /* ------------------------------------------------------------------------------ */ 1440 1441 @Override 1442 public int getRowCount() { 1443 List<Layer> layers = getLayers(); 1444 if (layers == null) return 0; 1445 return layers.size(); 1446 } 1447 1448 @Override 1449 public int getColumnCount() { 1450 return 3; 1451 } 1452 1453 @Override 1454 public Object getValueAt(int row, int col) { 1455 if (row >= 0 && row < getLayers().size()) { 1456 switch (col) { 1457 case 0: return getLayers().get(row) == getActiveLayer(); 1458 case 1: return getLayers().get(row); 1459 case 2: return getLayers().get(row); 1460 default: throw new RuntimeException(); 1461 } 1462 } 1463 return null; 1464 } 1465 1466 @Override 1467 public boolean isCellEditable(int row, int col) { 1468 if (col == 0 && getActiveLayer() == getLayers().get(row)) 1469 return false; 1470 return true; 1471 } 1472 1473 @Override 1474 public void setValueAt(Object value, int row, int col) { 1475 Layer l = getLayers().get(row); 1476 switch (col) { 1477 case 0: 1478 Main.map.mapView.setActiveLayer(l); 1479 l.setVisible(true); 1480 break; 1481 case 1: 1482 l.setVisible((Boolean) value); 1483 break; 1484 case 2: 1485 l.setName((String) value); 1486 break; 1487 default: throw new RuntimeException(); 1488 } 1489 fireTableCellUpdated(row, col); 1490 } 1491 1492 /* ------------------------------------------------------------------------------ */ 1493 /* Interface LayerChangeListener */ 1494 /* ------------------------------------------------------------------------------ */ 1495 @Override 1496 public void activeLayerChange(final Layer oldLayer, final Layer newLayer) { 1497 GuiHelper.runInEDTAndWait(new Runnable() { 1498 @Override 1499 public void run() { 1500 if (oldLayer != null) { 1501 int idx = getLayers().indexOf(oldLayer); 1502 if (idx >= 0) { 1503 fireTableRowsUpdated(idx,idx); 1504 } 1505 } 1506 1507 if (newLayer != null) { 1508 int idx = getLayers().indexOf(newLayer); 1509 if (idx >= 0) { 1510 fireTableRowsUpdated(idx,idx); 1511 } 1512 } 1513 ensureActiveSelected(); 1514 } 1515 }); 1516 } 1517 1518 @Override 1519 public void layerAdded(Layer newLayer) { 1520 onAddLayer(newLayer); 1521 } 1522 1523 @Override 1524 public void layerRemoved(final Layer oldLayer) { 1525 onRemoveLayer(oldLayer); 1526 } 1527 1528 /* ------------------------------------------------------------------------------ */ 1529 /* Interface PropertyChangeListener */ 1530 /* ------------------------------------------------------------------------------ */ 1531 @Override 1532 public void propertyChange(PropertyChangeEvent evt) { 1533 if (evt.getSource() instanceof Layer) { 1534 Layer layer = (Layer)evt.getSource(); 1535 final int idx = getLayers().indexOf(layer); 1536 if (idx < 0) return; 1537 fireRefresh(); 1538 } 1539 } 1540 } 1541 1542 static class LayerList extends JTable { 1543 public LayerList(TableModel dataModel) { 1544 super(dataModel); 1545 } 1546 1547 public void scrollToVisible(int row, int col) { 1548 if (!(getParent() instanceof JViewport)) 1549 return; 1550 JViewport viewport = (JViewport) getParent(); 1551 Rectangle rect = getCellRect(row, col, true); 1552 Point pt = viewport.getViewPosition(); 1553 rect.setLocation(rect.x - pt.x, rect.y - pt.y); 1554 viewport.scrollRectToVisible(rect); 1555 } 1556 } 1557 1558 /** 1559 * Creates a {@link ShowHideLayerAction} for <code>layer</code> in the 1560 * context of this {@link LayerListDialog}. 1561 * 1562 * @return the action 1563 */ 1564 public ShowHideLayerAction createShowHideLayerAction() { 1565 ShowHideLayerAction act = new ShowHideLayerAction(true); 1566 act.putValue(Action.NAME, tr("Show/Hide")); 1567 return act; 1568 } 1569 1570 /** 1571 * Creates a {@link DeleteLayerAction} for <code>layer</code> in the 1572 * context of this {@link LayerListDialog}. 1573 * 1574 * @return the action 1575 */ 1576 public DeleteLayerAction createDeleteLayerAction() { 1577 // the delete layer action doesn't depend on the current layer 1578 return new DeleteLayerAction(); 1579 } 1580 1581 /** 1582 * Creates a {@link ActivateLayerAction} for <code>layer</code> in the 1583 * context of this {@link LayerListDialog}. 1584 * 1585 * @param layer the layer 1586 * @return the action 1587 */ 1588 public ActivateLayerAction createActivateLayerAction(Layer layer) { 1589 return new ActivateLayerAction(layer); 1590 } 1591 1592 /** 1593 * Creates a {@link MergeLayerAction} for <code>layer</code> in the 1594 * context of this {@link LayerListDialog}. 1595 * 1596 * @param layer the layer 1597 * @return the action 1598 */ 1599 public MergeAction createMergeLayerAction(Layer layer) { 1600 return new MergeAction(layer); 1601 } 1602 1603 public static Layer getLayerForIndex(int index) { 1604 1605 if (!Main.isDisplayingMapView()) 1606 return null; 1607 1608 List<Layer> layers = Main.map.mapView.getAllLayersAsList(); 1609 1610 if (index < layers.size() && index >= 0) 1611 return layers.get(index); 1612 else 1613 return null; 1614 } 1615 1616 // This is not Class<? extends Layer> on purpose, to allow asking for layers implementing some interface 1617 public static List<MultikeyInfo> getLayerInfoByClass(Class<?> layerClass) { 1618 1619 List<MultikeyInfo> result = new ArrayList<>(); 1620 1621 if (!Main.isDisplayingMapView()) 1622 return result; 1623 1624 List<Layer> layers = Main.map.mapView.getAllLayersAsList(); 1625 1626 int index = 0; 1627 for (Layer l: layers) { 1628 if (layerClass.isAssignableFrom(l.getClass())) { 1629 result.add(new MultikeyInfo(index, l.getName())); 1630 } 1631 index++; 1632 } 1633 1634 return result; 1635 } 1636 1637 public static boolean isLayerValid(Layer l) { 1638 if (l == null) 1639 return false; 1640 1641 if (!Main.isDisplayingMapView()) 1642 return false; 1643 1644 return Main.map.mapView.getAllLayersAsList().contains(l); 1645 } 1646 1647 public static MultikeyInfo getLayerInfo(Layer l) { 1648 1649 if (l == null) 1650 return null; 1651 1652 if (!Main.isDisplayingMapView()) 1653 return null; 1654 1655 int index = Main.map.mapView.getAllLayersAsList().indexOf(l); 1656 if (index < 0) 1657 return null; 1658 1659 return new MultikeyInfo(index, l.getName()); 1660 } 1661}