/** * OrbisGIS is a java GIS application dedicated to research in GIScience. * OrbisGIS is developed by the GIS group of the DECIDE team of the * Lab-STICC CNRS laboratory, see <http://www.lab-sticc.fr/>. * * The GIS group of the DECIDE team is located at : * * Laboratoire Lab-STICC – CNRS UMR 6285 * Equipe DECIDE * UNIVERSITÉ DE BRETAGNE-SUD * Institut Universitaire de Technologie de Vannes * 8, Rue Montaigne - BP 561 56017 Vannes Cedex * * OrbisGIS is distributed under GPL 3 license. * * Copyright (C) 2007-2014 CNRS (IRSTV FR CNRS 2488) * Copyright (C) 2015-2017 CNRS (Lab-STICC UMR CNRS 6285) * * This file is part of OrbisGIS. * * OrbisGIS is free software: you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later * version. * * OrbisGIS is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * OrbisGIS. If not, see <http://www.gnu.org/licenses/>. * * For more information, please consult: <http://www.orbisgis.org/> * or contact directly: * info_at_ orbisgis.org */ package org.orbisgis.view.toc; import org.orbisgis.commons.progress.SwingWorkerPM; import org.orbisgis.coremap.process.ZoomToSelection; import org.orbisgis.editorjdbc.EditableSource; import org.orbisgis.frameworkapi.CoreWorkspace; import org.orbisgis.mapeditorapi.MapElement; import org.orbisgis.sif.components.actions.ActionCommands; import org.orbisgis.sif.components.actions.DefaultAction; import org.orbisgis.sif.components.resourceTree.AbstractTreeModel; import org.orbisgis.sif.docking.DockingPanelParameters; import org.orbisgis.sif.edition.EditableElement; import org.orbisgis.sif.edition.EditableTransferEvent; import org.orbisgis.sif.edition.EditableTransferListener; import org.orbisgis.sif.edition.EditorDockable; import org.orbisgis.sif.edition.EditorManager; import org.orbisgis.tocapi.LayerAction; import org.orbisgis.tocapi.StyleAction; import org.orbisgis.tocapi.TocActionFactory; import org.orbisgis.tocapi.TocExt; import org.orbisgis.tocapi.TocTreeNodeLayer; import org.orbisgis.tocapi.TocTreeNodeStyle; import org.orbisgis.view.toc.icons.TocIcon; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.wms.Capabilities; import com.vividsolutions.wms.MapImageFormatChooser; import com.vividsolutions.wms.MapLayer; import com.vividsolutions.wms.WMService; import java.awt.BorderLayout; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.beans.EventHandler; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.net.URI; import java.net.URISyntaxException; import java.sql.Connection; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicBoolean; import javax.swing.Action; import javax.swing.JComponent; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTree; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.MutableTreeNode; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import javax.xml.bind.JAXBElement; import net.opengis.se._2_0.core.StyleType; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; import org.osgi.service.component.annotations.ReferenceCardinality; import org.osgi.service.component.annotations.ReferencePolicy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.h2gis.utilities.JDBCUtilities; import org.h2gis.utilities.SFSUtilities; import org.h2gis.utilities.TableLocation; import org.orbisgis.corejdbc.MetaData; import org.orbisgis.corejdbc.TableEditEvent; import org.orbisgis.corejdbc.TableEditListener; import org.orbisgis.coremap.layerModel.BeanLayer; import org.orbisgis.coremap.layerModel.ILayer; import org.orbisgis.coremap.layerModel.Layer; import org.orbisgis.coremap.layerModel.LayerCollection; import org.orbisgis.coremap.layerModel.LayerCollectionEvent; import org.orbisgis.coremap.layerModel.LayerException; import org.orbisgis.coremap.layerModel.LayerListener; import org.orbisgis.coremap.layerModel.LayerListenerEvent; import org.orbisgis.coremap.layerModel.MapContext; import org.orbisgis.coremap.layerModel.MapContextListener; import org.orbisgis.coremap.layerModel.SelectionEvent; import org.orbisgis.coremap.renderer.se.CompositeSymbolizer; import org.orbisgis.coremap.renderer.se.Rule; import org.orbisgis.coremap.renderer.se.SeExceptions; import org.orbisgis.coremap.renderer.se.Style; import org.orbisgis.coremap.renderer.se.Symbolizer; import org.orbisgis.commons.progress.ProgressMonitor; import org.orbisgis.mapeditorapi.MapEditorExtension; import org.orbisgis.sif.SIFWizard; import org.orbisgis.sif.UIFactory; import org.orbisgis.sif.UIPanel; import org.orbisgis.sif.components.OpenFilePanel; import org.orbisgis.sif.components.SaveFilePanel; import org.orbisgis.view.toc.actions.cui.SimpleStyleEditor; import org.orbisgis.view.toc.actions.cui.legend.wizard.LegendWizard; import org.orbisgis.view.toc.wms.LayerConfigurationPanel; import org.orbisgis.view.toc.wms.SRSPanel; import org.orbisgis.view.toc.wms.WMSConnectionPanel; import org.xnap.commons.i18n.I18n; import org.xnap.commons.i18n.I18nFactory; /** * The Toc Panel component */ @Component(service = EditorDockable.class) public class Toc extends JPanel implements EditorDockable, TocExt, TableEditListener { //The UID must be incremented when the serialization is not compatible with the new version of this class private static final long serialVersionUID = 1L; private static final I18n I18N = I18nFactory.getI18n(Toc.class); private static final Logger LOGGER_POPUP = LoggerFactory.getLogger("gui.popup" + Toc.class); private static final Logger LOGGER = LoggerFactory.getLogger("gui." + Toc.class); private DockingPanelParameters dockingPanelParameters; private transient MapContext mapContext = null; private JTree tree; private transient DefaultTreeModel treeModel; private transient TocRenderer treeRenderer; //When this boolean is false, the selection event is not fired private AtomicBoolean fireSelectionEvent = new AtomicBoolean(true); //When this boolean is false, the selection is not propagated to tables private AtomicBoolean fireRowSelectionEvent = new AtomicBoolean(true); //Listen for map context changes private transient TocMapContextListener tocMapContextListener = new TocMapContextListener(); //Listen for all layers changes private transient TocLayerListener tocLayerListener = new TocLayerListener(); private PropertyChangeListener tocStyleListListener = EventHandler.create(PropertyChangeListener.class, this, "onStyleListChange",""); private PropertyChangeListener tocStyleListener = EventHandler.create(PropertyChangeListener.class,this,"onStyleChange",""); //Loaded editable map private transient MapElement mapElement = null; private PropertyChangeListener modificationListener = EventHandler.create(PropertyChangeListener.class,this,"onMapModified",""); private Action saveAction; // Actions containers private ActionCommands popupActions = new ActionCommands(); private PropertyChangeListener mapContextPropertyChange = EventHandler.create(PropertyChangeListener.class,this,"onMapContextPropertyChange",""); private EditorManager editorManager; private ExecutorService executorService; private CoreWorkspace coreWorkspace; private MapEditorExtension mapEditorExtension; /** * Constructor */ public Toc() { super(new BorderLayout()); } @Reference public void setEditorManager(EditorManager editorManager) { this.editorManager = editorManager; } public void unsetEditorManager(EditorManager editorManager) { this.editorManager = null; } @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC) public void setExecutorService(ExecutorService executorService) { this.executorService = executorService; } public void unsetExecutorService(ExecutorService executorService) { this.executorService = null; } @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) public void addTocActionFactory(TocActionFactory tocActionFactory) { popupActions.addActionFactory(tocActionFactory, this); } public void removeTocActionFactory(TocActionFactory tocActionFactory) { popupActions.removeActionFactory(tocActionFactory); } @Reference public void setCoreWorkspace(CoreWorkspace coreWorkspace) { this.coreWorkspace = coreWorkspace; } public void unsetCoreWorkspace(CoreWorkspace coreWorkspace) { this.coreWorkspace = null; } @Reference public void setMapEditorExtension(MapEditorExtension mapEditorExtension){ this.mapEditorExtension=mapEditorExtension; } public void unsetMapEditorExtension(MapEditorExtension mapEditorExtension) { this.mapEditorExtension = null; } @Activate public void activate() { //Set docking parameters dockingPanelParameters = new DockingPanelParameters(); dockingPanelParameters.setName("toc"); dockingPanelParameters.setTitle(I18N.tr("Toc")); dockingPanelParameters.setTitleIcon(TocIcon.getIcon("toc")); initTitleActions(); initPopupActions(); //Initialise an empty tree add(new JScrollPane(makeTree())); // Fetch Map // Find a map element among other editors setEditableElement(MapElement.fetchFirstMapElement(editorManager)); popupActions.setAccelerators(tree); } private void initTitleActions() { List<Action> tools = new ArrayList<Action>(); saveAction = new DefaultAction("SAVE_MAP", I18N.tr("Save"), I18N.tr("Save the Map"), TocIcon.getIcon("save"), EventHandler.create(ActionListener.class, this, "onSaveMapContext"), KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK)); saveAction.setEnabled(false); tools.add(saveAction); DefaultAction refreshIconsAction = new DefaultAction("REFRESH_ICONS", I18N.tr("Refresh"), I18N.tr("Refresh layer icons"), TocIcon.getIcon("refresh"), EventHandler.create(ActionListener.class, this, "onRefreshTocTree"), KeyStroke.getKeyStroke("ctrl R")); tools.add(refreshIconsAction); tools.add(new DefaultAction(TocActionFactory.G_ADD, I18N.tr("Add")).setMenuGroup(true)); tools.add(new DefaultAction(TocActionFactory.A_ADD_LAYER_GROUP, I18N.tr("Add layer group"), I18N.tr("Add layer group to the map context"), TocIcon.getIcon("folder_add"), EventHandler.create(ActionListener.class, this, "onAddGroup"), null) .setParent(TocActionFactory.G_ADD)); tools.add(new DefaultAction(TocActionFactory.A_WMS_LAYER, I18N.tr("Add WMS layer"), I18N.tr("Add WMS layer to the map context"), TocIcon.getIcon("world_add"), EventHandler.create(ActionListener.class, this, "onAddWMSLayer"), null) .setParent(TocActionFactory.G_ADD)); dockingPanelParameters.setDockActions(tools); } private void initPopupActions() { // Layer actions popupActions.addAction(new LayerAction(this, TocActionFactory.A_ZOOM_TO, I18N.tr("Zoom to"), I18N.tr("Zoom to the layer bounding box"), TocIcon.getIcon("zoom_layer"), EventHandler.create(ActionListener.class, this, "zoomToLayer"), null) .setLogicalGroup(TocActionFactory.G_ZOOM)); popupActions.addAction(new LayerAction(this, TocActionFactory.A_ZOOM_TO_SELECTION, I18N.tr("Zoom to selection"), I18N.tr("Zoom to selected geometries"), TocIcon.getIcon("zoom_selected"), EventHandler.create(ActionListener.class, this, "zoomToLayerSelection"), null) .setOnLayerWithRowSelection(true) .setLogicalGroup(TocActionFactory.G_ZOOM)); popupActions.addAction(new LayerAction(this, TocActionFactory.A_CLEAR_SELECTION, I18N.tr("Clear selection"), I18N.tr("Clear the selected geometries"), TocIcon.getIcon("edit-clear"), EventHandler.create(ActionListener.class,this, "clearLayerRowSelection"),null) .setOnLayerWithRowSelection(true) .setLogicalGroup(TocActionFactory.G_SELECTION)); popupActions.addAction(new LayerAction(this, TocActionFactory.A_IMPORT_STYLE, I18N.tr("Import style"), I18N.tr("Import a style from a file."), TocIcon.getIcon("palette_import"), EventHandler.create(ActionListener.class, this, "onImportStyle"),KeyStroke.getKeyStroke("ctrl I")) .setSingleSelection(true) .setOnRealLayerOnly(true) .setOnVectorSourceOnly(true) .setLogicalGroup(TocActionFactory.G_STYLE)); popupActions.addAction(new LayerAction(this, TocActionFactory.A_ADD_STYLE, I18N.tr("Create a thematic map"), I18N.tr("Add a new legend."), TocIcon.getIcon("palette_add"), EventHandler.create(ActionListener.class, this, "onAddStyle"),KeyStroke.getKeyStroke("ctrl N")) .setSingleSelection(true) .setOnRealLayerOnly(true) .setOnVectorSourceOnly(true) .setLogicalGroup(TocActionFactory.G_STYLE)); //TODO: DataSource Drawing Actions /*popupActions.addAction(new EditLayerSourceAction(this,TocActionFactory.A_EDIT_GEOMETRY, I18N.tr("Start editing"), I18N.tr("The edit geometry toolbar will update this layer's data source."), TocIcon.getIcon("pencil"), EventHandler.create(ActionListener.class,this, "onMenuSetActiveLayer"),null) .setEnabledOnNotActiveLayer(true) .setSingleSelection(true)); popupActions.addAction(new EditLayerSourceAction(this,TocActionFactory.A_STOP_EDIT_GEOMETRY, I18N.tr("Stop editing"), I18N.tr("Close the edit geometry toolbar."), TocIcon.getIcon("stop"), EventHandler.create(ActionListener.class,this, "onMenuUnsetActiveLayer"),null) .setEnabledOnActiveLayer(true) .setSingleSelection(true));*/ popupActions.addAction(new LayerAction(this,TocActionFactory.A_ADD_LAYER_GROUP, I18N.tr("Add layer group"),I18N.tr("Add layer group to the map context"), TocIcon.getIcon("folder_add"), EventHandler.create(ActionListener.class, this, "onAddGroup"),null) .setOnLayerGroup(true) .setSingleSelection(true) .setOnEmptySelection(true) .setLogicalGroup(TocActionFactory.G_LAYER_GROUP)); DefaultAction addWMSAction = new LayerAction(this,"ADD_WMS_LAYER", I18N.tr("Add WMS layer"), I18N.tr("Add WMS layer to the map context"), TocIcon.getIcon("world_add"), EventHandler.create(ActionListener.class, this, "onAddWMSLayer"), KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.CTRL_DOWN_MASK)) .setOnLayerGroup(true) .setSingleSelection(true) .setOnEmptySelection(true) .setLogicalGroup(TocActionFactory.G_LAYER_GROUP); popupActions.addAction(addWMSAction); popupActions.addAction(new LayerAction(this, TocActionFactory.A_REMOVE_LAYER, I18N.tr("Remove layer"), I18N.tr("Remove the layer from the map context"),TocIcon.getIcon("remove"), EventHandler.create(ActionListener.class, this, "onDeleteLayer"), KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0)) .setLogicalGroup(TocActionFactory.G_REMOVE)); // Style actions popupActions.addAction(new StyleAction(this,TocActionFactory.A_ADD_LEGEND, I18N.tr("Create a thematic map"), I18N.tr("Add a legend in this style"), TocIcon.getIcon("palette_add"), EventHandler.create(ActionListener.class, this, "onAddLegend"),null).setOnSingleStyleSelection(true)); popupActions.addAction(new StyleAction(this,TocActionFactory.A_SIMPLE_EDITION, I18N.tr("Style editor"), I18N.tr("Open the simple editor for SE styles"), TocIcon.getIcon("palette_edit"), EventHandler.create(ActionListener.class, this, "onSimpleEditor"),null).setOnSingleStyleSelection(true)); popupActions.addAction(new StyleAction(this,TocActionFactory.A_REMOVE_STYLE, I18N.tr("Remove style"), I18N.tr("Remove this style from the associated layer."), TocIcon.getIcon("palette_remove"), EventHandler.create(ActionListener.class, this, "onDeleteStyle"),null)); popupActions.addAction(new StyleAction(this, TocActionFactory.A_EXPORT_STYLE, I18N.tr("Export style"), I18N.tr("Export this style from the associated layer."), TocIcon.getIcon("palette_export"), EventHandler.create(ActionListener.class, this, "onExportStyle"), null).setOnSingleStyleSelection(true)); } /** * User click on the save button */ public void onSaveMapContext() { if(mapElement!=null) { mapElement.save(); } } /** * Refresh TOC tree icons */ public void onRefreshTocTree() { treeRenderer.clearTableIconCache(); treeModel.reload(); } /** * The user starts or stops editing a layer's geometries */ public void onMenuSetActiveLayer() { if(mapContext!=null) { List<ILayer> selectedLayers = getSelectedLayers(); if(!selectedLayers.isEmpty()) { ILayer selectedLayer = selectedLayers.get(0); if(mapContext.getActiveLayer()==null || !mapContext.getActiveLayer().equals(selectedLayer)) { mapContext.setActiveLayer(selectedLayer); } } } } public void onMenuCommitDataSource() { // TODO clear UNDO/REDO history /* if(mapContext!=null) { List<ILayer> selectedLayers = getSelectedLayers(); if(!selectedLayers.isEmpty()) { for(ILayer layer : selectedLayers) { DataSource source = layer.getDataSource(); if(source!=null) { if(source.isModified()) { try { source.commit(); } catch (Exception ex) { LOGGER.error(ex.getLocalizedMessage(),ex); } } } } } } */ } /** * The user cancels modifications on layer node */ public void onMenuSyncDataSource() { // TODO UNDO All commands /* int response = JOptionPane.showConfirmDialog(UIFactory.getMainFrame(), I18N.tr("Are you sure to cancel all your modifications ?"), I18N.tr("Return to the original state"), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); if(response==JOptionPane.YES_OPTION) { if(mapContext!=null) { List<ILayer> selectedLayers = getSelectedLayers(); for(ILayer layer : selectedLayers) { DataSource dataSource = layer.getDataSource(); if(dataSource!=null) { try { dataSource.syncWithSource(); } catch (DriverException ex) { LOGGER.error(ex.getLocalizedMessage(),ex); } } } } } */ } /** * The user starts or stops editing a layer's geometries */ public void onMenuUnsetActiveLayer() { if(mapContext!=null) { List<ILayer> selectedLayers = getSelectedLayers(); if(!selectedLayers.isEmpty()) { ILayer selectedLayer = selectedLayers.get(0); if(mapContext.getActiveLayer()!=null && mapContext.getActiveLayer().equals(selectedLayer)) { mapContext.setActiveLayer(null); } } } } /** * The Map Element has been updated * @param evt */ public void onMapModified(PropertyChangeEvent evt) { if(mapElement!=null && MapElement.PROP_MODIFIED.equals(evt.getPropertyName())) { saveAction.setEnabled(mapElement.isModified()); treeModel.reload(); } } /** * * @return The editable map */ public MapElement getMapElement() { return mapElement; } private TreeNode[] createTreeNodeArray(ILayer[] layers) { TreeNode[] nodes = new TreeNode[layers.length]; for(int i=0;i<layers.length;i++) { nodes[i]=new TocTreeNodeLayer(layers[i]); } return nodes; } private TreeNode[] createTreeNodeArray(Style[] styles) { TreeNode[] nodes = new TreeNode[styles.length]; for(int i=0;i<styles.length;i++) { nodes[i]=new TocTreeNodeStyle(styles[i]); } return nodes; } private void setTocSelection(MapContext mapContext) { ILayer[] layers = mapContext.getSelectedLayers(); Style[] styles = mapContext.getSelectedStyles(); TreePath[] selectedPaths = new TreePath[layers.length + styles.length]; for (int i = 0; i < layers.length; i++) { selectedPaths[i] = new TreePath(createTreeNodeArray(layers[i].getLayerPath())); } for (int i = 0; i < styles.length; i++) { Style s = styles[i]; TreeNode[] lays = createTreeNodeArray(s.getLayer().getLayerPath()); TreeNode[] path = new TreeNode[lays.length + 1]; System.arraycopy(lays, 0, path, 0, lays.length); path[path.length - 1] = new TocTreeNodeStyle(s); selectedPaths[i + layers.length] = new TreePath(path); } fireSelectionEvent.set(false); try { tree.setSelectionPaths(selectedPaths); } finally { fireSelectionEvent.set(true); } } /** * Create the Toc JTree * * @return the Toc JTree */ private JTree makeTree() { tree = new JTree(); //Items can be selected freely tree.getSelectionModel().setSelectionMode( TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); TocTransferHandler handler = new TocTransferHandler(this); //Add a drop listener handler.getTransferEditableEvent().addListener(this, EventHandler.create(EditableTransferListener.class, this, "onDropEditableElement", "")); tree.setDragEnabled(true); tree.setTransferHandler(handler); tree.setRootVisible(false); tree.setShowsRootHandles(true); tree.setEditable(true); treeRenderer = new TocRenderer(tree); tree.setCellRenderer(treeRenderer); setEmptyLayerModel(tree); tree.setCellEditor(new TocTreeEditor()); tree.addMouseListener(new PopupMouselistener()); //Add a tree selection listener tree.getSelectionModel().addTreeSelectionListener( EventHandler.create(TreeSelectionListener.class, this, "onTreeSelectionChange")); return tree; } /** * The user drop one or multiple EditableElement into the Toc Tree * * @param editTransfer * @throws IllegalArgumentException If drop location is not null,Style * or ILayer */ public void onDropEditableElement(EditableTransferEvent editTransfer) { JTree.DropLocation dropLocation = (JTree.DropLocation) editTransfer.getDropLocation(); EditableElement[] editableList = editTransfer.getEditableList(); //Evaluate Drop Location ILayer dropNode; if (dropLocation.getPath() != null) { Object node = dropLocation.getPath().getLastPathComponent(); if (node instanceof TocTreeNodeStyle) { dropNode = ((TocTreeNodeStyle) node).getStyle().getLayer(); } else if (node instanceof TocTreeNodeLayer) { dropNode = ((TocTreeNodeLayer) node).getLayer(); } else { throw new IllegalArgumentException("Drop node is not an instance of Style or ILayer"); } } else { // By default drop on rootNode dropNode = mapContext.getLayerModel(); } int index; if (!dropNode.acceptsChilds()) { ILayer parent = dropNode.getParent(); if (parent.acceptsChilds()) { index = parent.getIndex(dropNode); dropNode = parent; } else { LOGGER.error(I18N.tr("Cannot drop layer on {0}", dropNode.getName())); //$NON-NLS-1$ return; } } else { index = dropNode.getLayerCount(); } //Drop content to drop location List<EditableSource> sourceToDrop = new ArrayList<EditableSource>(); List<ILayer> newSelectedLayer = new ArrayList<ILayer>(editableList.length); for (EditableElement editableElement : editableList) { if (editableElement instanceof EditableSource) { //From the GeoCatalog sourceToDrop.add((EditableSource) editableElement); // Clear cached icon treeRenderer.clearTableIconCache(((EditableSource) editableElement).getTableReference()); } else if (editableElement instanceof EditableLayer) { //From the TOC (move) ILayer layer = ((EditableLayer) editableElement).getLayer(); try { layer.moveTo(dropNode, index); newSelectedLayer.add(layer); } catch (LayerException ex) { LOGGER.error(I18N.tr("Cannot drop layer on {0}", dropNode.getName()), ex); } } else { LOGGER.error(I18N.tr("Drop unknown editable of type : {0}", editableElement.getTypeId())); } } //Select moved layers if (!newSelectedLayer.isEmpty()) { mapContext.setSelectedLayers(newSelectedLayer.toArray(new ILayer[newSelectedLayer.size()])); } if (!sourceToDrop.isEmpty()) { execute(new DropDataSourceListProcess(dropNode, index, sourceToDrop)); } } private void execute(SwingWorker swingWorker) { if(executorService != null) { executorService.execute(swingWorker); } else { swingWorker.execute(); } } /** * @return A list of selected layers, same as {@link org.orbisgis.coremap.layerModel.MapContext#getSelectedLayers()} */ public List<ILayer> getSelectedLayers() { return Arrays.asList(mapContext.getSelectedLayers()); } /** * Copy the selection of the JTree into the selection in the layer * model. This method do nothing if the fireSelectionEvent is false */ public void onTreeSelectionChange() { if (fireSelectionEvent.getAndSet(false)) { try { //Update the mapcontext selection model //There are selection constraint, then //update also the jtree selection model List<Style> styles = new ArrayList<>(); List<ILayer> layers = new ArrayList<>(); List<TreePath> keptSelection = new ArrayList<>(); TreePath[] paths = tree.getSelectionPaths(); if(paths!=null) { for(TreePath path : paths) { TreeNode treeNode = (TreeNode) path.getLastPathComponent(); if(treeNode instanceof TocTreeNodeLayer) { //Do not mix the selection if(styles.isEmpty()) { keptSelection.add(path); layers.add(((TocTreeNodeLayer)treeNode).getLayer()); } } else if(treeNode instanceof TocTreeNodeStyle) { //Do not mix the selection if(layers.isEmpty()) { keptSelection.add(path); styles.add(((TocTreeNodeStyle)treeNode).getStyle()); } } } } //Update the two selection model tree.getSelectionModel().setSelectionPaths(keptSelection.toArray(new TreePath[keptSelection.size()])); mapContext.setSelectedLayers(layers.toArray(new ILayer[layers.size()])); mapContext.setSelectedStyles(styles.toArray(new Style[styles.size()])); } finally { fireSelectionEvent.set(true); } } } private void setEmptyLayerModel(JTree jTree) { //Add the treeModel jTree.setModel(new DefaultTreeModel(new TocTreeNodeLayer(new LayerCollection("empty")))); } @Override public DockingPanelParameters getDockingParameters() { return dockingPanelParameters; } public MapContext getMapContext() { return mapContext; } /** * Recursively add property listeners to the provided node * @param node */ private void addPropertyListeners(TreeNode node) { if(node instanceof TocTreeNodeLayer) { ILayer layer =((TocTreeNodeLayer) node).getLayer(); if(!layer.acceptsChilds()) { layer.addPropertyChangeListener(Layer.PROP_STYLES,tocStyleListListener); try(Connection connection = mapContext.getDataManager().getDataSource().getConnection()) { if (!layer.getTableReference().isEmpty() && JDBCUtilities.tableExists(connection,layer.getTableReference())) { mapContext.getDataManager().addTableEditListener(layer.getTableReference(), this); } } catch (SQLException ex) { // Ignore } for(Style st : layer.getStyles()) { addPropertyListeners(new TocTreeNodeStyle(st)); } } else { for(ILayer subLayer : layer.getChildren()) { addPropertyListeners(new TocTreeNodeLayer(subLayer)); } } } else if(node instanceof TocTreeNodeStyle) { Style st = ((TocTreeNodeStyle) node).getStyle(); st.addPropertyChangeListener(tocStyleListener); } } /** * One of layer change * @param event Event object, source is table identifier. */ @Override public void tableChange(TableEditEvent event) { // Found which layer(s) change for(ILayer layer : mapContext.getLayers()) { if(!layer.getTableReference().isEmpty()) { if(MetaData.isTableIdentifierEquals(event.getTableName(), layer.getTableReference())) { treeRenderer.clearTableIconCache(layer.getTableReference()); SwingUtilities.invokeLater(new NodeChangeEvent(treeModel, new TocTreeNodeLayer(layer))); } } } } /** * Recursively remove the property listeners of the provided node * @param node */ private void removePropertyListeners(TreeNode node) { if(node instanceof TocTreeNodeLayer) { ILayer layer =((TocTreeNodeLayer) node).getLayer(); if(!layer.acceptsChilds()) { layer.removePropertyChangeListener(tocStyleListListener); if(!layer.getTableReference().isEmpty()) { mapContext.getDataManager().removeTableEditListener(layer.getTableReference(), this); } for(Style st : layer.getStyles()) { removePropertyListeners(new TocTreeNodeStyle(st)); } } } else if(node instanceof TocTreeNodeStyle) { Style st = ((TocTreeNodeStyle) node).getStyle(); st.removePropertyChangeListener(tocStyleListener); } } /** * Load the specified MapElement in the toc * * @param newMapElement Map element to load into toc tree. */ public void setEditableMap(MapElement newMapElement) { if (newMapElement != null) { MapContext importedMap = ((MapContext) newMapElement.getObject()); if (!importedMap.isOpen()) { try { importedMap.open(null); } catch (LayerException | IllegalStateException ex) { throw new IllegalArgumentException(ex); } } } // Remove the listeners if (this.mapContext != null) { removePropertyListeners(new TocTreeNodeLayer(this.mapContext.getLayerModel())); this.mapContext.getLayerModel().removeLayerListenerRecursively(tocLayerListener); this.mapContext.removeMapContextListener(tocMapContextListener); this.mapContext.removePropertyChangeListener(mapContextPropertyChange); mapElement.removePropertyChangeListener(modificationListener); } if (newMapElement != null) { this.mapContext = ((MapContext) newMapElement.getObject()); mapContext.addPropertyChangeListener(mapContextPropertyChange); treeRenderer.setMapContext(mapContext); this.mapElement = newMapElement; // Add the listeners to the new MapContext this.mapContext.addMapContextListener(tocMapContextListener); final ILayer root = this.mapContext.getLayerModel(); addPropertyListeners(new TocTreeNodeLayer(root)); mapElement.addPropertyChangeListener(MapElement.PROP_MODIFIED, modificationListener); root.addLayerListenerRecursively(tocLayerListener); // Apply treeModel and clear the selection fireSelectionEvent.set(false); try { treeModel = new DefaultTreeModel(new TocTreeNodeLayer(root)); tree.setModel(treeModel); tree.getSelectionModel().clearSelection(); } finally { fireSelectionEvent.set(true); } setTocSelection(mapContext); //TODO ? repaint } } /** * Get the path for a node * @param node * @return TreePath to use with this JTree */ private TreePath getPathFromNode(TreeNode node) { List<Object> nodeHierarchy = new LinkedList<Object>(); nodeHierarchy.add(node); TreeNode parent = node.getParent(); while(parent!=null) { nodeHierarchy.add(parent); parent = parent.getParent(); } Collections.reverse(nodeHierarchy); return new TreePath(nodeHierarchy.toArray(new Object[nodeHierarchy.size()])); } /** * A property of the MapContext has been updated * @param evt Event information */ public void onMapContextPropertyChange(PropertyChangeEvent evt) { if(MapContext.PROP_ACTIVELAYER.equals(evt.getPropertyName())) { if(evt.getOldValue() instanceof ILayer) { treeModel.nodeChanged(new TocTreeNodeLayer((ILayer)evt.getOldValue())); } if(evt.getNewValue() instanceof ILayer) { treeModel.nodeChanged(new TocTreeNodeLayer((ILayer)evt.getNewValue())); } } } @Override public JComponent getComponent() { return this; } @Override public JTree getTree() { return tree; } /** * @return Action manager of tree popup. */ public ActionCommands getPopupActions() { return popupActions; } /** * Action triggered by the Add group button in the menu of the TOC. */ public void onAddGroup(){ LayerCollection lc = new LayerCollection("group"+System.currentTimeMillis()); try { if (tree.getSelectionCount() == 1){ Object selected = tree.getLastSelectedPathComponent(); if(selected instanceof TocTreeNodeLayer){ ILayer l = ((TocTreeNodeLayer) selected).getLayer(); if(l instanceof LayerCollection){ l.addLayer(lc); } else { LayerCollection parent = (LayerCollection) l.getParent(); parent.addLayer(lc); } } } else { LayerCollection root = (LayerCollection) ((TocTreeNodeLayer)tree.getModel().getRoot()).getLayer(); root.addLayer(lc); } } catch (LayerException l){ LOGGER.error(I18N.tr("Can not add a LayerCollection : {0}", l.getMessage())); } } /** * The user click on delete layer menu item. */ public void onDeleteLayer() { ILayer[] selectedResources = mapContext.getSelectedLayers(); // Remove layers for (ILayer resource : selectedResources) { try { resource.getParent().remove(resource); } catch (LayerException e) { LOGGER.error(I18N.tr("Cannot delete layer"), e); } } } /** * The user click on zoomto layer menu item. */ public void zoomToLayer() { ILayer[] selectedResources = mapContext.getSelectedLayers(); Envelope env=null; for (ILayer resource : selectedResources) { if(env==null) { env = resource.getEnvelope(); } else { env.expandToInclude(resource.getEnvelope()); } } if(env!=null) { mapContext.setBoundingBox(env); } } /** * The user click on the Zoom To Layer selection menu */ public void zoomToLayerSelection() { ILayer[] selectedLayers = mapContext.getSelectedLayers(); execute(new ZoomToSelection(mapContext, selectedLayers)); } /** * The user click on the clear layer selection menu */ public void clearLayerRowSelection() { ILayer[] selectedLayers = mapContext.getSelectedLayers(); for(ILayer layer : selectedLayers) { if(!layer.acceptsChilds()) { layer.setSelection(new HashSet<Long>()); } } } /** * The user choose to delete a style through the dedicated menu. */ public void onDeleteStyle() { Style[] styles = mapContext.getSelectedStyles(); for (Style s : styles) { ILayer l = s.getLayer(); l.removeStyle(s); } } /** * The user choose to export a style through the dedicated menu. */ public void onExportStyle() { Style[] styles = mapContext.getSelectedStyles(); if(styles.length == 1){ Style style = styles[0]; final SaveFilePanel outputXMLPanel = new SaveFilePanel( "org.orbisgis.coremap.ui.editorViews.toc.actions.ImportStyle", "Choose a location"); outputXMLPanel.addFilter("se", "Symbology Encoding FeatureTypeStyle"); outputXMLPanel.loadState(); if (UIFactory.showDialog(outputXMLPanel)) { String seFile = outputXMLPanel.getSelectedFile().getAbsolutePath(); style.export(seFile); } } } /** * Called by EventHandler. This methods opens a wizard for the configuration * of a legend that will be added to the selected style in a dedicated Rule. */ public void onAddLegend(){ Style[] styles = mapContext.getSelectedStyles(); if(styles.length == 1){ Style base = styles[0]; BeanLayer l = (BeanLayer) base.getLayer(); LegendWizard lw = new LegendWizard(); SIFWizard wizard = lw.getSIFWizard(l, mapEditorExtension.getMapTransform()); //Show a wizard to add new thematic map in a Layer if(UIFactory.showWizard(wizard)){ Symbolizer sym = lw.getSymbolizer(); Rule r = new Rule(l); CompositeSymbolizer cs = r.getCompositeSymbolizer(); Symbolizer def = cs.getSymbolizerList().get(0); cs.removeSymbolizer(def); cs.addSymbolizer(sym); base.addRule(r); l.onStyleChanged(new PropertyChangeEvent(base, ILayer.PROP_STYLES, base, base)); } } } /** * Add a new default style to the selected layer. */ public void onAddStyle() { ILayer[] layers = mapContext.getSelectedLayers(); if (layers.length == 1) { LegendWizard lw = new LegendWizard(); ILayer layer = layers[0]; if (isStyleAllowed(layer)) { SIFWizard wizard = lw.getSIFWizard(layer, mapEditorExtension.getMapTransform()); if(UIFactory.showWizard(wizard)){ Style s = lw.getStyle(); layer.addStyle(s); } } else { LOGGER.info(I18N.tr("This functionality is not supported.")); } } } /** * The user choose to import a style and to add it to the selected layer * through the dedicated menu. */ public void onImportStyle() { ILayer[] layers = mapContext.getSelectedLayers(); if (layers.length == 1) { ILayer layer = layers[0]; if (isStyleAllowed(layer)){ final OpenFilePanel inputXMLPanel = new OpenFilePanel( "org.orbisgis.coremap.ui.editorViews.toc.actions.ImportStyle", "Choose a location"); inputXMLPanel.addFilter("se", "Symbology Encoding 2.0 (FeatureTypeStyle"); inputXMLPanel.loadState(); if (UIFactory.showDialog(inputXMLPanel)) { String seFile = inputXMLPanel.getSelectedFile().getAbsolutePath(); try { layer.addStyle(new Style(layer, seFile)); } catch (SeExceptions.InvalidStyle ex) { LOGGER.error(ex.getLocalizedMessage()); String msg = ex.getMessage().replace("<", "\n - ").replace(',', ' ').replace(": ", "\n - "); JOptionPane.showMessageDialog(null, msg, "Error while loading the style", JOptionPane.ERROR_MESSAGE); } }} else{ LOGGER.info("This functionality is not supported."); } } } /** * Opens the simple editor, if we have only known configurations. */ public void onSimpleEditor() { TreePath selObjs = tree.getSelectionPath(); if (selObjs.getLastPathComponent() instanceof TocTreeNodeStyle) { try { Style style = ((TocTreeNodeStyle) selObjs.getLastPathComponent()).getStyle(); final Layer layer = (Layer) style.getLayer(); if(isStyleAllowed(layer)){ final int index = layer.indexOf(style); //In order to be able to cancel all of our modifications, //we produce a copy of our style. JAXBElement<StyleType> jest = style.getJAXBElement(); int geometryType; TableLocation tableLocation = TableLocation.parse(layer.getTableReference()); try(Connection connection = mapContext.getDataManager().getDataSource().getConnection()) { geometryType = SFSUtilities.getGeometryType(connection, tableLocation, ""); } Style copy = new Style(jest, layer); final SimpleStyleEditor pan = new SimpleStyleEditor(mapEditorExtension.getMapTransform(), geometryType, layer, copy); ActionListener apply = new ActionListener() { @Override public void actionPerformed(ActionEvent actionEvent) { Style s1 = pan.getStyleWrapper().getStyle(); JAXBElement<StyleType> jaxbElement = s1.getJAXBElement(); try { Style s2 = new Style(jaxbElement, layer); layer.setStyle(index, s2); } catch (SeExceptions.InvalidStyle invalidStyle) { LOGGER.error(I18N.tr("You produced an invalid style while copying " + "a valid one. Things are getting really wrong here.")); } } }; if (UIFactory.showApplyDialog(pan, apply, false)) { layer.setStyle(index, pan.getStyleWrapper().getStyle()); } }else{ LOGGER.info(I18N.tr("Styles can be set only on vector layers.")); } } catch (SeExceptions.InvalidStyle sis) { //I don't know how this could happen : we are creating a style //from a valid style. Should be valid too, consequently... LOGGER.error(I18N.tr("The style you're trying to edit is not valid !")); } catch (SQLException de) { LOGGER.error(I18N.tr("An error occurred while processing the DataSource")); } catch (UnsupportedOperationException uoe){ LOGGER_POPUP.info(I18N.tr("Cannot create the user interface for this style. \n" + "Please uses the advanced style editor."), uoe); } } } /** * The user click on the button and open the wms panel. */ public void onAddWMSLayer() { SRSPanel srsPanel = new SRSPanel(); LayerConfigurationPanel layerConfiguration = new LayerConfigurationPanel(srsPanel); WMSConnectionPanel wmsConnection = new WMSConnectionPanel(layerConfiguration, executorService, coreWorkspace); SIFWizard wizard = UIFactory.getWizard(new UIPanel[]{wmsConnection, layerConfiguration, srsPanel}); wizard.setTitle(wmsConnection.getTitle()); wizard.setResizable(false); if (UIFactory.showWizard(wizard)) { WMService service = wmsConnection.getServiceDescription(); Capabilities cap = service.getCapabilities(); MapImageFormatChooser mfc = new MapImageFormatChooser(service.getVersion()); mfc.setTransparencyRequired(true); String validImageFormat = mfc.chooseFormat(cap.getMapFormats()); if (validImageFormat == null) { LOGGER.error(I18N.tr("Cannot find a valid image format for this WMS server")); } else { Object[] layers = layerConfiguration.getSelectedLayers(); execute(new AddWMSLayers(service.getServerUrl(), service.getVersion(), layers, validImageFormat, srsPanel.getSRS())); } } } /** * Add the selected WMS layers to the mapcontext. * */ private class AddWMSLayers extends SwingWorker { private final Object[] layers; private final String validImageFormat; private final String WMSVersion; private final String srsIdentifier; private final String serverURL; /** * Create the background job that needs. * @param serverURL the URL of the service. * @param WMSVersion the version of the service. * @param layers the list of layers. * @param validImageFormat a valid image format. * @param srsIdentifier a valid SRS identifier. */ public AddWMSLayers(String serverURL, String WMSVersion, Object[] layers, String validImageFormat, String srsIdentifier ){ this.serverURL = serverURL; this.layers=layers; this.validImageFormat=validImageFormat; this.WMSVersion=WMSVersion; this.srsIdentifier=srsIdentifier; } @Override protected Object doInBackground() throws Exception { for (Object layer : layers) { if (isCancelled()) { break; } else { String layerName = ((MapLayer) layer).getName(); URI origin = URI.create(serverURL); StringBuilder url = new StringBuilder(origin.getQuery()); url.append("SERVICE=WMS&REQUEST=GetMap"); url.append("&VERSION=").append(WMSVersion); if (WMService.WMS_1_3_0.equals(WMSVersion)) { url.append("&CRS="); } else { url.append("&SRS="); } url.append(srsIdentifier); url.append("&LAYERS=").append(layerName); url.append("&FORMAT=").append(validImageFormat); try { URI streamUri = new URI(origin.getScheme(), origin.getUserInfo(), origin.getHost(), origin.getPort(), origin.getPath(), url.toString(), origin.getFragment()); ILayer wmsLayer = mapContext.createLayer(layerName, streamUri); mapContext.getLayerModel().addLayer(wmsLayer); } catch (URISyntaxException use) { LOGGER.error(I18N.tr("The given URI contains illegal character"), use); } catch (LayerException ex) { LOGGER.error(I18N.tr("Cannot add the WMS layer" + " " + layerName)); } } } return null; } } @Override public boolean match(EditableElement editableElement) { return editableElement instanceof MapElement; } @Override public EditableElement getEditableElement() { return mapElement; } /** * Search in the layer the provided data source name * @param layer * @param sourceName */ private boolean hasDataSource(ILayer layer,String sourceName) { String table = layer.getTableReference(); if(table!=null && !table.isEmpty()) { return table.equalsIgnoreCase(sourceName); } if(layer.acceptsChilds()) { for(ILayer subLayer : layer.getChildren()) { if(hasDataSource(subLayer, sourceName)) { return true; } } } return false; } @Override public void setEditableElement(EditableElement editableElement) { if (editableElement instanceof MapElement) { MapElement importedMap = (MapElement) editableElement; setEditableMap(importedMap); } } public void onStyleChange(PropertyChangeEvent evt) { treeModel.nodeChanged(new TocTreeNodeStyle((Style)evt.getSource())); } public void onStyleListChange(PropertyChangeEvent evt) { if(evt.getNewValue() instanceof List) { if(evt.getOldValue()!=null) { for(Style style : (List<Style>)evt.getOldValue()) { style.removePropertyChangeListener(tocStyleListener); } } for(Style style : (List<Style>)evt.getNewValue()) { style.removePropertyChangeListener(tocStyleListener); style.addPropertyChangeListener(tocStyleListener); } } else if(evt.getNewValue() instanceof Style){ Style newStyle = (Style)evt.getNewValue(); newStyle.removePropertyChangeListener(tocStyleListener); newStyle.addPropertyChangeListener(tocStyleListener); } treeModel.nodeStructureChanged(new TocTreeNodeLayer((ILayer) evt.getSource())); } /** * Return true is the layer is vectorial because of only SE geometry style * is currently supported */ private boolean isStyleAllowed(ILayer layer) { return layer.getTableReference() != null && !layer.getTableReference().isEmpty(); } private class TocLayerListener implements LayerListener { @Override public void layerAdded(final LayerCollectionEvent e) { for (final ILayer layer : e.getAffected()) { layer.addLayerListenerRecursively(this); } TreeNode parentNode = new TocTreeNodeLayer(e.getParent()); addPropertyListeners(parentNode); treeModel.nodeStructureChanged(parentNode); } @Override public void layerMoved(LayerCollectionEvent e) { treeModel.nodeStructureChanged(new TocTreeNodeLayer(e.getParent())); } @Override public boolean layerRemoving(LayerCollectionEvent e) { // Close editors for (final ILayer layer : e.getAffected()) { layer.removePropertyChangeListener(tocStyleListListener); layer.removeLayerListener(this); } return true; } @Override public void layerRemoved(final LayerCollectionEvent e) { for (final ILayer layer : e.getAffected()) { layer.removeLayerListenerRecursively(this); } treeModel.nodeStructureChanged(new TocTreeNodeLayer(e.getParent())); } @Override public void nameChanged(LayerListenerEvent e) { onLayerChanged(e.getAffectedLayer()); } private void onLayerChanged(ILayer layer) { treeModel.nodeChanged(new TocTreeNodeLayer(layer)); } @Override public void styleChanged(LayerListenerEvent e) { //Deprecated listener } @Override @SuppressWarnings("deprecation") public void visibilityChanged(LayerListenerEvent e) { onLayerChanged(e.getAffectedLayer()); } @Override public void selectionChanged(SelectionEvent e) { } } private final class TocMapContextListener implements MapContextListener { @Override public void layerSelectionChanged(MapContext mapContext) { setTocSelection(mapContext); } } /** * The user drop a list of EditableSource in the TOC tree */ private class DropDataSourceListProcess extends SwingWorkerPM { private ILayer dropNode; private int dropIndex; private List<EditableSource> draggedResources; public DropDataSourceListProcess(ILayer dropNode, int dropIndex, List<EditableSource> draggedResources) { super(I18N.tr("Load the data source droped into the toc."), 1); this.dropNode = dropNode; this.dropIndex = dropIndex; this.draggedResources = draggedResources; } @Override protected Object doInBackground() throws Exception { List<TreePath> dropPaths = new ArrayList<TreePath>(draggedResources.size()); ProgressMonitor pmSources = getProgressMonitor().startTask(draggedResources.size()); for (int i = 0; i < draggedResources.size(); i++) { String sourceName = draggedResources.get(i).getId(); if (isCancelled()) { break; } else { try { ILayer nl = mapContext.createLayer(sourceName); dropNode.insertLayer(nl, dropIndex); dropPaths.add(getPathFromNode(new TocTreeNodeLayer(nl))); } catch (Exception e) { throw new RuntimeException(I18N.tr("Cannot add the layer to the destination"), e); } } pmSources.endTask(); } SwingUtilities.invokeAndWait(new NodeChangeEvent(treeModel, new TocTreeNodeLayer(dropNode))); // Select the new layer(s) if there is no selection if(tree.getSelectionCount()==0) { tree.setSelectionPaths(dropPaths.toArray(new TreePath[dropPaths.size()])); } return null; } } /** * Implements Popup action on JTree, * and checkbox activation on single click (override Look&Feel) * */ private class PopupMouselistener extends MouseAdapter { /** * Show/Hide Layer if the checkbox is selected * override Look&Feel behaviour * @param e */ @Override public void mouseClicked(MouseEvent e) { final int x = e.getX(); final int y = e.getY(); final int mouseButton = e.getButton(); TreePath path = tree.getPathForLocation(x, y); Rectangle layerNodeLocation = tree.getPathBounds(path); if (path != null && layerNodeLocation != null) { Rectangle checkBoxBounds = treeRenderer.getCheckBoxBounds(); checkBoxBounds.translate( (int) layerNodeLocation.getX(), (int) layerNodeLocation.getY()); if ((checkBoxBounds.contains(e.getPoint())) && (MouseEvent.BUTTON1 == mouseButton) && (1 == e.getClickCount())) { // mouse click inside checkbox if (path.getLastPathComponent() instanceof TocTreeNodeLayer) { ILayer layer = ((TocTreeNodeLayer) path.getLastPathComponent()).getLayer(); try { layer.setVisible(!layer.isVisible()); } catch (LayerException e1) { LOGGER.error(e1.getLocalizedMessage(), e1); } } else if(path.getLastPathComponent() instanceof TocTreeNodeStyle) { Style style = ((TocTreeNodeStyle) path.getLastPathComponent()).getStyle(); style.setVisible(!style.isVisible()); } } } } @Override public void mousePressed(MouseEvent e) { maybeShowPopup(e); } @Override public void mouseReleased(MouseEvent e) { maybeShowPopup(e); } private void maybeShowPopup(MouseEvent e) { if (e.isPopupTrigger()) { //Update selection TreePath path = tree.getPathForLocation(e.getX(), e.getY()); TreePath[] selectionPaths = tree.getSelectionPaths(); if (selectionPaths != null && path != null){ if (!AbstractTreeModel.contains(selectionPaths, path)) { if (e.isControlDown()) { tree.addSelectionPath(path); } else { tree.setSelectionPath(path); } } } else { tree.setSelectionPath(path); } //Show popup JPopupMenu menu = new JPopupMenu(); popupActions.copyEnabledActions(menu); menu.show(e.getComponent(), e.getX(), e.getY()); } } } private static class NodeChangeEvent implements Runnable { private final DefaultTreeModel treeModel; private final MutableTreeNode nodeChanged; public NodeChangeEvent(DefaultTreeModel treeModel, MutableTreeNode nodeChanged) { this.treeModel = treeModel; this.nodeChanged = nodeChanged; } @Override public void run() { treeModel.nodeChanged(nodeChanged); } } }