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