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