1 /*
2  * Copyright (c) 2016 Vivid Solutions.
3  *
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
7  * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
8  * and the Eclipse Distribution License is available at
9  *
10  * http://www.eclipse.org/org/documents/edl-v10.php.
11  */
12 package org.locationtech.jtstest.testbuilder;
13 
14 import java.awt.BorderLayout;
15 import java.awt.Dimension;
16 import java.awt.FlowLayout;
17 import java.awt.Font;
18 import java.awt.GridLayout;
19 import java.awt.Insets;
20 import java.awt.event.ActionEvent;
21 import java.awt.event.ActionListener;
22 import java.util.Vector;
23 
24 import javax.swing.BorderFactory;
25 import javax.swing.BoxLayout;
26 import javax.swing.ComboBoxModel;
27 import javax.swing.DefaultComboBoxModel;
28 import javax.swing.ImageIcon;
29 import javax.swing.JButton;
30 import javax.swing.JCheckBox;
31 import javax.swing.JComboBox;
32 import javax.swing.JComponent;
33 import javax.swing.JLabel;
34 import javax.swing.JPanel;
35 import javax.swing.JTextField;
36 import javax.swing.SwingConstants;
37 import javax.swing.border.EmptyBorder;
38 
39 import org.locationtech.jts.operation.buffer.BufferParameters;
40 import org.locationtech.jts.util.Stopwatch;
41 import org.locationtech.jtstest.geomfunction.BaseGeometryFunction;
42 import org.locationtech.jtstest.geomfunction.GeometryFunction;
43 import org.locationtech.jtstest.geomfunction.GeometryFunctionRegistry;
44 import org.locationtech.jtstest.geomfunction.RepeaterGeometryFunction;
45 import org.locationtech.jtstest.geomfunction.SpreaderGeometryFunction;
46 import org.locationtech.jtstest.testbuilder.controller.JTSTestBuilderController;
47 import org.locationtech.jtstest.testbuilder.event.GeometryFunctionEvent;
48 import org.locationtech.jtstest.testbuilder.event.GeometryFunctionListener;
49 import org.locationtech.jtstest.testbuilder.event.SpatialFunctionPanelEvent;
50 import org.locationtech.jtstest.testbuilder.event.SpatialFunctionPanelListener;
51 import org.locationtech.jtstest.testbuilder.ui.SwingUtil;
52 import org.locationtech.jtstest.util.ClassUtil;
53 
54 
55 /**
56  * @version 1.7
57  */
58 public class SpatialFunctionPanel
59 extends JPanel implements FunctionPanel
60 {
61   private static final EmptyBorder LABEL_BORDER = new EmptyBorder(3,5,3,5);
62 
63   private static final String[] PARAM_DEFAULT = { "10", "0", "0", "0", "0" };
64 
65   private static String[] capStyleItems = new String[] { "Round", "Flat", "Square" };
66   private static Object[] capStyleValues = new Object[] {
67   		BufferParameters.CAP_ROUND,
68   		BufferParameters.CAP_FLAT,
69   		BufferParameters.CAP_SQUARE
70   		};
71   private static String[] joinStyleItems = new String[] { "Round", "Mitre", "Bevel" };
72   private static Object[] joinStyleValues = new Object[] {
73   		BufferParameters.JOIN_ROUND,
74   		BufferParameters.JOIN_MITRE,
75   		BufferParameters.JOIN_BEVEL
76   };
77 
78 
79 
80   JPanel panelExecControl = new JPanel();
81 //  GeometryFunctionListPanel geomFuncPanel = new GeometryFunctionListPanel();
82   GeometryFunctionTreePanel geomFuncPanel = new GeometryFunctionTreePanel();
83   GridLayout gridLayout1 = new GridLayout();
84   GridLayout gridLayout2 = new GridLayout();
85 
86 
87   BorderLayout borderLayout1 = new BorderLayout();
88   BorderLayout borderLayout2 = new BorderLayout();
89 
90   JPanel panelParam = new JPanel();
91   JPanel panelExec = new JPanel();
92   JPanel panelExecMeta = new JPanel();
93   JPanel panelExecParam = new JPanel();
94   FlowLayout flowLayout = new FlowLayout();
95   FlowLayout flowLayout1 = new FlowLayout();
96   FlowLayout flowLayout2 = new FlowLayout();
97 
98   private JButton execButton = new JButton();
99   private JButton execToNewButton = new JButton();
100 
101   private final ImageIcon clearIcon = new ImageIcon(this.getClass().getResource("clear.gif"));
102   private final ImageIcon expandDownIcon = new ImageIcon(this.getClass().getResource("Expand-Down.png"));
103 
104   private transient Vector spatialFunctionPanelListeners;
105   private JPanel panelControl = new JPanel();
106   private JCheckBox displayAAndBCheckBox = new JCheckBox();
107   private JCheckBox cbExecEachA = new JCheckBox();
108   private JCheckBox cbExecEachB = new JCheckBox();
109   private JCheckBox cbExecRepeat = new JCheckBox();
110   private final JTextField txtRepeatCount = new JTextField();
111   private JButton btnClearResult = new JButton();
112 
113   private JLabel lblFunctionName = new JLabel();
114   private JLabel lblFunction = new JLabel();
115   private JLabel lblDistance = new JLabel();
116   private JTextField txtDistance = new JTextField();
117   private JLabel lblQuadSegs = new JLabel();
118   private JTextField txtQuadrantSegs = new JTextField();
119   private JLabel lblCapStyle = new JLabel();
120   private JComboBox cbCapStyle = new JComboBox();
121   private JLabel lblJoinStyle = new JLabel();
122   private JComboBox cbJoinStyle = new JComboBox();
123   private JLabel lblMitreLimit = new JLabel();
124   private JTextField txtMitreLimit = new JTextField();
125 
126   private JComponent[] paramComp = { txtDistance, txtQuadrantSegs, cbCapStyle, cbJoinStyle, txtMitreLimit };
127   private JLabel[] paramLabel = { lblDistance, lblQuadSegs, lblCapStyle, lblJoinStyle, lblMitreLimit };
128 
129   private GeometryFunction currentFunc = null;
130   private Stopwatch timer;
131 
132   private JButton btnExecEach;
133 
SpatialFunctionPanel()134   public SpatialFunctionPanel() {
135     try {
136       uiInit();
137     }
138     catch (Exception ex) {
139       ex.printStackTrace();
140     }
141   }
142 
uiInit()143   void uiInit() throws Exception {
144     this.setLayout(borderLayout1);
145 
146 //    geomFuncPanel.populate(JTSTestBuilder.getFunctionRegistry().getGeometryFunctions());
147     geomFuncPanel.populate(JTSTestBuilder.getFunctionRegistry().getCategorizedGeometryFunctions());
148 
149     panelParam.setLayout(gridLayout2);
150     gridLayout2.setRows(6);
151     gridLayout2.setColumns(2);
152     panelExec.setLayout(flowLayout);
153     panelExecMeta.setLayout(flowLayout2);
154     panelExecParam.setLayout(borderLayout2);
155 
156 
157     displayAAndBCheckBox.setSelected(true);
158     displayAAndBCheckBox.setToolTipText("");
159     displayAAndBCheckBox.setText("Display Input");
160     displayAAndBCheckBox.addActionListener(new java.awt.event.ActionListener() {
161         public void actionPerformed(ActionEvent e) {
162           displayAAndBCheckBox_actionPerformed(e);
163         }
164       });
165 
166     lblFunction.setText("Function");
167     lblFunction.setHorizontalAlignment(SwingConstants.RIGHT);
168     lblFunction.setBorder(LABEL_BORDER);//top,left,bottom,right
169 
170     lblFunctionName.setHorizontalAlignment(SwingConstants.LEFT);
171     lblFunctionName.setFont(new java.awt.Font("Dialog", Font.BOLD, 12));
172 
173     lblDistance.setText("Distance");
174 
175     txtDistance.setMaximumSize(new Dimension(25, 2147483647));
176     txtDistance.setMinimumSize(new Dimension(25, 21));
177     txtDistance.setPreferredSize(new Dimension(25, 17));
178     txtDistance.setText(PARAM_DEFAULT[0]);
179     txtDistance.setHorizontalAlignment(SwingConstants.RIGHT);
180 
181     lblQuadSegs.setText("Quadrant Segs");
182     txtQuadrantSegs.setHorizontalAlignment(SwingConstants.RIGHT);
183 
184     lblCapStyle.setText("Cap Style");
185     ComboBoxModel modelCapStyle = new DefaultComboBoxModel(capStyleItems);
186     cbCapStyle.setModel(modelCapStyle);
187 
188     lblJoinStyle.setText("Join Style");
189     ComboBoxModel modelJoinStyle = new DefaultComboBoxModel(joinStyleItems);
190     cbJoinStyle.setModel(modelJoinStyle);
191 
192     lblMitreLimit.setText("Mitre Limit");
193     txtMitreLimit.setHorizontalAlignment(SwingConstants.RIGHT);
194 
195     initLabels(paramLabel);
196 
197 
198 
199     btnClearResult.setToolTipText("");
200     btnClearResult.setMargin(new Insets(0, 10, 0, 10));
201     btnClearResult.setSelected(true);
202     btnClearResult.setText("Clear Result");
203     btnClearResult.addActionListener(new java.awt.event.ActionListener() {
204 
205         public void actionPerformed(ActionEvent e) {
206           clearResultButton_actionPerformed(e);
207         }
208       });
209 
210     panelParam.add(lblFunction);
211     panelParam.add(lblFunctionName);
212     panelParam.add(lblDistance);
213     panelParam.add(txtDistance);
214     panelParam.add(lblQuadSegs);
215     panelParam.add(txtQuadrantSegs);
216     panelParam.add(lblCapStyle);
217     panelParam.add(cbCapStyle);
218     panelParam.add(lblJoinStyle);
219     panelParam.add(cbJoinStyle);
220     panelParam.add(lblMitreLimit);
221     panelParam.add(txtMitreLimit);
222 
223     panelControl.setLayout(flowLayout1);
224     panelControl.add(displayAAndBCheckBox, null);
225     //panelControl.add(btnClearResult, null);
226 
227 
228     cbExecEachA.setToolTipText("Compute for each A geometry element");
229     cbExecEachA.setText("Each A");
230 
231     cbExecEachB.setToolTipText("Compute for each B geometry element");
232     cbExecEachB.setText("Each B");
233 
234     cbExecRepeat.setToolTipText("Repeat function a number of times, incrementing the first parameter");
235     cbExecRepeat.setText("Repeat");
236 
237     txtRepeatCount.setMaximumSize(new Dimension(25, 2147483647));
238     txtRepeatCount.setMinimumSize(new Dimension(30, 21));
239     txtRepeatCount.setPreferredSize(new Dimension(30, 21));
240     txtRepeatCount.setText("10");
241     txtRepeatCount.setHorizontalAlignment(SwingConstants.RIGHT);
242 
243     execButton = SwingUtil.createButton(AppIcons.EXECUTE, AppStrings.TIP_EXECUTE,
244         new ActionListener() {
245       public void actionPerformed(ActionEvent e) {
246         execButton_actionPerformed(e);
247       }
248     });
249     execButton.setEnabled(false);
250 
251     execToNewButton = SwingUtil.createButton("New", AppIcons.EXECUTE, "Compute function result to a new case",
252         new ActionListener() {
253       public void actionPerformed(ActionEvent e) {
254         execToNewButton_actionPerformed(e);
255       }
256     });
257     execToNewButton.setEnabled(false);
258 
259     JButton btnShowExecExt = SwingUtil.createButton(expandDownIcon, "Show extended/meta Compute tools",
260         new ActionListener() {
261       public void actionPerformed(ActionEvent e) {
262         clearExtended();
263         panelExecMeta.setVisible(! panelExecMeta.isVisible());
264       }
265     });
266     btnShowExecExt.setPreferredSize(new Dimension(20, 20));
267     btnShowExecExt.setBorder(BorderFactory.createEmptyBorder());
268     btnShowExecExt.setContentAreaFilled(false);
269     btnShowExecExt.setFocusable(false);
270 
271     panelExec.add(execButton);
272     // disabled until behaviour is worked out
273     panelExec.add(execToNewButton);
274     //panelExec.add(btnShowExecExt);
275 
276     JPanel panelExecHolder = new JPanel();
277     panelExecHolder.setLayout(new BorderLayout());
278     panelExecHolder.add(panelExec, BorderLayout.CENTER);
279     panelExecHolder.add(btnShowExecExt, BorderLayout.EAST);
280 
281     panelExecMeta.add(cbExecEachA);
282     panelExecMeta.add(cbExecEachB);
283     panelExecMeta.add(cbExecRepeat);
284     panelExecMeta.add(txtRepeatCount);
285     panelExecMeta.setVisible(false);
286 
287     panelExecControl.setLayout(new BoxLayout(panelExecControl, BoxLayout.Y_AXIS));
288     panelExecControl.add(panelExecHolder);
289     panelExecControl.add(panelExecMeta);
290     panelExecControl.add(panelControl);
291 
292     panelExecParam.add(panelParam, BorderLayout.CENTER);
293     panelExecParam.add(panelExecControl, BorderLayout.SOUTH);
294 
295     this.add(geomFuncPanel, BorderLayout.CENTER);
296     this.add(panelExecParam, BorderLayout.SOUTH);
297 
298     GeometryFunctionListener gfListener = new GeometryFunctionListener() {
299       public void functionSelected(GeometryFunctionEvent e) {
300       	functionChanged(e.getFunction());
301       }
302       public void functionInvoked(GeometryFunctionEvent e) {
303         execFunction(e.getFunction(), false);
304       }
305     };
306     geomFuncPanel.addGeometryFunctionListener(gfListener);
307 
308     hideAllParams(paramComp, paramLabel);
309   }
310 
initLabels(JLabel[] paramLabel)311   static void initLabels(JLabel[] paramLabel)
312   {
313     for (int i = 0; i < paramLabel.length; i++) {
314       JLabel lbl = paramLabel[i];
315       lbl.setHorizontalAlignment(SwingConstants.RIGHT);
316       lbl.setBorder(LABEL_BORDER);
317     }
318   }
319 
enableExecuteControl(boolean isEnabled)320   public void enableExecuteControl(boolean isEnabled)
321   {
322     execButton.setEnabled(isEnabled);
323     execToNewButton.setEnabled(isEnabled);
324   }
325 
clearResultButton_actionPerformed(ActionEvent e)326   void clearResultButton_actionPerformed(ActionEvent e) {
327     clearFunction();
328   }
329 
execButton_actionPerformed(ActionEvent e)330   void execButton_actionPerformed(ActionEvent e) {
331     execFunction(getMetaFunction(), false);
332   }
333 
execToNewButton_actionPerformed(ActionEvent e)334   void execToNewButton_actionPerformed(ActionEvent e) {
335     execFunction(getMetaFunction(), true);
336   }
337 
getMetaFunction()338   GeometryFunction getMetaFunction() {
339     GeometryFunction funToRun = geomFuncPanel.getFunction();
340     if (! isMetaFunctionEnabled()) return funToRun;
341 
342     if (isFunctionRepeated()) {
343       int count = SwingUtil.getInteger(txtRepeatCount, 10);
344       funToRun = new RepeaterGeometryFunction(funToRun, count);
345     }
346     if (isFunctionEach()) {
347       funToRun = new SpreaderGeometryFunction(funToRun, isEachA(), isEachB());
348     }
349     return funToRun;
350   }
351 
352 
isMetaFunctionEnabled()353   private boolean isMetaFunctionEnabled() {
354     return panelExecMeta.isVisible();
355   }
356 
isFunctionRepeated()357   private boolean isFunctionRepeated() {
358     return cbExecRepeat.isSelected();
359   }
isFunctionEach()360   private boolean isFunctionEach() {
361     return cbExecEachA.isSelected() || cbExecEachB.isSelected();
362   }
isEachA()363   private boolean isEachA() {
364     return cbExecEachA.isSelected();
365   }
isEachB()366   private boolean isEachB() {
367     return cbExecEachB.isSelected();
368   }
clearExtended()369   void clearExtended() {
370     cbExecRepeat.setSelected(false);
371     cbExecEachA.setSelected(false);
372     cbExecEachB.setSelected(false);
373   }
374 
displayAAndBCheckBox_actionPerformed(ActionEvent e)375   void displayAAndBCheckBox_actionPerformed(ActionEvent e) {
376     JTSTestBuilderController.editPanel().setShowingInput(displayAAndBCheckBox.isSelected());
377   }
378 
setCurrentFunction(GeometryFunction func)379   private void setCurrentFunction(GeometryFunction func) {
380     currentFunc = func;
381     // fire execution event even if null, to set UI appropriately
382     fireFunctionExecuted(new SpatialFunctionPanelEvent(this));
383   }
384 
execFunction(GeometryFunction func, boolean createNew)385   public void execFunction(GeometryFunction func, boolean createNew) {
386     currentFunc = func;
387     if (currentFunc == null)
388       return;
389     fireFunctionExecuted(new SpatialFunctionPanelEvent(this, createNew));
390   }
391 
functionChanged(GeometryFunction func)392   private void functionChanged(GeometryFunction func)
393   {
394     currentFunc = func;
395     lblFunctionName.setText(func.getName());
396     lblFunctionName.setToolTipText( GeometryFunctionRegistry.functionDescriptionHTML(func) );
397 
398     updateParameters(func, paramComp, paramLabel);
399 
400     execButton.setEnabled(true);
401     execToNewButton.setEnabled(true);
402   }
403 
updateParameters(GeometryFunction func, JComponent[] paramComp, JLabel[] paramLabel)404   static void updateParameters(GeometryFunction func, JComponent[] paramComp, JLabel[] paramLabel) {
405     int numNonGeomParams = numNonGeomParams(func);
406     int indexOffset = BaseGeometryFunction.firstScalarParamIndex(func);
407     for (int i = 0; i < paramComp.length; i++) {
408       boolean isUsed = numNonGeomParams > i;
409       if (isUsed) {
410         paramLabel[i].setText(func.getParameterNames()[i+indexOffset]);
411       }
412       paramComp[i].setVisible(isUsed);
413       paramLabel[i].setVisible(isUsed);
414       SpatialFunctionPanel.setToolTipText(paramComp[i], func, i);
415     }
416   }
417 
hideAllParams(JComponent[] paramComp, JLabel[] paramLabel)418   static void hideAllParams(JComponent[] paramComp, JLabel[] paramLabel) {
419     for (int i = 0; i < paramComp.length; i++) {
420       paramComp[i].setVisible(false);
421       paramLabel[i].setVisible(false);
422     }
423   }
424 
setToolTipText(JComponent control, GeometryFunction func, int i)425   private static void setToolTipText(JComponent control, GeometryFunction func, int i) {
426     String txt = null;
427     if (func.getParameterTypes().length > i) {
428       txt = "Enter a " + func.getParameterTypes()[i].getSimpleName();
429     }
430     control.setToolTipText(txt);
431   }
432 
numNonGeomParams(GeometryFunction func)433   private static int numNonGeomParams(GeometryFunction func)
434   {
435     int count = 0;
436     Class[] paramTypes = func.getParameterTypes();
437     for (int i = 0; i < paramTypes.length; i++) {
438       if (! ClassUtil.isGeometry(paramTypes[i]))
439         count++;
440     }
441     return count;
442   }
443 
attributeParamOffset(GeometryFunction func)444   public static int attributeParamOffset(GeometryFunction func) {
445     return func.isBinary() ? 1 : 0;
446   }
447 
shouldShowGeometryA()448   public boolean shouldShowGeometryA() {
449     return displayAAndBCheckBox.isSelected();
450   }
451 
shouldShowGeometryB()452   public boolean shouldShowGeometryB() {
453     return displayAAndBCheckBox.isSelected();
454   }
455 
clearFunction()456   public void clearFunction() {
457     setCurrentFunction(null);
458   }
459 
getFunctionParams()460   public Object[] getFunctionParams()
461   {
462     if (currentFunc == null) return null;
463     Class[] paramTypes = currentFunc.getParameterTypes();
464     Object[] paramVal = new Object[paramTypes.length];
465 
466     for (int i = 0; i < paramVal.length; i++) {
467       Object valRaw = getParamValue(i);
468       paramVal[i] = SwingUtil.coerce(valRaw, paramTypes[i]);
469     }
470     return paramVal;
471   }
472 
getParamValue(int index)473   private Object getParamValue(int index) {
474     if (currentFunc.isBinary() && index == 0)
475       return JTSTestBuilder.controller().getGeometryB();
476 
477     int attrIndex = index - attributeParamOffset(currentFunc);
478 
479     switch (attrIndex) {
480     case 0: return valOrDefault(SwingUtil.value(txtDistance), PARAM_DEFAULT[0]);
481     case 1: return valOrDefault(SwingUtil.value(txtQuadrantSegs), PARAM_DEFAULT[1]);
482     case 2: return SwingUtil.value(cbCapStyle, capStyleValues);
483     case 3: return SwingUtil.value(cbJoinStyle, joinStyleValues);
484     case 4: return valOrDefault(SwingUtil.value(txtMitreLimit), PARAM_DEFAULT[4]);
485     }
486     return null;
487   }
488 
valOrDefault(String s, String defaultVal)489   public static String valOrDefault(String s, String defaultVal) {
490     if (s.length() > 0) return s;
491     return defaultVal;
492   }
493 
isFunctionSelected()494   public boolean isFunctionSelected()
495   {
496   	return currentFunc != null;
497   }
498 
getFunction()499   public GeometryFunction getFunction() {
500     return currentFunc;
501   }
502 
503   //=================================================
504   // Events
505   //=================================================
506 
removeSpatialFunctionPanelListener(SpatialFunctionPanelListener l)507   public synchronized void removeSpatialFunctionPanelListener(SpatialFunctionPanelListener l) {
508     if (spatialFunctionPanelListeners != null && spatialFunctionPanelListeners.contains(l)) {
509       Vector v = (Vector) spatialFunctionPanelListeners.clone();
510       v.removeElement(l);
511       spatialFunctionPanelListeners = v;
512     }
513   }
514 
addSpatialFunctionPanelListener(SpatialFunctionPanelListener l)515   public synchronized void addSpatialFunctionPanelListener(SpatialFunctionPanelListener l) {
516     Vector v = spatialFunctionPanelListeners == null ? new Vector(2) : (Vector) spatialFunctionPanelListeners.clone();
517     if (!v.contains(l)) {
518       v.addElement(l);
519       spatialFunctionPanelListeners = v;
520     }
521   }
522 
fireFunctionExecuted(SpatialFunctionPanelEvent e)523   protected void fireFunctionExecuted(SpatialFunctionPanelEvent e) {
524     if (spatialFunctionPanelListeners != null) {
525       Vector listeners = spatialFunctionPanelListeners;
526       int count = listeners.size();
527       for (int i = 0; i < count; i++) {
528         ((SpatialFunctionPanelListener) listeners.elementAt(i)).functionExecuted(e);
529       }
530     }
531   }
532 
533 
534 }
535 
536