Revision: 5992 http://sourceforge.net/p/jump-pilot/code/5992 Author: ma15569 Date: 2018-11-09 13:02:54 +0000 (Fri, 09 Nov 2018) Log Message: ----------- Preaparing the new LegendPlugin for vector layers
Added Paths: ----------- core/trunk/src/com/vividsolutions/jump/workbench/ui/style/LegendPanel.java core/trunk/src/com/vividsolutions/jump/workbench/ui/style/LegendPlugIn.java Added: core/trunk/src/com/vividsolutions/jump/workbench/ui/style/LegendPanel.java =================================================================== --- core/trunk/src/com/vividsolutions/jump/workbench/ui/style/LegendPanel.java (rev 0) +++ core/trunk/src/com/vividsolutions/jump/workbench/ui/style/LegendPanel.java 2018-11-09 13:02:54 UTC (rev 5992) @@ -0,0 +1,256 @@ +package com.vividsolutions.jump.workbench.ui.style; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.Stroke; +import java.awt.geom.AffineTransform; +import java.awt.geom.Point2D; + +import javax.swing.BorderFactory; +import javax.swing.JPanel; + +import org.openjump.core.ui.util.LayerableUtil; + +import com.vividsolutions.jts.geom.Coordinate; +import com.vividsolutions.jts.geom.Envelope; +import com.vividsolutions.jts.io.ParseException; +import com.vividsolutions.jts.io.WKTReader; +import com.vividsolutions.jts.util.Assert; +import com.vividsolutions.jump.feature.AttributeType; +import com.vividsolutions.jump.feature.Feature; +import com.vividsolutions.jump.feature.FeatureCollection; +import com.vividsolutions.jump.feature.FeatureSchema; +import com.vividsolutions.jump.feature.FeatureUtil; +import com.vividsolutions.jump.workbench.model.Layer; +import com.vividsolutions.jump.workbench.model.LayerManager; +import com.vividsolutions.jump.workbench.ui.LayerViewPanel; +import com.vividsolutions.jump.workbench.ui.LayerViewPanelContext; +import com.vividsolutions.jump.workbench.ui.Viewport; +import com.vividsolutions.jump.workbench.ui.renderer.style.BasicStyle; +import com.vividsolutions.jump.workbench.ui.renderer.style.LineStringStyle; +import com.vividsolutions.jump.workbench.ui.renderer.style.Style; +import com.vividsolutions.jump.workbench.ui.renderer.style.VertexStyle; + +public class LegendPanel extends JPanel { + + /** + * Panel that uses LayerView.class capability to show applied style + * according to feature type. See also + * com.vividsolutions.jump.workbench.ui.style.LegendPlugIn class + * + * @author Giuseppe Aruta + */ + private final Layer layer; + private final BasicStyle style; + private final FeatureCollection featureCollection; + + public LegendPanel(Layer layer, BasicStyle style, + FeatureCollection featureCollection) { + + this.layer = layer; + this.style = style; + this.featureCollection = featureCollection; + } + + private static final long serialVersionUID = 1L; + + { + setBackground(new Color(0, 0, 0, 0)); + setBorder(BorderFactory.createEmptyBorder()); + setMaximumSize(new Dimension(120, 40)); + setMinimumSize(new Dimension(120, 40)); + setPreferredSize(new Dimension(120, 40)); + + } + + private final LayerViewPanel dummyLayerViewPanel = new LayerViewPanel( + new LayerManager(), new LayerViewPanelContext() { + @Override + public void setStatusMessage(String message) { + } + + @Override + public void warnUser(String warning) { + } + + @Override + public void handleThrowable(Throwable t) { + } + + }); + + private final Viewport viewport = new Viewport(dummyLayerViewPanel) { + + private final AffineTransform transform = new AffineTransform(); + + @Override + public Envelope getEnvelopeInModelCoordinates() { + return new Envelope(0, 120, 0, 40); + } + + @Override + public AffineTransform getModelToViewTransform() { + return transform; + } + + @Override + public Point2D toViewPoint(Coordinate modelCoordinate) { + return new Point2D.Double(modelCoordinate.x, modelCoordinate.y); + } + + }; + + private void paint(Style style, Graphics2D g) { + final Stroke originalStroke = g.getStroke(); + + if (layer.getVertexStyle().getSize() > 40) { + setPreferredSize(new Dimension(120, layer.getVertexStyle() + .getSize())); + } + + try { + + if (LayerableUtil.isLinealLayer(featureCollection)) { + style.paint(lineFeature(), g, viewport); + } else if (LayerableUtil.isPointLayer(featureCollection)) { + style.paint(pointFeature(), g, viewport); + } else if (LayerableUtil.isPolygonalLayer(featureCollection)) { + style.paint(polygonFeature(), g, viewport); + } else { + style.paint(multiGeometriesFeature(), g, viewport); + } + + } catch (final Exception e) { + try { + style.paint(multiGeometriesFeature(), g, viewport); + } catch (final Exception e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + } finally { + g.setStroke(originalStroke); + } + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + + paint(style, (Graphics2D) g); + + for (final Object element : layer.getStyles()) { + final Style item = (Style) element; + if (item instanceof LineStringStyle) { + final LineStringStyle lineStyle = (LineStringStyle) item + .clone(); + paint(lineStyle, (Graphics2D) g); + } else if (item instanceof VertexStyle + & layer.getVertexStyle().isEnabled()) { + + final VertexStyle vertexStyle = (VertexStyle) item; + // [Giuseppe Aruta 2018_11_9] if the object is + // ExternalSymbolsType.class CadPlan + // otype, symbol is correctly displayed but is + // changed/modified/resized even on LegendPanel + // whenever the user changes it. Using VertexStyle.clone() + // method + // ExternalSymbolsType object is removed + // TODO find a valid clone() method + paint(vertexStyle, (Graphics2D) g); + } + } + + } + + private Feature polygonFeature() { + try { + Feature feat; + + feat = FeatureUtil.toFeature(new WKTReader() + .read("POLYGON ((10 10, 110 10, 110 30, 10 30, 10 10))"), + new FeatureSchema() { + private static final long serialVersionUID = -8627306219650589202L; + { + addAttribute("GEOMETRY", AttributeType.GEOMETRY); + } + }); + + return feat; + } catch (final ParseException e) { + Assert.shouldNeverReachHere(); + + return null; + } + } + + private Feature lineFeature() { + try { + Feature feat; + + feat = FeatureUtil.toFeature( + new WKTReader().read("LINESTRING (10 20.05, 110 19.95)"), + new FeatureSchema() { + private static final long serialVersionUID = -8627306219650589202L; + { + addAttribute("GEOMETRY", AttributeType.GEOMETRY); + } + }); + + return feat; + } catch (final ParseException e) { + Assert.shouldNeverReachHere(); + + return null; + } + } + + private Feature pointFeature() { + try { + Feature feat; + + feat = FeatureUtil.toFeature(new WKTReader().read("POINT (60 20)"), + new FeatureSchema() { + private static final long serialVersionUID = -8627306219650589202L; + { + addAttribute("GEOMETRY", AttributeType.GEOMETRY); + } + }); + + return feat; + } catch (final ParseException e) { + Assert.shouldNeverReachHere(); + + return null; + } + } + + private Feature multiGeometriesFeature() { + try { + Feature feat; + + feat = FeatureUtil + .toFeature( + new WKTReader() + .read("GEOMETRYCOLLECTION (POLYGON ((10 10, 55 10, 55 30, 10 30, 10 10)), POINT (85 15), LINESTRING (65 25.05, 110 24.95))"), + new FeatureSchema() { + private static final long serialVersionUID = -8627306219650589202L; + { + addAttribute("GEOMETRY", + AttributeType.GEOMETRY); + } + }); + + return feat; + } catch (final ParseException e) { + Assert.shouldNeverReachHere(); + + return null; + } + } + +} Property changes on: core/trunk/src/com/vividsolutions/jump/workbench/ui/style/LegendPanel.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Added: core/trunk/src/com/vividsolutions/jump/workbench/ui/style/LegendPlugIn.java =================================================================== --- core/trunk/src/com/vividsolutions/jump/workbench/ui/style/LegendPlugIn.java (rev 0) +++ core/trunk/src/com/vividsolutions/jump/workbench/ui/style/LegendPlugIn.java 2018-11-09 13:02:54 UTC (rev 5992) @@ -0,0 +1,442 @@ +package com.vividsolutions.jump.workbench.ui.style; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.GridBagLayout; +import java.awt.Point; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import javax.swing.BorderFactory; +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; + +import org.openjump.core.ui.util.LayerableUtil; +import org.openjump.sextante.gui.additionalResults.AdditionalResults; +import org.saig.core.gui.swing.sldeditor.util.FormUtils; + +import com.vividsolutions.jts.geom.Envelope; +import com.vividsolutions.jts.geom.Geometry; +import com.vividsolutions.jts.util.Assert; +import com.vividsolutions.jump.I18N; +import com.vividsolutions.jump.feature.Feature; +import com.vividsolutions.jump.feature.FeatureCollection; +import com.vividsolutions.jump.feature.FeatureDataset; +import com.vividsolutions.jump.feature.FeatureSchema; +import com.vividsolutions.jump.util.LangUtil; +import com.vividsolutions.jump.workbench.JUMPWorkbench; +import com.vividsolutions.jump.workbench.WorkbenchContext; +import com.vividsolutions.jump.workbench.model.Layer; +import com.vividsolutions.jump.workbench.plugin.AbstractPlugIn; +import com.vividsolutions.jump.workbench.plugin.EnableCheckFactory; +import com.vividsolutions.jump.workbench.plugin.MultiEnableCheck; +import com.vividsolutions.jump.workbench.plugin.PlugInContext; +import com.vividsolutions.jump.workbench.ui.TextEditor; +import com.vividsolutions.jump.workbench.ui.WorkbenchFrame; +import com.vividsolutions.jump.workbench.ui.renderer.style.BasicStyle; +import com.vividsolutions.jump.workbench.ui.renderer.style.ColorThemingStyle; + +public class LegendPlugIn extends AbstractPlugIn { + /** + * Plugin to display the legend of correct symbology used into a view + * + * @author Giuseppe Aruta + */ + private JScrollPane scrollPane = new JScrollPane(); + + String taskString = I18N.get("ui.WorkbenchFrame.task"); + String layerString = I18N.get("ui.plugin.analysis.BufferPlugIn.layer"); + String labelString = I18N.get("ui.renderer.style.LabelStyle.Label"); + + @Override + public void initialize(PlugInContext context) throws Exception { + super.initialize(context); + + } + + public static MultiEnableCheck createEnableCheck( + WorkbenchContext workbenchContext) { + final EnableCheckFactory checkFactory = new EnableCheckFactory( + workbenchContext); + return new MultiEnableCheck() + .add(checkFactory.createTaskWindowMustBeActiveCheck()) + .add(checkFactory.createAtLeastNLayersMustExistCheck(1)) + .add(checkFactory.createAtLeastNLayersMustBeSelectedCheck(1)); + } + + @SuppressWarnings("deprecation") + public ImageIcon getIcon() { + + return org.openjump.core.ui.images.IconLoader.icon("save_legend.png"); + } + + WorkbenchFrame frame = JUMPWorkbench.getInstance().getFrame(); + AdditionalResults frames; + + @Override + public boolean execute(PlugInContext context) throws Exception { + reportNothingToUndoYet(context); + + if (getVectorLayers(context).size() == 0) { + JOptionPane + .showMessageDialog( + frame, + I18N.get("org.openjump.core.ui.plugin.wms.WMSLegendPlugIn.message") + + " (" + + I18N.get("com.vividsolutions.jump.workbench.imagery.ReferencedImagesLayer") + + ", " + + I18N.get("org.openjump.core.ui.plugin.layer.LayerPropertiesPlugIn.Null-Geometries") + + ",...)", I18N + .get("ui.WorkbenchFrame.warning"), + JOptionPane.WARNING_MESSAGE); + } else { + AdditionalResults.addAdditionalResultAndShow( + getName() + "-" + I18N.get("ui.WorkbenchFrame.task") + ": " + + context.getTask().getName(), legend(context)); + } + + return true; + + } + + private final static String EMPTY = I18N + .get("org.openjump.core.ui.plugin.layer.ExtractLayersByGeometry.empty"); + private final static String POINT = I18N + .get("org.openjump.core.ui.plugin.layer.ExtractLayersByGeometry.point"); + private final static String POLYLINE = I18N + .get("org.openjump.core.ui.plugin.layer.ExtractLayersByGeometry.polyline"); + private final static String POLYGON = I18N + .get("org.openjump.core.ui.plugin.layer.ExtractLayersByGeometry.polygon"); + private final static String GEOMETRYCOLLECTION = I18N + .get("org.openjump.core.ui.plugin.layer.ExtractLayersByGeometry.geometrycollection"); + + private LegendPanel legendPanel(final Layer layer, final BasicStyle style, + FeatureCollection featureCollection) { + final LegendPanel previewPanel = new LegendPanel(layer, style, + featureCollection); + previewPanel.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent evt) { + + } + }); + return previewPanel; + } + + private JLabel label(final String text, Font font, int increaseSize) { + final JLabel labelValue = new JLabel(); + labelValue.setToolTipText(""); + + labelValue.setBorder(BorderFactory.createEmptyBorder()); + font = new Font(labelValue.getFont().getName(), labelValue.getFont() + .getStyle(), labelValue.getFont().getSize() + increaseSize); + labelValue.setFont(font); + labelValue.setText(text); + // labelValue.setEnabled(editable); + labelValue.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent evt) { + if (evt.getClickCount() == 2) { + final TextEditor fc = new TextEditor(); + + fc.setSelectedFont(labelValue.getFont()); + fc.setSelectedFontSize(labelValue.getFont().getSize()); + fc.setSelectedFontStyle(labelValue.getFont().getStyle()); + fc.setSelectedFontFamily(labelValue.getFont().getFamily()); + fc.setSampleTextField(labelValue.getText()); + + fc.showDialog( + labelValue.getParent(), + I18N.get("org.openjump.core.ui.plugin.style.LegendPlugIn.modify-label")); + final Font labelFont = fc.getSelectedFont(); + if (fc.wasOKPressed()) { + labelValue.setFont(labelFont); + labelValue.setText(fc.getSampleTextField().getText()); + + } else { + reportNothingToUndoYet(null); + } + + } + } + }); + labelValue.setForeground(new Color(20, 24, 25)); + return labelValue; + } + + private void mapByGeomType(Feature feature, + Map<String, FeatureCollection> map) { + final Geometry geometry = feature.getGeometry(); + final Feature f = feature.clone(false, true); + f.setGeometry(geometry); + if (geometry.getGeometryType().equals("Point")) { + map.get(POINT).add(f); + } else if (geometry.getGeometryType().equals("MultiPoint")) { + map.get(POINT).add(f); + } else if (geometry.getGeometryType().equals("LineString")) { + map.get(POLYLINE).add(f); + } else if (geometry.getGeometryType().equals("MultiLineString")) { + map.get(POLYLINE).add(f); + } else if (geometry.getGeometryType().equals("Polygon")) { + map.get(POLYGON).add(f); + } else if (geometry.getGeometryType().equals("MultiPolygon")) { + map.get(POLYGON).add(f); + } else { + map.get(GEOMETRYCOLLECTION).add(f); + } + } + + public JPanel layersPanel(List<Layer> layers) { + final JPanel mainPanel = new JPanel(); + final Font plainFont = new Font(mainPanel.getFont().getName(), + Font.PLAIN, mainPanel.getFont().getSize()); + mainPanel.setLayout(new GridBagLayout()); + final int gridx = 0; + int gridy = 0; + final String projectTitle = taskString + ": " + + JUMPWorkbench.getInstance().getContext().getTask().getName(); + final JLabel projectName = label(projectTitle, plainFont, 6); + projectName.setToolTipText(projectTitle); + FormUtils.addRowInGBL(mainPanel, gridy, gridx + 1, projectName, false, + true); + gridy++; + FormUtils.addSpacerInGBL(mainPanel, gridy++, gridx, 80, Color.white); + gridy++; + final Iterator<Layer> it = layers.iterator(); + while (it.hasNext()) { + final Layer layer = it.next(); + LegendPanel previewPanel = null; + final FeatureSchema schema = layer.getFeatureCollectionWrapper() + .getFeatureSchema(); + final FeatureCollection emptyFeatures = new FeatureDataset(schema); + final FeatureCollection pointFeatures = new FeatureDataset(schema); + final FeatureCollection polyLineFeatures = new FeatureDataset( + schema); + final FeatureCollection polygonFeatures = new FeatureDataset(schema); + final FeatureCollection geometryCollectionFeatures = new FeatureDataset( + schema); + final Map<String, FeatureCollection> mapfeat = new HashMap<>(); + mapfeat.put(EMPTY, emptyFeatures); + mapfeat.put(POINT, pointFeatures); + mapfeat.put(POLYLINE, polyLineFeatures); + mapfeat.put(POLYGON, polygonFeatures); + mapfeat.put(GEOMETRYCOLLECTION, geometryCollectionFeatures); + final FeatureCollection featureCollection = layer + .getFeatureCollectionWrapper(); + for (final Feature feature : featureCollection.getFeatures()) { + mapByGeomType(feature, mapfeat); + } + String text; + if (ColorThemingStyle.get(layer).isEnabled()) { + final JLabel layerName = label(layer.getName(), plainFont, 4); + layerName.setToolTipText(taskString + + " :" + + JUMPWorkbench.getInstance().getContext().getTask() + .getName() + " - " + layerString + " :" + + layer.getName()); + FormUtils.addRowInGBL(mainPanel, gridy++, gridx, layerName, + true, true); + final Map<Object, BasicStyle> attributeValueToBasicStyleMap = ColorThemingStyle + .get(layer).getAttributeValueToBasicStyleMap(); + final Map<Object, String> attributeValueToLabelMap = ColorThemingStyle + .get(layer).getAttributeValueToLabelMap(); + final List<ColorThemingValue> colorThemingValues = new ArrayList<>(); + for (final Map.Entry<Object, BasicStyle> entry : attributeValueToBasicStyleMap + .entrySet()) { + final Object key = entry.getKey(); + for (final Map.Entry<String, FeatureCollection> entryFeatCol : mapfeat + .entrySet()) { + if (entryFeatCol.getValue().size() != 0) { + if (isEsriType(layer)) { + colorThemingValues.add(new ColorThemingValue( + key, entry.getValue(), + attributeValueToLabelMap.get(key), + entryFeatCol.getValue())); + } else { + final Set<String> listValues = getAvailableValues( + ColorThemingStyle.get(layer), + entryFeatCol.getValue()); + for (final Object stock : listValues) { + if (stock.toString().matches( + entry.getKey().toString())) { + colorThemingValues + .add(new ColorThemingValue(key, + entry.getValue(), + attributeValueToLabelMap + .get(key), + entryFeatCol.getValue())); + } + } + } + } + } + } + for (final ColorThemingValue entry : colorThemingValues) { + previewPanel = legendPanel(layer, entry.getStyle(), + entry.getFeatureCollection()); + + text = entry.toString(); + final JLabel entryName = label(entry.toString(), plainFont, + 2); + entryName.setToolTipText(taskString + + " :" + + JUMPWorkbench.getInstance().getContext() + .getTask().getName() + " - " + layerString + + " :" + layer.getName() + " - " + labelString + + " :" + text); + FormUtils.addRowInGBL(mainPanel, gridy++, gridx, + previewPanel, entryName); + gridy++; + } + } else { + for (final Map.Entry<String, FeatureCollection> entryFeatCol : mapfeat + .entrySet()) { + if (entryFeatCol.getValue().size() == 0) { + continue; + } + previewPanel = legendPanel(layer, layer.getBasicStyle(), + entryFeatCol.getValue()); + previewPanel.setToolTipText(taskString + + " :" + + JUMPWorkbench.getInstance().getContext() + .getTask().getName() + " - " + layerString + + " :" + layer.getName()); + + FormUtils.addRowInGBL(mainPanel, gridy++, gridx, + previewPanel, label(layer.getName(), plainFont, 2)); + gridy++; + } + } + FormUtils + .addSpacerInGBL(mainPanel, gridy++, gridx, 80, Color.white); + gridy++; + } + + mainPanel.repaint(); + return mainPanel; + } + + @SuppressWarnings("unchecked") + private List<Layer> getVectorLayers(PlugInContext context) { + final List<Layer> ListVectorLayerNames = new ArrayList<Layer>(); + final Collection<Layer> layers1 = context.getLayerNamePanel() + .selectedNodes(Layer.class); + for (final Layer layer : layers1) { + final Envelope env = layer.getFeatureCollectionWrapper() + .getEnvelope(); + if (env.isNull() | LayerableUtil.isImage(layer)) { + // Remove Images, empty vector layers and tables + ListVectorLayerNames.remove(layer); + } else { + ListVectorLayerNames.add(layer); + } + } + return ListVectorLayerNames; + } + + private JPanel legend(PlugInContext context) throws IOException { + final JPanel mainPanel = new JPanel(new BorderLayout()); + JPanel mPanel = new JPanel(); + + mPanel = layersPanel(getVectorLayers(context)); + + scrollPane.setBackground(Color.WHITE); + scrollPane = new JScrollPane(mPanel, + JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, + JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); + scrollPane.getViewport().getView().setBackground(Color.WHITE); + scrollPane.getViewport().getView().setForeground(Color.WHITE); + scrollPane.setPreferredSize(new Dimension(400, context + .getLayerViewPanel().getHeight() - 20)); + scrollPane.getViewport().setViewPosition(new Point(0, 0)); + + mainPanel.add(scrollPane, BorderLayout.CENTER); + return mainPanel; + } + + private boolean isEsriType(Layer layer) { + if (LayerableUtil.isLinealLayer(layer) + || LayerableUtil.isPointLayer(layer) + || LayerableUtil.isPolygonalLayer(layer)) { + return true; + } else { + return false; + } + } + + @Override + public String getName() { + return I18N.get("org.openjump.core.ui.plugin.style.LegendPlugIn"); + } + + public static class ColorThemingValue { + private final Object value; + private final BasicStyle style; + private final String label; + private final FeatureCollection featureCollection; + + ColorThemingValue(Object value, BasicStyle style, String label, + FeatureCollection featureCollection) { + this.value = value; + this.style = style; + Assert.isTrue(label != null); + this.label = label; + this.featureCollection = featureCollection; + } + + @Override + public String toString() { + return label; + } + + @Override + public boolean equals(Object other) { + return other instanceof ColorThemingValue + && LangUtil.bothNullOrEqual(value, + ((ColorThemingValue) other).value) + && style == ((ColorThemingValue) other).style; + } + + public BasicStyle getStyle() { + return style; + } + + public FeatureCollection getFeatureCollection() { + return featureCollection; + } + } + + public static Set<String> getAvailableValues(ColorThemingStyle style, + FeatureCollection fc) { + final Set<String> set = new TreeSet<>(); + set.add(""); + final Iterator<Feature> it = fc.iterator(); + while (it.hasNext()) { + final Feature f = it.next(); + + if (style.isEnabled()) { + try { + set.add(f.getAttribute(style.getAttributeName()).toString()); + } catch (final Throwable t) { + } + + } + } + return set; + } + +} Property changes on: core/trunk/src/com/vividsolutions/jump/workbench/ui/style/LegendPlugIn.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property _______________________________________________ Jump-pilot-devel mailing list Jump-pilot-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/jump-pilot-devel