source: other-projects/gs3-webservices-java-client/trunk/src/GS3DemoClient/org/greenstone/gs3client/QueryFormControl.java@ 26174

Last change on this file since 26174 was 26174, checked in by ak19, 12 years ago

Handling special case in search form control generation, where no controls but pure labels are specified.

File size: 19.1 KB
Line 
1/**
2 *#########################################################################
3 * QueryFormControl.java - part of the demo-client for Greenstone 3, of the
4 * Greenstone digital library suite from the New Zealand Digital Library
5 * Project at the * University of Waikato, New Zealand.
6 * <BR><BR>
7 * Copyright (C) 2008 New Zealand Digital Library Project
8 * <BR><BR>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 * <BR><BR>
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *########################################################################
19 */
20
21package org.greenstone.gs3client;
22
23import java.awt.Color;
24import java.awt.FlowLayout;
25import java.awt.GridBagLayout;
26import java.awt.GridBagConstraints;
27import java.util.HashMap;
28import java.util.Vector;
29
30import javax.swing.BorderFactory;
31import javax.swing.JCheckBox;
32import javax.swing.JComboBox;
33import javax.swing.JComponent;
34import javax.swing.JLabel;
35import javax.swing.JList;
36import javax.swing.JPanel;
37import javax.swing.JTextField;
38import javax.swing.border.Border;
39
40import org.greenstone.gs3client.data.QueryFormData;
41import org.greenstone.gs3client.data.QueryFormData.QueryFormParam;
42import org.greenstone.gsdl3.util.GSXML;
43import org.w3c.dom.Element;
44import org.w3c.dom.NodeList;
45
46/**
47 * Represents a control in a query form. This wrapper class is a subclass of
48 * QueryFormParam that presents its QueryFormParam details as an appropriate
49 * GUI item *with* label (based on the QueryFormParam's type) for display
50 * in its own JPanel.
51 * This wrapper class is a widget that knows to display this QueryFormParam --
52 * and any child &lt;param&gt;s or &lt;option&gt;s -- in its own JPanel.
53 * @author ak19
54*/
55class QueryFormControl extends QueryFormParam {
56 /** the widget that's a visual representation of this QueryFormParam */
57 protected JComponent ref;
58 /* Width in units of this form control's component
59 * Width only used when calculating layout for type=MULTI */
60 int width; // 1 to 3 units: 1 for checkboxes, 2 for combos,
61 // 3 for lists and textboxes. 0 for everything else.
62
63 /** Constructor that creates a QueryFormControl for a &lt;param&gt; element
64 * @param param - a &lt;param&gt; element in a Query Service's describe
65 * response XML message */
66 public QueryFormControl(Element param) {
67 super(param);
68
69 // work out the widths (else it remains at 0)
70 if(this.type.equals(GSXML.PARAM_TYPE_INTEGER)
71 || this.type.equals(GSXML.PARAM_TYPE_BOOLEAN))
72 {
73 width = 1;
74 } else if(this.type.equals(GSXML.PARAM_TYPE_ENUM_SINGLE)
75 || this.type.equals(GSXML.PARAM_TYPE_ENUM_MULTI))
76 {
77 width = 2;
78 } else if(this.type.equals(GSXML.PARAM_TYPE_STRING))
79 {
80 width = 3;
81 } else { // all other types of controls have 0 width
82 width = 0;
83 }
84 }
85
86 /** Constructor that creates a QueryFormControl for a &lt;param&gt; element
87 * inside a panel.
88 * @param param - a &lt;param&gt; element in a Query Service's describe
89 * response XML message
90 * @param parentPanel - the panel inside which this QueryFormControl is to
91 * display itself. */
92 public QueryFormControl(Element param, JPanel parentPanel) {
93 super(param);
94 createWidget(param); // This will set this.ref
95 // In type=multi case, ref is a panel)
96
97 // The following if statements have to be done in this order
98
99 // For all other values of this.type:
100 // Now, in both textfield and enum cases (but not the others that
101 // haven't been dealt with yet like INVISIBLE, FILE, ENUM, etc),
102 // add the newly created GUI obj (in ref) to the parentPanel.
103 // And attach a label if instructed to do so.
104 if(this.ref != null) {
105 JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT));
106 panel.add(new JLabel(this.displayItem));
107 panel.add(this.ref);
108 parentPanel.add(panel);
109 }
110
111 // this.ref is still null for MultiPanel, until now when we create it:
112 if(this.type.equals(GSXML.PARAM_TYPE_MULTI)) {
113 createMultiPanel(param, parentPanel);
114 //width = -1;
115 }
116
117 // finally let's change the colours - as long as ref exists
118 // (invisible QueryFormData have no ref Component, so check for
119 // null case)
120 if(this.ref != null) {
121 ColourCombo.changeColor(this.ref.getComponents());
122 ColourCombo.changeColor(this.ref);
123 ColourCombo.changeAncestorColor(this.ref);
124 }
125 }
126
127 /** Creates the appropriate form control for the &lt;param&gt; element,
128 * based on its type.
129 * @param param - a &lt;param&gt; element in a Query Service's describe
130 * response XML message */
131 public void createWidget(Element param)
132 {
133 if(this.type.equals(GSXML.PARAM_TYPE_INTEGER)
134 || this.type.equals(GSXML.PARAM_TYPE_STRING))
135 {
136 createTextField();
137 }
138 else if (this.type.equals(GSXML.PARAM_TYPE_BOOLEAN))
139 {
140 createBooleanField();
141 }
142 else if(this.type.equals(GSXML.PARAM_TYPE_ENUM_SINGLE)
143 || this.type.equals(GSXML.PARAM_TYPE_ENUM_MULTI))
144 {
145 createEnumListing(param); // either a combo or list
146 }
147 } // end method setUpWidget()
148
149 /** Creates check box controls for the &lt;param&gt; elements whose
150 * types are GSXML.PARAM_TYPE_BOOLEAN. */
151 public void createBooleanField() {
152 JCheckBox checkbox = new JCheckBox();
153 if(def.equals("1"))
154 checkbox.setSelected(true);
155 this.ref = checkbox;
156 }
157
158 /** Creates text fields for the &lt;param&gt; elements whose types are
159 * GSXML.PARAM_TYPE_STRING or GSXML.PARAM_TYPE_INTEGER). */
160 public void createTextField() {
161 int cols = this.type.equals(GSXML.PARAM_TYPE_INTEGER) ? 4 : 15;
162 this.ref = new JTextField(cols);
163 if(!def.equals(""))
164 ((JTextField)this.ref).setText(def);
165 }
166
167 /** Creates combo boxes/drop-downs for the &lt;param&gt; elements
168 * whose types are GSXML.PARAM_TYPE_ENUM_SINGLE and listboxes for those
169 * whose types are GSXML.PARAM_TYPE_ENUM_MULTI.
170 * @param param - a &lt;param&gt; element in a Query Service's describe
171 * response XML message */
172 public void createEnumListing(Element param)
173 {
174 NodeList nl = param.getElementsByTagName(GSXML.PARAM_OPTION_ELEM);
175 // If no <option> elements, we do nothing
176 if(nl != null && nl.getLength() > 0) {
177 subElements = new QueryFormData[nl.getLength()];
178 Vector selectedIndices = new Vector(subElements.length);
179 for(int i = 0; i < subElements.length; i++){
180 Element e = (Element)nl.item(i);
181 subElements[i] = new QueryFormData(e);
182 // create list items for each <option>
183
184 // Keep track of which options should be selected by
185 // default, by storing these indices.
186 // Don't use def.equals() but def.indexOf():
187 // i.e. select those items where this.name occurs in def,
188 // because def is a comma separated list of default values
189 if(def.indexOf(subElements[i].name) != -1)
190 selectedIndices.add(new Integer(i));
191 }
192 // Now create the widgets
193 if(this.type.equals(GSXML.PARAM_TYPE_ENUM_MULTI)) {
194 this.ref = new JList(subElements);
195 // by default, JList is set to what we want:
196 // MULTIPLE_INTERVAL_SELECTION
197 if(!def.equals("")) {
198 int[] selIndices = new int[selectedIndices.size()];
199 for(int i = 0; i < selectedIndices.size(); i++)
200 selIndices[i] =
201 ((Integer)selectedIndices.get(i)).intValue();
202
203 ((JList)this.ref).setSelectedIndices(selIndices);
204 }
205 }
206 else { // ENUM_SINGLE and BOOLEAN get a JComboBox
207 this.ref = new JComboBox(subElements);
208 if(!def.equals("")) { // for JCombobox, only 1 item default at a time
209 ((JComboBox)this.ref).setSelectedIndex(
210 ((Integer)selectedIndices.get(0)).intValue());
211 }
212 }
213 }
214 else { // no <option>s, look for a <displayItem>
215 nl = param.getElementsByTagName(GSXML.DISPLAY_TEXT_ELEM);
216 if(nl != null && nl.getLength() > 0) {
217 Element displayItem = (Element)nl.item(0);
218 this.ref = new JLabel(GSXML.getNodeText(displayItem));
219 }
220 }
221 }
222
223 /**
224 * A multi-panel is created for a *set* of controls that occur more than
225 * once.
226 * Processes &lt;param type="multi"&gt; in a Query Service's describe
227 * response XML. (I.e. the type is GSXML.PARAM_TYPE_MULTI)
228 * Complicated case: can have 'occurs' field set. And this &lt;param&gt;
229 * element can have multiple &lt;param&gt;s itself, each with the 'ignore'
230 * possibly set and each with its own &lt;option&gt; elements.
231 * @param param - a &lt;param&gt; element in a Query Service's describe
232 * response XML message
233 * @param parentPanel - the panel inside which the multipanel is to display
234 * itself.
235 */
236 public void createMultiPanel(Element param, JPanel parentPanel) {
237 // Create the widgets for MULTI at least once:
238 int num_occurs = occurs.equals("") ? 1 : Integer.parseInt(occurs);
239 NodeList nl = param.getElementsByTagName(GSXML.PARAM_ELEM);
240
241 // This panel is now a multi panel: contains multiple associated
242 // widgets each in their own panels along with their labels.
243 // All *grouped* sub <param> elements in a panel each, and these
244 // panels on top of each other.
245 GridBagLayout gbLayout = new GridBagLayout();
246 GridBagConstraints gbConstraints = new GridBagConstraints();
247 // fixed for all components
248 //gbConstraints.anchor = GridBagConstraints.NORTHWEST;
249 gbConstraints.insets = new java.awt.Insets(2, 2, 2, 2);
250 gbConstraints.ipadx = 5;
251 gbConstraints.ipady = 3;
252 gbConstraints.fill = GridBagConstraints.BOTH;
253 gbConstraints.weightx = 0;
254 gbConstraints.weighty = 0;
255 JPanel panel = new JPanel(gbLayout);
256 //new GraphPaperLayout(new Dimension(nl.getLength(), num_occurs+1)));
257 // num_occurs + 1: extra row to allow for the row of headings
258 Border lineBorder = BorderFactory.createLineBorder(Color.GRAY);
259 panel.setBorder(lineBorder);
260 this.ref = panel; // this QueryFormControl objects JComp ref variable
261
262 // For GridBagLayout calculations
263 int[] colWidths = null; // store the width of each column
264 int colPosition = 0; // the column position of the widget in the layout
265
266 // Instantiating and positioning the Query Form controls
267 if(nl != null && nl.getLength() > 0) {
268 colWidths = new int[nl.getLength()]; // to store the width of the columns
269 // For each <param> child of Element param, we create a QueryFormParam
270 // object which is stored in the subElements array
271 // We do this 'occurs' number of times (minimum 'occurs' is once)
272 subElements = new QueryFormParam[nl.getLength() * num_occurs];
273 for(int j = 1; j <= num_occurs; j++) {
274 // Now process all <param> elements and put them into this.ref panel
275 int numParams = nl.getLength();
276 for(int i = 0; i < numParams; i++){
277 Element e = (Element)nl.item(i); // get the child
278 QueryFormControl paramEl = new QueryFormControl(e);
279 subElements[(j-1) * numParams+i] = paramEl;
280
281 // GridBagLayout calculations
282 if(j == 1) { // When processing the first row, store the col widths
283 colWidths[i] = paramEl.width;
284 }
285 // Calculate the column position of the form control in the layout
286 // ColPosition of current control is the sum of the widths of all
287 // previous controls (all controls to the left of it in this row)
288 if(i > 0) {
289 // increment column position of this control by previous
290 // widget's width
291 colPosition += colWidths[i-1];
292 } else { // i == 0, reset colPosition to 0
293 colPosition = 0;
294 }
295
296 // If 'ignore' is set, check whether we should ignore the
297 // widget at this occurs-iteration of the loop
298 if(!paramEl.ignore.equals("")) {
299 int ignorePos = Integer.parseInt(paramEl.ignore);
300 if(ignorePos != (j-1)) {
301 // occurs-iteration (outer loop) when indexed from 0
302 // doesn't match ignorePos so can set up this widget
303 paramEl.createWidget(e);
304 // false means label not attached to widget
305 // j is row, i is col
306 this.addGridBagComponent(panel, paramEl.ref, gbLayout,
307 gbConstraints, colPosition, j, paramEl.width, 1);
308 }
309 // else if the outer loop iteration-index matched,
310 // ignore widget
311 }
312 else { // when 'ignore' member var not set, set up this widget
313 paramEl.createWidget(e);
314 this.addGridBagComponent(panel, paramEl.ref, gbLayout,
315 gbConstraints, colPosition, j, paramEl.width, 1);
316 }
317 } // end for on subElements
318 // add each panel of grouped param elements to this multi panel
319 // this.panel.add(singlePanel);
320 } // end for on num_occurs
321
322 // Now add a heading row: labelling each column in the multipanel:
323 // JPanel headingPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
324 // get the labels from the subelements' displayElement:
325 for(int i = 0; i < subElements.length/num_occurs; i++) {
326 // Calculate the column position of the form control in the layout
327 // ColPosition of current control is the sum of the widths of all
328 // previous controls (all controls to the left of it in this row)
329 if(i == 0) {
330 colPosition = 0;
331 } else { // i > 0
332 colPosition += colWidths[i-1];
333 }
334 JLabel colHeading = new JLabel(subElements[i].displayItem);
335 // add it to the top of this multi panel
336 this.addGridBagComponent(panel, colHeading,
337 gbLayout, gbConstraints, colPosition, 0, 1, 1);
338 // col i, row 0, size (1,1)
339 }
340
341 // Add the multi panel to the parent.
342 // First it is nested in a panel laid out with FlowLayout in order
343 // to not have the multipanel centred in an ugly manner.
344 JPanel outerPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
345 outerPanel.add(panel); // same as adding this.ref
346 parentPanel.add(outerPanel);
347 } // end if
348
349 } // end processing <param type=multi>
350
351 /**
352 * The multi-panel is laid out with a GridBagLayout in order to make
353 * the layout more attractive (so that the form controls don't have as
354 * odd sizes as before when GraphPaperLayout was used).
355 * This method sets the constraints for the given control and then adds
356 * it to the given JPanel.
357 * This method is called to add a control (or heading label) to the
358 * multi-panel using GridBagLayout.
359 * @param panel is the panel to add the control (or heading label) to.
360 * @param comp is the Query Form control or heading JLabel to be laid out.
361 * @param gbLayout is the GridBagLayout object used to lay out the panel.
362 * @param gbConstraints is the GridBagConstraints object for the panel's
363 * GridBagLayout.
364 * @param col is the column value of the top-left of the control (for
365 * setting the GridBagLayout's gridx value).
366 * @param row is the row value of the top-left of the control (for
367 * setting the GridBagLayout's gridy value).
368 * @param w is the number of columns spanned by the control (for setting
369 * the GridBagLayout's gridwidth value).
370 * @param h is the number of rows spanned by the control (for setting
371 * the GridBagLayout's gridheight value).
372 */
373 protected void addGridBagComponent(JPanel panel, JComponent comp,
374 GridBagLayout gbLayout, GridBagConstraints gbConstraints,
375 int col, int row, int w, int h)
376 {
377 // different for all components
378 gbConstraints.gridx = col;
379 gbConstraints.gridy = row;
380 gbConstraints.gridwidth = w;
381 gbConstraints.gridheight = h;
382
383 gbLayout.setConstraints(comp, gbConstraints);
384 panel.add(comp);
385 }
386
387 /** If called after the form containing these param controls has been
388 * displayed and the user has entered/selected values (i.e. after the search
389 * button to execute a query has been pressed), then this method puts
390 * the controls' associated QueryFormParam names and the user-
391 * entered/-selected values into the HashMap nameValsmap (= the argument).
392 * Note that for the GSXML.PARAM_TYPE_MULTI case - where each related set
393 * of controls may occur several times (depending on member variable
394 * 'occurs') - the value entered/selected for each control that has the
395 * same *name* (= key into the HashMap) is added into the HashMap as
396 * a comma-separated list associated with its name-key.
397 * This method requires that argument nameValsmap is a non-null HashMap.
398 * @param nameValsMap - a non-null HashMap. At the end of this method,
399 * the nameValsMap HashMap will contain mappings from query parameter field
400 * names to their user-entered values (for the field's associated
401 * form controls).
402 */
403 public void setSelectedValue(HashMap nameValsMap) {
404 // When setSelectedValue() is called on subElements
405 // that have their ignore member var set at any stage,
406 // their this.ref will be null, so can add "" for those cases.
407 if(this.ref == null) {
408 //Also the case for *ignored* ParamElementGUIs whose widgets
409 //never got instantiated (to do with member var 'ignore')
410
411 //In these cases, there is no value that the user input,
412 //so just add ""
413 if(nameValsMap.containsKey(this.name)) {
414 //just append to existing value for key:
415 String cumVal = (String)nameValsMap.get(this.name);
416 cumVal += ",";
417 nameValsMap.put(this.name, cumVal);
418 } else {
419 // widget.type=invisible, set to default
420 if(this.type.equals(GSXML.PARAM_TYPE_INVISIBLE)
421 && !this.def.equals(""))
422 nameValsMap.put(this.name, this.def);
423 else //put empty string in there for now
424 nameValsMap.put(this.name, "");
425 }
426 return; //that's it, don't go any further
427 }
428
429 // Complex type: can contain multiple child parameters/options
430 // Need all names that match to have associated values in
431 // comma-separated list
432 if(this.type.equals(GSXML.PARAM_TYPE_MULTI)) {
433 for(int i = 0; i < subElements.length; i++) {
434 ((QueryFormControl)subElements[i]).setSelectedValue(
435 nameValsMap);
436 }
437 } else if(this.type.equals(GSXML.PARAM_TYPE_ENUM_MULTI)) {
438 // JList
439 JList list = (JList)this.ref;
440 Object[] selectedParams = list.getSelectedValues();
441 for(int i = 0; i < selectedParams.length; i++) {
442 String value = ((QueryFormData)selectedParams[i]).name;
443 if(nameValsMap.containsKey(this.name))
444 //append value to existing value for the same key
445 value = (String)nameValsMap.get(this.name) + "," + value;
446 nameValsMap.put(this.name, value);
447 }
448 } else if(this.type.equals(GSXML.PARAM_TYPE_ENUM_SINGLE)) {
449 // JCombobox
450 JComboBox combo = (JComboBox)this.ref;
451 QueryFormData selectedParam = (QueryFormData)combo.getSelectedItem();
452 String value = selectedParam.name;
453 if(nameValsMap.containsKey(this.name)) // key exists, so append
454 value = (String)nameValsMap.get(this.name) + "," + value;
455 nameValsMap.put(this.name, value);
456 } else if (this.type.equals(GSXML.PARAM_TYPE_BOOLEAN)) {
457 JCheckBox checkbox = (JCheckBox)this.ref;
458 String value = "0"; //"off"
459 if(checkbox.isSelected()) //boolean is "on"
460 value = "1";
461 if(nameValsMap.containsKey(this.name)) // key exists, so append
462 value = (String)nameValsMap.get(this.name) + "," + value;
463 nameValsMap.put(this.name, value);
464 } else if(this.type.equals(GSXML.PARAM_TYPE_INTEGER)
465 || this.type.equals(GSXML.PARAM_TYPE_STRING)) {
466 // Textfield
467 JTextField field = (JTextField)this.ref;
468 String value = field.getText();
469 if(nameValsMap.containsKey(this.name)) // key exists, so append
470 value = (String)nameValsMap.get(this.name) + "," + value;
471 nameValsMap.put(this.name, value);
472 }
473 }
474}
Note: See TracBrowser for help on using the repository browser.