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