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