/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2007 - 2008, Open Source Geospatial Foundation (OSGeo)
* (C) 2008 - 2010, Johann Sorel
* (C) 2013 - 2014, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library 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
* Lesser General Public License for more details.
*/
package org.geotoolkit.gui.swing.propertyedit.styleproperty;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.EventObject;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextPane;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.PlainView;
import javax.swing.text.Segment;
import javax.swing.text.StyledEditorKit;
import javax.swing.text.Utilities;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;
import javax.swing.tree.TreePath;
import javax.xml.bind.JAXBException;
import org.geotoolkit.gui.swing.propertyedit.PropertyPane;
import org.geotoolkit.gui.swing.resource.IconBundle;
import org.geotoolkit.gui.swing.resource.MessageBundle;
import org.geotoolkit.gui.swing.style.JStyleTree;
import org.geotoolkit.gui.swing.style.StyleElementEditor;
import org.geotoolkit.map.MapLayer;
import org.geotoolkit.sld.xml.Specification;
import org.geotoolkit.sld.xml.StyleXmlIO;
import org.geotoolkit.style.MutableRule;
import org.geotoolkit.style.MutableStyle;
import org.apache.sis.util.logging.Logging;
import org.geotoolkit.map.LayerListener;
import org.geotoolkit.map.MapItem;
import org.geotoolkit.util.collection.CollectionChangeEvent;
import org.opengis.style.Style;
import org.opengis.style.Symbolizer;
import org.opengis.util.FactoryException;
/**
* @author Johann Sorel (Puzzle-GIS)
* @module
*/
public class JAdvancedStylePanel extends StyleElementEditor implements PropertyPane, LayerListener {
private MapLayer layer = null;
private Object style = null;
private StyleElementEditor editor = null;
private TreePath editedPath = null;
//used to dissociate selection and apply
private volatile boolean applying = false;
private final TreeSelectionListener treeListener = new TreeSelectionListener() {
@Override
public void valueChanged(TreeSelectionEvent e) {
final TreePath path = e.getNewLeadSelectionPath();
//we validate the previous edition pane
if(!applying){
//we keep the same editor if we are currently applying changes
applyEditor(e.getOldLeadSelectionPath());
pan_info.removeAll();
if (path != null) {
final Object val = path.getLastPathComponent();
editor = StyleElementEditor.findEditor(val);
editedPath = path;
if(editor != null){
editor.setLayer(getLayer());
editor.parse(val);
pan_info.add(editor);
}
}
pan_info.revalidate();
pan_info.repaint();
}
}
};
private final Weak layerListener = new Weak(this);
/** Creates new form JAdvancedStylePanel */
public JAdvancedStylePanel() {
super(Object.class);
initComponents();
tree.addTreeSelectionListener(treeListener);
guiXml.setEditorKit(new XMLEditorKit());
guiTabs.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
if(guiTabs.getSelectedIndex()==1){
//switched to xml pane, update text
final StyleXmlIO tool = new StyleXmlIO();
try {
final StringWriter writer = new StringWriter();
tool.writeStyle(writer, (Style)style, Specification.StyledLayerDescriptor.V_1_1_0);
guiXml.setText(writer.getBuffer().toString());
} catch (JAXBException ex) {
LOGGER.log(Level.WARNING,ex.getMessage(),ex);
}
}
}
});
tree.getModel().addTreeModelListener(new TreeModelListener() {
public void treeNodesChanged(TreeModelEvent e) {
}
public void treeNodesInserted(TreeModelEvent e) {
if(applying){
final TreePath tp = e.getTreePath().pathByAddingChild(e.getChildren()[0]);
//select the new element
if(tp!=null){
tree.getSelectionModel().setSelectionPath(tp);
}
}
}
public void treeNodesRemoved(TreeModelEvent e) {
}
public void treeStructureChanged(TreeModelEvent e) {
}
});
}
private void applyEditor(final TreePath oldPath){
if(editor == null) return;
//create implies a call to apply if a style element is present
final Object obj = editor.create();
if(obj instanceof Symbolizer){
//in case of a symbolizer we must update it.
if(oldPath != null && oldPath.getLastPathComponent() != null){
final Symbolizer symbol = (Symbolizer) oldPath.getLastPathComponent();
if(!symbol.equals(obj)){
//new symbol created is different, update in the rule
final MutableRule rule = (MutableRule) oldPath.getParentPath().getLastPathComponent();
final int index = rule.symbolizers().indexOf(symbol);
if(index >= 0){
rule.symbolizers().set(index, (Symbolizer) obj);
}
}
}
}
}
/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
guiTabs = new JTabbedPane();
jSplitPane1 = new JSplitPane();
jsp2 = new JScrollPane();
tree = new JStyleTree();
jScrollPane1 = new JScrollPane();
pan_info = new JPanel();
guiXmlPane = new JPanel();
jPanel1 = new JPanel();
guiApply = new JButton();
jScrollPane2 = new JScrollPane();
guiXml = new JTextPane();
setLayout(new BorderLayout());
jSplitPane1.setDividerLocation(220);
jSplitPane1.setDividerSize(4);
jsp2.setViewportView(tree);
jSplitPane1.setLeftComponent(jsp2);
pan_info.setLayout(new GridLayout(1, 1));
jScrollPane1.setViewportView(pan_info);
jSplitPane1.setRightComponent(jScrollPane1);
guiTabs.addTab(MessageBundle.format("xmlGraphic"), jSplitPane1); // NOI18N
guiXmlPane.setLayout(new BorderLayout());
jPanel1.setLayout(new FlowLayout(FlowLayout.RIGHT));
guiApply.setText(MessageBundle.format("apply")); // NOI18N
guiApply.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
guiApplyActionPerformed(evt);
}
});
jPanel1.add(guiApply);
guiXmlPane.add(jPanel1, BorderLayout.SOUTH);
jScrollPane2.setViewportView(guiXml);
guiXmlPane.add(jScrollPane2, BorderLayout.CENTER);
guiTabs.addTab(MessageBundle.format("xmlview"), guiXmlPane); // NOI18N
add(guiTabs, BorderLayout.CENTER);
}// </editor-fold>//GEN-END:initComponents
private void guiApplyActionPerformed(ActionEvent evt) {//GEN-FIRST:event_guiApplyActionPerformed
if(layer != null){
final StyleXmlIO tool = new StyleXmlIO();
try {
final MutableStyle style = tool.readStyle(new StringReader(guiXml.getText()),
Specification.SymbologyEncoding.V_1_1_0);
layer.setStyle(style);
setTarget(layer);
} catch (JAXBException | FactoryException ex) {
Logging.getLogger("org.geotoolkit.gui.swing.propertyedit.styleproperty").log(Level.FINEST,ex.getMessage(),ex);
}
}
}//GEN-LAST:event_guiApplyActionPerformed
@Override
public boolean canHandle(Object target) {
return target instanceof MapLayer;
}
@Override
public void apply() {
applying = true;
applyEditor(tree.getSelectionModel().getSelectionPath());
applying = false;
style = tree.getStyleElement();
if (layer != null && style instanceof MutableStyle && style != layer.getStyle()) {
layer.setStyle((MutableStyle)style);
}
}
@Override
public void setLayer(final MapLayer layer) {
if(this.layer==layer)return;
if(this.layer!=null){
layerListener.unregisterSource(this.layer);
}
this.layer = layer;
if(this.layer!=null){
layerListener.registerSource(this.layer);
}
}
@Override
public MapLayer getLayer() {
return layer;
}
@Override
public void parse(final Object style) {
this.style = style;
parse();
}
@Override
public Object create() {
style = tree.getStyleElement();
apply();
return style;
}
private void parse() {
tree.setStyleElement(style);
}
@Override
public ImageIcon getIcon() {
return IconBundle.getIcon("16_advanced_style");
}
@Override
public Image getPreview() {
return null;
}
@Override
public String getTitle() {
return MessageBundle.format("sldeditor");
}
@Override
public void setTarget(final Object layer) {
if (layer instanceof MapLayer) {
setLayer((MapLayer) layer);
parse(this.layer.getStyle());
}
}
@Override
public void reset() {
parse();
}
@Override
public String getToolTip() {
return "";
}
@Override
public Component getComponent() {
return this;
}
@Override
protected Object[] getFirstColumnComponents() {
return new Object[]{};
}
// Variables declaration - do not modify//GEN-BEGIN:variables
JButton guiApply;
JTabbedPane guiTabs;
JTextPane guiXml;
JPanel guiXmlPane;
JPanel jPanel1;
JScrollPane jScrollPane1;
JScrollPane jScrollPane2;
JSplitPane jSplitPane1;
JScrollPane jsp2;
JPanel pan_info;
JStyleTree tree;
// End of variables declaration//GEN-END:variables
//style events
@Override
public void styleChange(MapLayer source, EventObject event) {}
@Override
public void itemChange(CollectionChangeEvent<MapItem> event) {}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if(MapLayer.STYLE_PROPERTY.equals(evt.getPropertyName()) && evt.getOldValue()!=evt.getNewValue()){
parse(this.layer.getStyle());
}
}
private static final class XMLEditorKit extends StyledEditorKit {
private final ViewFactory xmlViewFactory = new ViewFactory() {
@Override
public View create(Element elem) {
return new XMLView(elem);
}
};
@Override
public ViewFactory getViewFactory() {
return xmlViewFactory;
}
}
/*
* Copyright 2006-2008 Kees de Kooter
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
public static final class XMLView extends PlainView {
private static final Map<Pattern, Color> PATTERNS = new HashMap<Pattern, Color>();
static {
PATTERNS.put(Pattern.compile("(\\<!\\[CDATA\\[).*"), new Color(0, 190, 0)); // cdata start
PATTERNS.put(Pattern.compile(".*(]]>)"), new Color(0, 190, 0)); //cdata end
PATTERNS.put(Pattern.compile("(</?[a-z]*)\\s?>?"), new Color(100, 100, 255)); // tag start
PATTERNS.put(Pattern.compile("\\s(\\w*)\\="), new Color(127, 0, 127)); // attribute name
PATTERNS.put(Pattern.compile("(/>)"), new Color(100, 100,255)); // tag end
PATTERNS.put(Pattern.compile("[a-z-]*\\=(\"[^\"]*\")"), new Color(0,0, 190)); // attribute value
PATTERNS.put(Pattern.compile("(<!--.*-->)"), new Color(0, 190, 0)); // comment
}
public XMLView(Element element) {
super(element);
}
@Override
protected int drawUnselectedText(final Graphics g, int x, int y, final int p0, final int p1) throws BadLocationException {
final Document doc = getDocument();
final String text = doc.getText(p0, p1-p0);
final Segment segment = getLineBuffer();
final SortedMap<Integer, Integer> startMap = new TreeMap<Integer, Integer>();
final SortedMap<Integer, Color> colorMap = new TreeMap<Integer, Color>();
// Match all regexes on this snippet, store positions
for (Map.Entry<Pattern, Color> entry : PATTERNS.entrySet()) {
final Matcher matcher = entry.getKey().matcher(text);
while (matcher.find()) {
startMap.put(matcher.start(1), matcher.end());
colorMap.put(matcher.start(1), entry.getValue());
}
}
// TODO: check the map for overlapping parts
int i = 0;
// Colour the parts
for (Map.Entry<Integer, Integer> entry : startMap.entrySet()) {
int start = entry.getKey();
int end = entry.getValue();
if (i < start) {
g.setColor(Color.black);
doc.getText(p0 + i, start - i, segment);
x = Utilities.drawTabbedText(segment, x, y, g, this, i);
}
g.setColor(colorMap.get(start));
i = end;
doc.getText(p0 + start, i - start, segment);
x = Utilities.drawTabbedText(segment, x, y, g, this, start);
}
// Paint possible remaining text black
if (i < text.length()) {
g.setColor(Color.black);
doc.getText(p0 + i, text.length() - i, segment);
x = Utilities.drawTabbedText(segment, x, y, g, this, i);
}
return x;
}
}
}