/**
* 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.actions.cui;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.EventHandler;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JToolBar;
import javax.swing.JTree;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import org.orbisgis.coremap.renderer.se.Rule;
import org.orbisgis.coremap.renderer.se.Symbolizer;
import org.orbisgis.legend.Legend;
import org.orbisgis.legend.thematic.factory.LegendFactory;
import org.orbisgis.sif.UIFactory;
import org.orbisgis.sif.components.renderers.TreeLaFRenderer;
import org.orbisgis.sif.multiInputPanel.MIPValidation;
import org.orbisgis.sif.multiInputPanel.MultiInputPanel;
import org.orbisgis.sif.multiInputPanel.TextBoxType;
import org.orbisgis.view.toc.actions.cui.legend.ILegendPanel;
import org.orbisgis.view.toc.actions.cui.legend.ILegendPanelFactory;
import org.orbisgis.view.toc.actions.cui.legend.ISELegendPanel;
import org.orbisgis.view.toc.actions.cui.legend.LegendTreeModel;
import org.orbisgis.view.toc.icons.TocIcon;
import org.orbisgis.view.toc.wrapper.RuleWrapper;
import org.orbisgis.view.toc.wrapper.StyleWrapper;
import org.xnap.commons.i18n.I18n;
import org.xnap.commons.i18n.I18nFactory;
/**
* A panel embedding a JTree representing the legend structure (as well as some
* buttons to manage it).
*
* @author Alexis Guéganno
* @author Adam Gouge
*/
public class LegendTree extends JPanel {
private static final I18n I18N = I18nFactory.getI18n(LegendTree.class);
private JTree tree;
private SimpleStyleEditor simpleStyleEditor;
private JToolBar toolBar;
private JButton jButtonMenuDel;
private JButton jButtonMenuDown;
private JButton jButtonMenuRename;
private JButton jButtonMenuUp;
public LegendTree(final SimpleStyleEditor simpleEditor) {
simpleStyleEditor = simpleEditor;
StyleWrapper style = simpleStyleEditor.getStyleWrapper();
//We create our tree
tree = new JTree();
//We don't want to display the root.
tree.setRootVisible(true);
//We have a custom model to provide... Listeners on the TreeModel
//are added by the tree when calling setModel.
LegendTreeModel ltm = new LegendTreeModel(tree, style);
tree.setModel(ltm);
//..A custom cell editor...
LegendTreeCellEditor editor = new LegendTreeCellEditor();
editor.setClickCountToStart(2);
tree.setCellEditor(editor);
//...and a custom TreeCellRenderer.
LegendCellRenderer lcr = new LegendCellRenderer(tree);
tree.setCellRenderer(lcr);
//We want to select only one element at a time.
tree.getSelectionModel().setSelectionMode(
TreeSelectionModel.SINGLE_TREE_SELECTION);
selectAndShowFirstLegend(style);
//We refresh icons when the selection changes.
TreeSelectionListener tsl = EventHandler.create(
TreeSelectionListener.class, this, "refreshIcons");
tree.addTreeSelectionListener(tsl);
//We refresh the CardLayout of the associated SimpleStyleEditor
TreeSelectionListener select = EventHandler.create(
TreeSelectionListener.class, simpleStyleEditor, "legendSelected");
tree.addTreeSelectionListener(select);
expandAll(tree);
//We want an editable tree
tree.setEditable(true);
initButtons();
this.setLayout(new BorderLayout());
this.add(toolBar, BorderLayout.PAGE_START);
JScrollPane scrollPane = new JScrollPane(tree);
this.add(scrollPane, BorderLayout.CENTER);
refreshIcons();
}
/**
* Removes the currently selected element from the tree. If it is an inner
* node, all its children will be lost.
*/
public void removeSelectedElement() {
TreePath tp = tree.getSelectionPath();
Object select = tp.getLastPathComponent();
LegendTreeModel tm = (LegendTreeModel) tree.getModel();
if (select instanceof ILegendPanel) {
RuleWrapper rw = (RuleWrapper) tp.getPath()[tp.getPath().length - 2];
tree.setSelectionPath(null);
tm.removeElement(rw, select);
//We refresh the legend container
simpleStyleEditor.showDialogForCurrentlySelectedLegend();
//We refresh the icons
} else if (select instanceof RuleWrapper) {
tree.setSelectionPath(null);
tm.removeElement(tm.getRoot(), select);
simpleStyleEditor.showDialogForCurrentlySelectedLegend();
}
refreshIcons();
}
/**
* Adds an element to the tree. It will open a window to let the user choose
* which type of element (legend or rule) it must be.
*/
public void addElement() {
TreePath tp = tree.getSelectionPath();
if (tp == null) {
addRule();
} else {
Object select = tp.getLastPathComponent();
if (select instanceof StyleWrapper) {
addRule();
} else {
addLegend();
}
}
refreshIcons();
}
/**
* Move the currently selected element (if any) up in the model.
*/
public void moveSelectedElementUp() {
TreePath tp = tree.getSelectionPath();
Object select = tp.getLastPathComponent();
if (select instanceof RuleWrapper) {
LegendTreeModel tm = (LegendTreeModel) tree.getModel();
tm.moveElementUp(tm.getRoot(), select);
refreshIcons();
} else if (select instanceof ILegendPanel) {
RuleWrapper rw = (RuleWrapper) tp.getPath()[tp.getPath().length - 2];
LegendTreeModel tm = (LegendTreeModel) tree.getModel();
tm.moveElementUp(rw, select);
refreshIcons();
}
}
/**
* Move the currently selected element (if any) down in its structure.
*/
public void moveSelectedElementDown() {
TreePath tp = tree.getSelectionPath();
Object select = tp.getLastPathComponent();
if (select instanceof RuleWrapper) {
LegendTreeModel tm = (LegendTreeModel) tree.getModel();
tm.moveElementDown(tm.getRoot(), select);
refreshIcons();
} else if (select instanceof ILegendPanel) {
RuleWrapper rw = (RuleWrapper) tp.getPath()[tp.getPath().length - 2];
LegendTreeModel tm = (LegendTreeModel) tree.getModel();
tm.moveElementDown(rw, select);
refreshIcons();
}
}
/**
* Start editing the name of the currently selected element.
*
* @param evt The initial event.
*/
public final void renameElement(ActionEvent evt) {
TreePath tp = tree.getSelectionPath();
tree.startEditingAtPath(tp);
}
/**
* Get the currently selected Legend, if any. If we find it, we return it.
* Otherwise, we return null.
*
* @return The currently selected panel, or null if none is selected.
*/
public ILegendPanel getSelectedLegend() {
TreePath tp = tree.getSelectionPath();
if (tp != null) {
Object last = tp.getLastPathComponent();
if (last instanceof ILegendPanel) {
return (ILegendPanel) last;
}
}
return null;
}
/**
* Gets the {@code ISELegendPanel} that is associated to the currently
* selected element in the tree.
*
* @return The currently selected panel, or null if none is selected.
*/
public ISELegendPanel getSelectedPanel() {
TreePath tp = tree.getSelectionPath();
if (tp != null) {
Object last = tp.getLastPathComponent();
if (last instanceof ILegendPanel) {
return (ILegendPanel) last;
} else if (last instanceof RuleWrapper) {
return ((RuleWrapper) last).getPanel();
} else if (last instanceof StyleWrapper) {
return ((StyleWrapper) last).getPanel();
}
}
return null;
}
/**
* Refreshes the state of the icons contained in the toolbar of this panel.
*/
public final void refreshIcons() {
//We must retrieve the index of the currently selected item in its
//parent to decide if we display the buttons or not.
TreePath tp = tree.getSelectionPath();
if (tp == null) {
jButtonMenuDel.setEnabled(false);
jButtonMenuRename.setEnabled(false);
jButtonMenuDown.setEnabled(false);
jButtonMenuUp.setEnabled(false);
} else {
jButtonMenuRename.setEnabled(true);
Object last = tp.getLastPathComponent();
int index = -1;
int max = -1;
if (last instanceof StyleWrapper) {
max = 0;
index = 0;
} else if (last instanceof RuleWrapper) {
StyleWrapper sw = simpleStyleEditor.getStyleWrapper();
index = sw.indexOf((RuleWrapper) last);
max = sw.getSize() - 1;
} else if (last instanceof ILegendPanel) {
RuleWrapper rw = getSelectedRule();
index = rw.indexOf((ILegendPanel) last);
max = rw.getSize() - 1;
}
if (index == 0) {
jButtonMenuUp.setEnabled(false);
} else {
jButtonMenuUp.setEnabled(true);
}
if (index < max) {
jButtonMenuDown.setEnabled(true);
} else {
jButtonMenuDown.setEnabled(false);
}
if (max < 1) {
jButtonMenuDel.setEnabled(false);
} else {
jButtonMenuDel.setEnabled(true);
}
}
}
/**
* Tests if we have a legend in our tree.
*
* @return
*/
public boolean hasLegend() {
LegendTreeModel ltm = (LegendTreeModel) tree.getModel();
return ltm.hasLegend();
}
void refresh() {
refreshIcons();
refreshModel();
}
/**
* Initialize all the buttons that can be used to manage the tree content.
*/
private void initButtons() {
toolBar = new JToolBar();
toolBar.setFloatable(false);
jButtonMenuUp = new JButton();
jButtonMenuUp.setIcon(TocIcon.getIcon("go-up"));
jButtonMenuUp.setToolTipText(I18N.tr("Up"));
ActionListener alu = EventHandler.create(
ActionListener.class, this, "moveSelectedElementUp");
jButtonMenuUp.addActionListener(alu);
toolBar.add(jButtonMenuUp);
jButtonMenuDown = new JButton();
jButtonMenuDown.setIcon(TocIcon.getIcon("go-down"));
jButtonMenuDown.setToolTipText(I18N.tr("Down"));
ActionListener ald = EventHandler.create(
ActionListener.class, this, "moveSelectedElementDown");
jButtonMenuDown.addActionListener(ald);
toolBar.add(jButtonMenuDown);
JButton jButtonMenuAdd = new JButton();
jButtonMenuAdd.setIcon(TocIcon.getIcon("add"));
jButtonMenuAdd.setToolTipText(I18N.tr("Add"));
ActionListener aladd = EventHandler.create(
ActionListener.class, this, "addElement");
jButtonMenuAdd.addActionListener(aladd);
jButtonMenuAdd.setFocusPainted(false);
toolBar.add(jButtonMenuAdd);
jButtonMenuDel = new JButton();
jButtonMenuDel.setIcon(TocIcon.getIcon("delete"));
jButtonMenuDel.setToolTipText(I18N.tr("Delete"));
ActionListener alrem = EventHandler.create(
ActionListener.class, this, "removeSelectedElement");
jButtonMenuDel.addActionListener(alrem);
toolBar.add(jButtonMenuDel);
jButtonMenuRename = new JButton();
jButtonMenuRename.setIcon(TocIcon.getIcon("pencil"));
jButtonMenuRename.setToolTipText(I18N.tr("Rename"));
jButtonMenuRename.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent evt) {
renameElement(evt);
}
});
toolBar.add(jButtonMenuRename);
}
/**
* Add a legend to the tree, in the currently selected RuleWrapper, after
* the currently selected Legend (if any in both case). A RuleWrapper will
* be added in the case there is none.
*/
private void addLegend() {
LegendUIChooser legendPicker = new LegendUIChooser(simpleStyleEditor);
if (UIFactory.showDialog(legendPicker, true, true)) {
// Recover the panel that was selected when the user clicked OK.
ILegendPanel ilp = legendPicker.getSelectedPanel();
// Get the currently selected RuleWrapper, or the last one in this
// style if none is currently selected.
RuleWrapper currentrw = getSelectedRule();
StyleWrapper sw = simpleStyleEditor.getStyleWrapper();
if (currentrw == null) {
if (sw.getSize() == 0) {
addRule();
}
currentrw = sw.getRuleWrapper(sw.getSize() - 1);
}
// Set the Legend's name.
Legend legend = ilp.getLegend();
legend.getSymbolizer().setName(
getUniqueName(legend.getLegendTypeName(),
currentrw.getRule(), 0));
// Add the panel to the LegendTree.
((LegendTreeModel) tree.getModel())
.addElement(currentrw, ilp, getSelectedLegend());
// Automatically select the newly added legend in the tree.
TreePath selectionPath = tree.getSelectionPath();
TreePath parent;
if(selectionPath.getLastPathComponent() instanceof RuleWrapper){
parent = selectionPath;
} else {
parent = selectionPath.getParentPath();
}
tree.setSelectionPath(parent.pathByAddingChild(ilp));
// Notify the SimpleStyleEditor that a Legend has been added.
simpleStyleEditor.legendAdded(ilp);
}
}
/**
* Get a name for a Symbolizer that is not already contained in the rule.
*
* @param n The base name
* @param r the rule where we search
* @param i An int we'll try to had to reach unicity
*
* @return The unique name.
*/
private String getUniqueName(String n, Rule r, int i) {
int a = i < 0 ? 0 : i;
String ret = n;
if (a > 0) {
ret = n + " " + a;
}
boolean contained = false;
List<Symbolizer> symbolizerList = r.getCompositeSymbolizer().
getSymbolizerList();
for (int p = 0; p < symbolizerList.size() && !contained; p++) {
Symbolizer s = symbolizerList.get(p);
if (s.getName().equals(ret)) {
contained = true;
}
}
if (!contained) {
return ret;
} else {
return getUniqueName(n, r, a + 1);
}
}
private void addRule() {
//We must add it just after the currently selected Rule.
//Let's find which one it is. If there is none, we add it at the
//end of the list.
MultiInputPanel mip = new MultiInputPanel(I18N.tr(
"Choose a name for your rule"));
mip.addInput("RuleName", I18N.tr("Name of the Rule : "),
new TextBoxType(10));
mip.addValidation(new MIPValidation() {
@Override
public String validate(MultiInputPanel mid) {
String ruleName = mid.getInput("RuleName");
return ruleName.isEmpty() ? I18N.tr(
"Rule name cannot be null or empty.") : null;
}
});
if (UIFactory.showDialog(mip)) {
String s = mip.getInput("RuleName");
LegendTreeModel tm = (LegendTreeModel) tree.getModel();
//We need to link our new RuleWrapper with the layer we are editing.
Rule temp = new Rule(simpleStyleEditor.getStyleWrapper().getStyle().getLayer());
temp.setName(s);
Legend leg = LegendFactory.getLegend(
temp.getCompositeSymbolizer().getSymbolizerList().get(0));
// Initialize a panel for this legend.
ILegendPanel ilp = ILegendPanelFactory.getILegendPanel(
simpleStyleEditor, leg);
List<ILegendPanel> list = new ArrayList<ILegendPanel>();
list.add(ilp);
RuleWrapper nrw = new RuleWrapper(simpleStyleEditor, temp, list);
tm.addElement(tm.getRoot(), nrw, getSelectedRule());
simpleStyleEditor.legendAdded(nrw.getPanel());
}
}
/**
* Get the currently selected Rule, if any. If we find it, we return it.
* Otherwise, we return null.
*/
private RuleWrapper getSelectedRule() {
TreePath tp = tree.getSelectionPath();
if (tp != null) {
Object[] path = tp.getPath();
for (int i = path.length - 1; i >= 0; i--) {
if (path[i] instanceof RuleWrapper) {
return (RuleWrapper) path[i];
}
}
}
return null;
}
private void refreshModel() {
((LegendTreeModel) tree.getModel()).refresh();
}
/**
* Selects the first legend attached to the given style in this
* {@link LegendTree} and displays it in the Simple Style Editor's card
* layout.
*
* @param style Style
*/
private void selectAndShowFirstLegend(StyleWrapper style) {
RuleWrapper firstRW = style.getRuleWrapper(0);
ILegendPanel firstPanel = firstRW.getLegend(0);
TreePath tp = new TreePath(style)
.pathByAddingChild(firstRW)
.pathByAddingChild(firstPanel);
tree.setSelectionPath(tp);
simpleStyleEditor.showDialogForLegend(firstPanel);
}
/**
* A TreeCellRenderer dedicated to our tree. Paints text in red if the cell
* is selected, in black otherwise.
*/
private static class LegendCellRenderer extends TreeLaFRenderer {
public LegendCellRenderer(JTree tree) {
super(tree);
}
@Override
public Component getTreeCellRendererComponent(
JTree tree, Object value, boolean selected,
boolean expanded, boolean leaf, int row, boolean hasFocus) {
Component comp = lookAndFeelRenderer.getTreeCellRendererComponent(
tree, value, selected, expanded, leaf, row, hasFocus);
if (comp instanceof JLabel) {
JLabel lab = (JLabel) comp;
if (value instanceof StyleWrapper) {
return getComponent((StyleWrapper) value, lab);
} else if (value instanceof RuleWrapper) {
return getComponent((RuleWrapper) value, lab);
} else if (value instanceof ILegendPanel) {
return getComponent((ILegendPanel) value, lab);
} else {
lab.setText("root");
}
}
return comp;
}
private Component getComponent(ILegendPanel legend, JLabel lab) {
lab.setText(legend.getLegend().getName());
lab.setIcon(TocIcon.getIcon("symbol_style"));
return lab;
}
private Component getComponent(RuleWrapper rw, JLabel lab) {
String s = rw.getRule().getName();
if (s == null || s.isEmpty()) {
s = "Unknown";
}
lab.setText(s);
lab.setIcon(TocIcon.getIcon("rule_style"));
return lab;
}
private Component getComponent(StyleWrapper sw, JLabel lab) {
String s = sw.getStyle().getName();
if (s == null || s.isEmpty()) {
s = "Unknown";
}
lab.setText(s);
lab.setIcon(TocIcon.getIcon("palette"));
return lab;
}
}
/**
* The name of the selected element changed !
*/
public void selectedNameChanged() {
LegendTreeModel model = (LegendTreeModel) tree.getModel();
model.refresh();
}
/**
* Expand configuration tree
*
* @param tree The tree we want to process.
*/
private void expandAll(JTree tree) {
for (int row = 0; row < tree.getRowCount(); row++) {
tree.expandRow(row);
}
}
}