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