001: /*
002: * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI
003: * for visualizing and manipulating spatial features with geometry and attributes.
004: *
005: * Copyright (C) 2003 Vivid Solutions
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: *
021: * For more information, contact:
022: *
023: * Vivid Solutions
024: * Suite #1A
025: * 2328 Government Street
026: * Victoria BC V8T 5G5
027: * Canada
028: *
029: * (250)385-6040
030: * www.vividsolutions.com
031: */
032: package com.vividsolutions.jump.workbench.ui;
033:
034: import java.awt.event.ActionEvent;
035: import java.awt.event.ComponentEvent;
036: import java.awt.event.MouseAdapter;
037: import java.awt.event.MouseEvent;
038: import java.awt.image.BufferedImage;
039: import java.util.*;
040: import javax.swing.*;
041: import com.vividsolutions.jts.util.Assert;
042: import com.vividsolutions.jump.I18N;
043: import com.vividsolutions.jump.util.CollectionMap;
044: import com.vividsolutions.jump.workbench.model.Layer;
045: import com.vividsolutions.jump.workbench.model.LayerManager;
046: import com.vividsolutions.jump.workbench.plugin.EnableCheck;
047: import java.awt.*;
048:
049: /**
050: * Flexible generic dialog for prompting the user to type in several values.
051: */
052: public class MultiInputDialog extends JDialog {
053: public EnableCheck createDoubleCheck(final String fieldName) {
054: return new EnableCheck() {
055: public String check(JComponent component) {
056: try {
057: Double.parseDouble(getText(fieldName).trim());
058: return null;
059: } catch (NumberFormatException e) {
060: return "\""
061: + getText(fieldName).trim()
062: + "\" "
063: + I18N
064: .get("ui.MultiInputDialog.is-an-invalid-double")
065: + " (" + fieldName + ")";
066: }
067: }
068: };
069: }
070:
071: public EnableCheck createIntegerCheck(final String fieldName) {
072: return new EnableCheck() {
073: public String check(JComponent component) {
074: try {
075: Integer.parseInt(getText(fieldName).trim());
076: return null;
077: } catch (NumberFormatException e) {
078: return "\""
079: + getText(fieldName).trim()
080: + "\" "
081: + I18N
082: .get("ui.MultiInputDialog.is-an-invalid-integer")
083: + " (" + fieldName + ")";
084: }
085: }
086: };
087: }
088:
089: public EnableCheck createPositiveCheck(final String fieldName) {
090: return new EnableCheck() {
091: public String check(JComponent component) {
092: if (Double.parseDouble(getText(fieldName).trim()) > 0) {
093: return null;
094: }
095: return "\"" + getText(fieldName).trim() + "\" "
096: + I18N.get("ui.MultiInputDialog.must-be")
097: + " > 0 (" + fieldName + ")";
098: }
099: };
100: }
101:
102: public EnableCheck createNonNegativeCheck(final String fieldName) {
103: return new EnableCheck() {
104: public String check(JComponent component) {
105: if (Double.parseDouble(getText(fieldName).trim()) >= 0) {
106: return null;
107: }
108: return "\"" + getText(fieldName).trim() + "\" "
109: + I18N.get("ui.MultiInputDialog.must-be")
110: + " >= 0 (" + fieldName + ")";
111: }
112: };
113: }
114:
115: private final static int SIDEBAR_WIDTH = 150;
116: OKCancelPanel okCancelPanel = new OKCancelPanel();
117: GridBagLayout gridBagLayout2 = new GridBagLayout();
118: JPanel outerMainPanel = new JPanel();
119: private HashMap fieldNameToComponentMap = new HashMap();
120: private Map buttonGroupMap = new HashMap();
121:
122: private JComponent getComponent(String fieldName) {
123: return (JComponent) fieldNameToComponentMap.get(fieldName);
124: }
125:
126: public JComboBox getComboBox(String fieldName) {
127: return (JComboBox) getComponent(fieldName);
128: }
129:
130: public JCheckBox getCheckBox(String fieldName) {
131: return (JCheckBox) getComponent(fieldName);
132: }
133:
134: public JRadioButton getRadioButton(String fieldName) {
135: return (JRadioButton) getComponent(fieldName);
136: }
137:
138: public JComponent getLabel(String fieldName) {
139: return (JComponent) fieldNameToLabelMap.get(fieldName);
140: }
141:
142: private HashMap fieldNameToLabelMap = new HashMap();
143: private int rowCount = 0;
144: private LayerNameRenderer layerListCellRenderer = new LayerNameRenderer();
145: private CollectionMap fieldNameToEnableCheckListMap = new CollectionMap();
146: private BorderLayout borderLayout2 = new BorderLayout();
147: private JPanel imagePanel = new JPanel();
148: private GridBagLayout gridBagLayout3 = new GridBagLayout();
149: private JLabel imageLabel = new JLabel();
150: private JPanel mainPanel = new JPanel();
151: private GridBagLayout mainPanelGridBagLayout = new GridBagLayout();
152: private JPanel innerMainPanel = new JPanel();
153: private JPanel innerMainPanel2 = new JPanel();
154: private GridBagLayout gridBagLayout5 = new GridBagLayout();
155: private GridBagLayout gridBagLayout7 = new GridBagLayout();
156: private GridBagLayout gridBagLayout6 = new GridBagLayout();
157: private JTextArea descriptionTextArea = new JTextArea();
158: private JPanel strutPanel = new JPanel();
159: private JPanel currentMainPanel = innerMainPanel;
160: private JPanel verticalSeparatorPanel = new JPanel();
161:
162: /**
163: * @param frame the frame on which to make this dialog modal and centred
164: */
165: public MultiInputDialog(final Frame frame, String title,
166: boolean modal) {
167: super (frame, title, modal);
168: try {
169: jbInit();
170: } catch (Exception ex) {
171: ex.printStackTrace();
172: }
173: imagePanel.setVisible(false);
174: descriptionTextArea.setText("");
175: imageLabel.setText("");
176: innerMainPanel2.setVisible(false);
177: verticalSeparatorPanel.setVisible(false);
178: }
179:
180: public MultiInputDialog() {
181: this (null, "", false);
182: }
183:
184: public void setVisible(boolean visible) {
185: //Workaround for Java bug 4446522 " JTextArea.getPreferredSize()
186: //incorrect when line wrapping is used": call #pack twice [Jon Aquino]
187: pack();
188: pack();
189: GUIUtil.centreOnWindow(MultiInputDialog.this );
190: super .setVisible(visible);
191: }
192:
193: /**
194: * Gets the string value of a control
195: * @param fieldName control to read
196: * @return the string value of the control
197: * @return null if the control is not in a valid state (e.g. not selected)
198: */
199: public String getText(String fieldName) {
200: if (fieldNameToComponentMap.get(fieldName) instanceof JTextField) {
201: return ((JTextField) fieldNameToComponentMap.get(fieldName))
202: .getText();
203: }
204: if (fieldNameToComponentMap.get(fieldName) instanceof JComboBox) {
205: Object selObj = ((JComboBox) fieldNameToComponentMap
206: .get(fieldName)).getSelectedItem();
207: if (selObj == null)
208: return null;
209: return selObj.toString();
210: }
211: Assert.shouldNeverReachHere(fieldName);
212: return null;
213: }
214:
215: /**
216: * Returns selected state for checkboxes, radio buttons.
217: *
218: * @param fieldName the name of the control to test
219: * @return the selected state of the control
220: */
221: public boolean getBoolean(String fieldName) {
222: AbstractButton button = (AbstractButton) fieldNameToComponentMap
223: .get(fieldName);
224: return button.isSelected();
225: }
226:
227: public double getDouble(String fieldName) {
228: return Double.parseDouble(getText(fieldName).trim());
229: }
230:
231: public int getInteger(String fieldName) {
232: return Integer.parseInt(getText(fieldName).trim());
233: }
234:
235: public Layer getLayer(String fieldName) {
236: JComboBox comboBox = (JComboBox) fieldNameToComponentMap
237: .get(fieldName);
238: return (Layer) comboBox.getSelectedItem();
239: }
240:
241: public JTextField addTextField(String fieldName,
242: String initialValue, int approxWidthInChars,
243: EnableCheck[] enableChecks, String toolTipText) {
244: JTextField textField = new JTextField(initialValue,
245: approxWidthInChars);
246: addRow(fieldName, new JLabel(fieldName), textField,
247: enableChecks, toolTipText);
248: return textField;
249: }
250:
251: public JComboBox addComboBox(String fieldName, Object selectedItem,
252: Collection items, String toolTipText) {
253: JComboBox comboBox = new JComboBox(new Vector(items));
254: comboBox.setSelectedItem(selectedItem);
255: addRow(fieldName, new JLabel(fieldName), comboBox, null,
256: toolTipText);
257: return comboBox;
258: }
259:
260: public JLabel addLabel(String text) {
261: //Take advantage of #addRow's special rule for JLabels: they span all
262: //the columns of the GridBagLayout. [Jon Aquino]
263: JLabel lbl = new JLabel(text);
264: addRow(lbl);
265: return lbl;
266: }
267:
268: public JButton addButton(String text) {
269: //Take advantage of #addRow's special rule for JLabels: they span all
270: //the columns of the GridBagLayout. [Jon Aquino]
271: JButton button = new JButton(text);
272: addRow(button);
273: return button;
274: }
275:
276: public void addRow(JComponent c) {
277: addRow("DUMMY", new JLabel(""), c, null, null);
278: }
279:
280: public void addSeparator() {
281: JPanel separator = new JPanel();
282: separator.setBackground(Color.black);
283: separator.setPreferredSize(new Dimension(1, 1));
284: addRow(separator);
285: }
286:
287: private JTextField addNumericField(String fieldName,
288: String initialValue, int approxWidthInChars,
289: EnableCheck[] enableChecks, String toolTipText) {
290: JTextField fld = addTextField(fieldName, initialValue,
291: approxWidthInChars, enableChecks, toolTipText);
292: fld.setHorizontalAlignment(JTextField.RIGHT);
293: return fld;
294: }
295:
296: public JTextField addIntegerField(String fieldName,
297: int initialValue, int approxWidthInChars, String toolTipText) {
298: return addNumericField(fieldName, String.valueOf(initialValue),
299: approxWidthInChars,
300: new EnableCheck[] { createIntegerCheck(fieldName) },
301: toolTipText);
302: }
303:
304: public JTextField addPositiveIntegerField(String fieldName,
305: int initialValue, int approxWidthInChars) {
306: return addNumericField(fieldName, String.valueOf(initialValue),
307: approxWidthInChars, new EnableCheck[] {
308: createIntegerCheck(fieldName),
309: createPositiveCheck(fieldName) }, null);
310: }
311:
312: public JTextField addDoubleField(String fieldName,
313: double initialValue, int approxWidthInChars) {
314: return addNumericField(fieldName, String.valueOf(initialValue),
315: approxWidthInChars,
316: new EnableCheck[] { createDoubleCheck(fieldName) },
317: null);
318: }
319:
320: public JTextField addDoubleField(String fieldName,
321: double initialValue, int approxWidthInChars,
322: String toolTipText) {
323: return addNumericField(fieldName, String.valueOf(initialValue),
324: approxWidthInChars,
325: new EnableCheck[] { createDoubleCheck(fieldName) },
326: toolTipText);
327: }
328:
329: public JTextField addPositiveDoubleField(String fieldName,
330: double initialValue, int approxWidthInChars) {
331: return addNumericField(fieldName, String.valueOf(initialValue),
332: approxWidthInChars, new EnableCheck[] {
333: createDoubleCheck(fieldName),
334: createPositiveCheck(fieldName) }, null);
335: }
336:
337: public JTextField addNonNegativeDoubleField(String fieldName,
338: double initialValue, int approxWidthInChars) {
339: return addNumericField(fieldName, String.valueOf(initialValue),
340: approxWidthInChars, new EnableCheck[] {
341: createDoubleCheck(fieldName),
342: createNonNegativeCheck(fieldName) }, null);
343: }
344:
345: public static void main(String[] args) {
346: MultiInputDialog d = new MultiInputDialog(null, "Title!", true);
347: d.addLabel("Yay!");
348: d.addLayerComboBox("LayerField", null, "ToolTip",
349: new LayerManager());
350: d.setVisible(true);
351: System.out.println(d.getLayer("LayerField"));
352: System.exit(0);
353: }
354:
355: public JComboBox addLayerComboBox(String fieldName,
356: Layer initialValue, LayerManager layerManager) {
357: return addLayerComboBox(fieldName, initialValue, null,
358: layerManager);
359: }
360:
361: public JComboBox addLayerComboBox(String fieldName,
362: Layer initialValue, String toolTipText,
363: LayerManager layerManager) {
364: return addLayerComboBox(fieldName, initialValue, toolTipText,
365: layerManager.getLayers());
366: }
367:
368: public JComboBox addEditableLayerComboBox(String fieldName,
369: Layer initialValue, String toolTipText,
370: LayerManager layerManager) {
371: return addLayerComboBox(fieldName, initialValue, toolTipText,
372: layerManager.getEditableLayers());
373: }
374:
375: public JComboBox addLayerComboBox(String fieldName,
376: Layer initialValue, String toolTipText, Collection layers) {
377: addComboBox(fieldName, initialValue, layers, toolTipText);
378: getComboBox(fieldName).setRenderer(layerListCellRenderer);
379: return getComboBox(fieldName);
380: }
381:
382: public JCheckBox addCheckBox(String fieldName, boolean initialValue) {
383: return addCheckBox(fieldName, initialValue, null);
384: }
385:
386: public JCheckBox addCheckBox(String fieldName,
387: boolean initialValue, String toolTipText) {
388: JCheckBox checkBox = new JCheckBox(fieldName, initialValue);
389: addRow(fieldName, new JLabel(""), checkBox, null, toolTipText);
390: return checkBox;
391: }
392:
393: public JRadioButton addRadioButton(String fieldName,
394: String buttonGroupName, boolean initialValue,
395: String toolTipText) {
396: JRadioButton radioButton = new JRadioButton(fieldName,
397: initialValue);
398: addRow(fieldName, new JLabel(""), radioButton, null,
399: toolTipText);
400:
401: // add to button group, if specified (and create one if it doesn't exist)
402: if (buttonGroupName != null) {
403: ButtonGroup group = (ButtonGroup) buttonGroupMap
404: .get(buttonGroupName);
405: if (group == null) {
406: group = new ButtonGroup();
407: buttonGroupMap.put(buttonGroupName, group);
408: }
409: group.add(radioButton);
410: }
411:
412: return radioButton;
413: }
414:
415: public void setSideBarImage(Icon icon) {
416: //Add imageLabel only if #setSideBarImage is called. Otherwise the margin
417: //above the description will be too tall. [Jon Aquino]
418: imagePanel.add(imageLabel, new GridBagConstraints(0, 0, 1, 1,
419: 0.0, 0.0, GridBagConstraints.NORTHWEST,
420: GridBagConstraints.HORIZONTAL,
421: new Insets(10, 10, 0, 10), 0, 0));
422: imagePanel.setVisible(true);
423: imageLabel.setIcon(icon);
424: }
425:
426: public void setSideBarDescription(String description) {
427: imagePanel.setVisible(true);
428: descriptionTextArea.setText(description);
429: }
430:
431: public boolean wasOKPressed() {
432: return okCancelPanel.wasOKPressed();
433: }
434:
435: //Experience suggests that one should avoid using weights when using the
436: //GridBagLayout. I find that nonzero weights can cause layout bugs that are
437: //hard to track down. [Jon Aquino]
438: void jbInit() throws Exception {
439: verticalSeparatorPanel.setBackground(Color.black);
440: imageLabel.setText(I18N
441: .get("ui.MultiInputDialog.images-goes-here"));
442: descriptionTextArea.setOpaque(false);
443: okCancelPanel
444: .addActionListener(new java.awt.event.ActionListener() {
445: public void actionPerformed(ActionEvent e) {
446: okCancelPanel_actionPerformed(e);
447: }
448: });
449: this
450: .addComponentListener(new java.awt.event.ComponentAdapter() {
451: public void componentShown(ComponentEvent e) {
452: this _componentShown(e);
453: }
454: });
455: outerMainPanel.setLayout(gridBagLayout6);
456: outerMainPanel.setAlignmentX((float) 0.7);
457: this .setResizable(true);
458: this .getContentPane().setLayout(borderLayout2);
459: imagePanel.setBorder(BorderFactory.createEtchedBorder());
460: imagePanel.setLayout(gridBagLayout3);
461: mainPanel.setLayout(mainPanelGridBagLayout);
462: innerMainPanel.setLayout(gridBagLayout5);
463: innerMainPanel2.setLayout(gridBagLayout7);
464: descriptionTextArea.setEnabled(false);
465: descriptionTextArea.setEditable(false);
466: descriptionTextArea.setText(I18N
467: .get("ui.MultiInputDialog.description-goes-here"));
468: descriptionTextArea.setLineWrap(true);
469: descriptionTextArea.setWrapStyleWord(true);
470: strutPanel.setMaximumSize(new Dimension(SIDEBAR_WIDTH, 1));
471: strutPanel.setMinimumSize(new Dimension(SIDEBAR_WIDTH, 1));
472: strutPanel.setPreferredSize(new Dimension(SIDEBAR_WIDTH, 1));
473: verticalSeparatorPanel.setPreferredSize(new Dimension(1, 1));
474: this .getContentPane().add(okCancelPanel, BorderLayout.SOUTH);
475: this .getContentPane().add(outerMainPanel, BorderLayout.CENTER);
476: imagePanel.add(descriptionTextArea, new GridBagConstraints(0,
477: 1, 1, 1, 0.0, 1.0, GridBagConstraints.NORTHWEST,
478: GridBagConstraints.BOTH, new Insets(10, 10, 10, 10), 0,
479: 0));
480: imagePanel.add(strutPanel, new GridBagConstraints(0, 3, 1, 1,
481: 0.0, 0.0, GridBagConstraints.CENTER,
482: GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0),
483: 0, 0));
484: outerMainPanel.add(mainPanel, new GridBagConstraints(2, 0, 1,
485: 1, 1.0, 1.0, GridBagConstraints.NORTHWEST,
486: GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
487: mainPanel.add(innerMainPanel, new GridBagConstraints(1, 0, 1,
488: 2, 1.0, 1.0, GridBagConstraints.NORTHWEST,
489: GridBagConstraints.HORIZONTAL, new Insets(10, 10, 10,
490: 10), 0, 0));
491: mainPanel.add(innerMainPanel2, new GridBagConstraints(3, 0, 1,
492: 1, 1.0, 1.0, GridBagConstraints.NORTHWEST,
493: GridBagConstraints.HORIZONTAL, new Insets(10, 10, 10,
494: 10), 0, 0));
495: mainPanel.add(verticalSeparatorPanel, new GridBagConstraints(2,
496: 0, 1, 1, 0.0, 1.0, GridBagConstraints.CENTER,
497: GridBagConstraints.VERTICAL, new Insets(0, 0, 0, 0), 0,
498: 0));
499: outerMainPanel.add(imagePanel, new GridBagConstraints(1, 0, 1,
500: 1, 0.0, 0.0, GridBagConstraints.NORTHWEST,
501: GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
502: descriptionTextArea.setFont(imageLabel.getFont());
503: descriptionTextArea.setDisabledTextColor(imageLabel
504: .getForeground());
505: }
506:
507: /**
508: * If the dialog contains a single tabbed panel, it looks better to have a 0
509: * inset.
510: */
511: public void setInset(int inset) {
512: setInset(inset, innerMainPanel);
513: setInset(inset, innerMainPanel2);
514: }
515:
516: private void setInset(int inset, JComponent component) {
517: GridBagLayout layout = (GridBagLayout) component.getParent()
518: .getLayout();
519: GridBagConstraints constraints = layout
520: .getConstraints(component);
521: constraints.insets = new Insets(inset, inset, inset, inset);
522: layout.setConstraints(component, constraints);
523: }
524:
525: void okCancelPanel_actionPerformed(ActionEvent e) {
526: if (!okCancelPanel.wasOKPressed() || isInputValid()) {
527: setVisible(false);
528: return;
529: }
530: reportValidationError(firstValidationErrorMessage());
531: }
532:
533: void this _componentShown(ComponentEvent e) {
534: okCancelPanel.setOKPressed(false);
535: }
536:
537: private boolean isInputValid() {
538: return firstValidationErrorMessage() == null;
539: }
540:
541: private void reportValidationError(String errorMessage) {
542: JOptionPane.showMessageDialog(this , errorMessage, "JUMP",
543: JOptionPane.ERROR_MESSAGE);
544: }
545:
546: private String firstValidationErrorMessage() {
547: for (Iterator i = fieldNameToEnableCheckListMap.keySet()
548: .iterator(); i.hasNext();) {
549: String fieldName = (String) i.next();
550: for (Iterator j = fieldNameToEnableCheckListMap.getItems(
551: fieldName).iterator(); j.hasNext();) {
552: EnableCheck enableCheck = (EnableCheck) j.next();
553: String message = enableCheck.check(null);
554: if (message != null) {
555: return message;
556: }
557: }
558: }
559: return null;
560: }
561:
562: /**
563: * This method can be called once only.
564: */
565: public void startNewColumn() {
566: if (innerMainPanel2.isVisible()) {
567: Assert
568: .shouldNeverReachHere("#startNewColumn can be called once only");
569: }
570: currentMainPanel = innerMainPanel2;
571: innerMainPanel2.setVisible(true);
572: verticalSeparatorPanel.setVisible(true);
573: }
574:
575: public void addRow(String fieldName, JComponent label,
576: JComponent component, EnableCheck[] enableChecks,
577: String toolTipText) {
578: if (toolTipText != null) {
579: label.setToolTipText(toolTipText);
580: component.setToolTipText(toolTipText);
581: }
582: fieldNameToLabelMap.put(fieldName, label);
583: fieldNameToComponentMap.put(fieldName, component);
584: if (enableChecks != null) {
585: addEnableChecks(fieldName, Arrays.asList(enableChecks));
586: }
587: int componentX;
588: int componentWidth;
589: int labelX;
590: int labelWidth;
591: if (component instanceof JCheckBox
592: || component instanceof JRadioButton
593: || component instanceof JLabel
594: || component instanceof JPanel) {
595: componentX = 1;
596: componentWidth = 3;
597: labelX = 4;
598: labelWidth = 1;
599: } else {
600: labelX = 1;
601: labelWidth = 1;
602: componentX = 2;
603: componentWidth = 1;
604: }
605: currentMainPanel.add(label, new GridBagConstraints(labelX,
606: rowCount, labelWidth, 1, 0.0, 0.0,
607: GridBagConstraints.WEST, GridBagConstraints.NONE,
608: new Insets(0, 0, 5, 10), 0, 0));
609: //HORIZONTAL especially needed by separator. [Jon Aquino]
610: currentMainPanel
611: .add(
612: component,
613: new GridBagConstraints(
614: componentX,
615: rowCount,
616: componentWidth,
617: 1,
618: 0,
619: 0.0,
620: GridBagConstraints.WEST,
621: component instanceof JPanel ? GridBagConstraints.HORIZONTAL
622: : GridBagConstraints.NONE,
623: new Insets(0, 0, 5, 0), 0, 0));
624: rowCount++;
625: }
626:
627: public void addEnableChecks(String fieldName,
628: Collection enableChecks) {
629: fieldNameToEnableCheckListMap.addItems(fieldName, enableChecks);
630: }
631:
632: public void indentLabel(String comboBoxFieldName) {
633: getLabel(comboBoxFieldName).setBorder(
634: BorderFactory.createMatteBorder(0,
635: (int) new JCheckBox().getPreferredSize()
636: .getWidth(), 0, 0, getLabel(
637: comboBoxFieldName).getBackground()));
638: }
639: }
|