This is a complement to the doc How to write an intervention tool, it explains how to replace the automatic dialog box by a more specific one.
In this documentation, we will consider the example of the Diameter Height Age intervener, with two classes:
To build the dialog box:
In the same package than the intervener, create a class with same name and appending an extra “Dialog” .java. E.g. for DHAThinner → DHAThinnerDialog. You may copy the code below as a template and adapt it for your own case.
Before beginning
The code of the dialog is commented below step by step.
/* * Capsis 4 - Computer-Aided Projections of Strategies in Silviculture * * Copyright (C) 2000-2003 Francois de Coligny * * 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; either version * 2.1 of the License, or (at your option) any later version. * * 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. * * You should have received a copy of the GNU Lesser General Public License along with this library; * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA */ package capsis.extension.intervener; import java.awt.BorderLayout; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.text.NumberFormat; import java.util.Locale; import javax.swing.ButtonGroup; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.JTextField; import jeeb.lib.util.AmapDialog; import jeeb.lib.util.Check; import jeeb.lib.util.ColumnPanel; import jeeb.lib.util.MessageDialog; import jeeb.lib.util.LinePanel; import jeeb.lib.util.Translator; import capsis.commongui.command.tmp.Helper; import capsis.defaulttype.Tree; /** * This dialog box is used to set DHAThinner parameters in interactive context. * * @author F. de Coligny - march 2002 */ public class DHAThinnerDialog extends AmapDialog implements ActionListener { // 24.6.2002 - fc - bug correction (phd) : getMin () and getMax () returned int values, now they // return float. DHAThinner version passed to 1.1.
Depending on the compatibility of this extension (see the matchWith () method in the intervener class, e.g. DHAThinner), it should be written in different packages:
The class comment should explain clearly what the class does and the @author tag is required (author's name and 'month year').
The dialog class should extend AmapDialog and implement ActionListener (to manage the responses to button clicks, see below).
private NumberFormat nf; private DHAThinner thinner; private ButtonGroup group1; private JRadioButton dbh; private JRadioButton height; private JRadioButton age; private JTextField min; private JTextField max; protected JButton ok; protected JButton cancel; protected JButton help;
The instance variables (the main variables of the dialog) :
/** * Constructor */ public DHAThinnerDialog (DHAThinner thinner) { super (); this.thinner = thinner; // To show numbers in a nice way nf = NumberFormat.getInstance (Locale.ENGLISH); nf.setGroupingUsed (false); nf.setMaximumFractionDigits (3); createUI (); presetMinMax (); setTitle (Translator.swap ("DHAThinnerDialog")); setModal (true); // location is set by the AmapDialog superclass pack (); // uses component's preferredSize show (); }
The constructor should accept one parameter (at least one, more is possible): the intervener under configuration.
It first calls the constructor of the superclass (AmapDialog), then memorizes the intervener in its instance variable and prepares the dialog:
When the dialog is set visible, the code execution is suspended to the user actions: using the widgets to configure the options and clicking on the buttons will trigger actions (see below for the linking). When the dialog is set invisible again, the code of the caller will go on again.
/** * Accessor for context. */ public int getContext () { if (dbh.isSelected ()) { return DHAThinner.DBH; } else if (height.isSelected ()) { return DHAThinner.HEIGHT; } else { return DHAThinner.AGE; } } /** * Accessor for min value. */ public float getMin () { if (min.getText ().trim ().length () == 0) { return 0; } return (float) Check.doubleValue (min.getText ().trim ()); } /** * Accessor for max value. */ public float getMax () { if (max.getText ().trim ().length () == 0) { return Float.MAX_VALUE; } return (float) Check.doubleValue (max.getText ().trim ()); }
Accessors to get the user values at the end. These accessors may return directly the values they extract from the widgets without checking for error.
Checking the user entries
/** * Action on ok button. */ private void okAction () { boolean minIsEmpty = Check.isEmpty (min.getText ().trim ()); boolean maxIsEmpty = Check.isEmpty (max.getText ().trim ()); // Checks... if (minIsEmpty && maxIsEmpty) { MessageDialog.print (this, Translator.swap ("DHAThinnerDialog.someValueIsNeeded")); return; } // Age must be an int if (age.isSelected ()) { if (!minIsEmpty && !Check.isInt (min.getText ().trim ())) { MessageDialog.print (this, Translator.swap ( "DHAThinnerDialog.ageMustBeAnInteger")); return; } if (!maxIsEmpty && !Check.isInt (max.getText ().trim ())) { MessageDialog.print (this, Translator.swap ( "DHAThinnerDialog.ageMustBeAnInteger")); return; } // Dbh and height must be doubles } else { if (!minIsEmpty && !Check.isDouble (min.getText ().trim ())) { MessageDialog.print (this, Translator.swap ( "DHAThinnerDialog.bothValuesMustBeNumbers")); return; } if (!maxIsEmpty && !Check.isDouble (max.getText ().trim ())) { MessageDialog.print (this, Translator.swap ( "DHAThinnerDialog.bothValuesMustBeNumbers")); return; } } // Min must be lower than max if (!minIsEmpty && !maxIsEmpty) { if (Check.doubleValue (min.getText ().trim ()) > Check.doubleValue (max.getText ().trim ())) { MessageDialog.print (this, Translator.swap ( "DHAThinnerDialog.minMustBeLowerThanMax")); return; } } // All has been checked successfully, set the dialog invisible // and go back to caller (will check for validity and dispose the dialog) setValidDialog (true); } /** * Action on cancel button. */ private void cancelAction () { // Set the dialog invisible // and go back to caller (will check for validity and dispose the dialog) setValidDialog (false); }
These two methods are called resp. when the user hits Ok or Cancel (see below for linking buttons to methods).
okAction (): it is imperative that this method checks all the user entries before setting the dialog valid by setValid (true). To check the user JTextFields, you may use the Check class: pass its methods the String in the JTextField and ask if it is an int, a double, what is the int or double value… See Check for more details.
For example, if we assess that the min JTextField must contain a double value :
Only if all the user entries are checked correct, set the dialog valid AND invisible with setValidDialog (true).
cancelAction (): called on Cancel, sets the dialog invalid AND invisible.
/** * Someone hit a button. */ public void actionPerformed (ActionEvent evt) { if (evt.getSource () instanceof JRadioButton) { presetMinMax (); } else if (evt.getSource ().equals (ok)) { okAction (); } else if (evt.getSource ().equals (cancel)) { cancelAction (); } else if (evt.getSource ().equals (help)) { Helper.helpFor (this); } }
actionPerformed () is called each time the user clicks on a button which was registered 'this' (the reflexive reference to the dialog itself) as listener (see createUI () below).
Depending on the source of the given event, we link with some prepared methods. If some buttons are added in createUI (), some new links may be added here.
/** * Writes the available min max values in the min and max textfields as an information * for the user. */ private void presetMinMax () { if (thinner.concernedTrees == null || thinner.concernedTrees.isEmpty ()) { return; } double m = Double.MAX_VALUE; double M = -Double.MAX_VALUE; double v = 0; for (Tree t : thinner.concernedTrees) { if (dbh.isSelected ()) { v = t.getDbh (); } else if (height.isSelected ()) { v = t.getHeight (); } else if (age.isSelected ()) { v = t.getAge (); } m = Math.min (m, v); M = Math.max (M, v); } min.setText (nf.format (m)); max.setText (nf.format (M)); }
This user method updates the min and max values each time it is called (optional).
/** * Creates the dialog box user interface. */ private void createUI () { // All lines will be inserted in this main column ColumnPanel panel = new ColumnPanel (); // Choose the mode: diameter, height OR age LinePanel l1 = new LinePanel (); l1.add (new JLabel (Translator.swap ("DHAThinnerDialog.cutTreesWith") + " :")); l1.addGlue (); panel.add (l1); // Radio buttons LinePanel l10 = new LinePanel (); LinePanel l11 = new LinePanel (); LinePanel l12 = new LinePanel (); dbh = new JRadioButton (Translator.swap ("DHAThinnerDialog.dbh")); dbh.addActionListener (this); height = new JRadioButton (Translator.swap ("DHAThinnerDialog.height")); height.addActionListener (this); age = new JRadioButton (Translator.swap ("DHAThinnerDialog.age")); age.addActionListener (this); // Add the radio buttons in their group group1 = new ButtonGroup (); group1.add (dbh); group1.add (height); group1.add (age); // Choose the default selection group1.setSelected (dbh.getModel (), true); l10.add (dbh); l10.addGlue (); l11.add (height); l11.addGlue (); l12.add (age); l12.addGlue (); panel.add (l10); panel.add (l11); panel.add (l12); // Min and max fields on a single line LinePanel l21 = new LinePanel (); min = new JTextField (5); max = new JTextField (5); l21.add (new JLabel (Translator.swap ("DHAThinnerDialog.between") + " : ")); l21.add (min); l21.add (new JLabel (" " + Translator.swap ("DHAThinnerDialog.and") + " : ")); l21.add (max); l21.addStrut0 (); panel.add (l21); panel.addGlue (); // Put the main panel at the top (north) of the user interface getContentPane ().setLayout (new BorderLayout ()); getContentPane ().add (panel, BorderLayout.NORTH); // Control panel (Ok Cancel Help) LinePanel controlPanel = new LinePanel (); ok = new JButton (Translator.swap ("Shared.ok")); cancel = new JButton (Translator.swap ("Shared.cancel")); help = new JButton (Translator.swap ("Shared.help")); controlPanel.addGlue (); // adding glue first -> the buttons will be right justified controlPanel.add (ok); controlPanel.add (cancel); controlPanel.add (help); controlPanel.addStrut0 (); ok.addActionListener (this); cancel.addActionListener (this); help.addActionListener (this); // Put the control panel at the bottom (south) getContentPane ().add (controlPanel, BorderLayout.SOUTH); // Set Ok as default (see AmapDialog) setDefaultButton (ok); } }
createUI (): this method is called in the constructor before the dialog is set visible. It builds all the dialog inner user interface by creating widgets and placing them nicely.
Why are the JLabels not declared at the top like the other widgets ?
Managing the layout
To place the widgets, we use LinePanels and ColumnPanels:
Note: here, we add several lines in a single column to get the final aspect for our dialog (see image upper).
LinePanels and ColumnPanels must be 'closed' by adding a final glue or strut0 component:
Finally, we use a BorderLayout to add our panels at the NORTH and SOUTH of the dialog (EAST, WEST and CENTER are also available).
Creating the widgets
All widgets must be instanciated by 'new', taking care of their titles, names…
The widgets which must be listened to must be added a listener with w.addActionListener (this);
They must be all added in some panels to appear on the user interface.
A default button may be set with setDefaultButton (b);
Translations
All texts going to the user interface must be translated by the Translator. This includes the buttons, labels, titles and error messages.
At runtime, the Translator will replace the translation keys (e.g. “DHAThinnerDialog.dbh”) by a text according to the language of the user interface (decided at Capsis launch time, e.g. capsis -l en).
Conventions for the translation keys:
static { Translator.addBundle ("capsis.extension.intervener.DHAThinner"); }
In the thinner class (here DHAThinner), the initGUI () method must handle the new dialog box. (i) It first open the dialog. (ii) Then the code is executed within the dialog class until it is set invisible (see the dialog class source code for setValidDialog (true or false). (iii) When the dialog is set invisible, the code continues here and we check if it was either validated or canceled by the user (dlg.isValidDialog ()). (iv) If validated, we get the parameters entered in the dialog and copy them in the intervener instance variables (here context, min and max). Finally and in any case, we dispose() the dialog (destruction and memory release).
@Override public boolean initGUI () throws Exception { // Interactive start DHAThinnerDialog dlg = new DHAThinnerDialog (this); constructionCompleted = false; if (dlg.isValidDialog ()) { // valid -> ok was hit and all checks were ok try { context = dlg.getContext (); min = dlg.getMin (); max = dlg.getMax (); constructionCompleted = true; } catch (Exception e) { constructionCompleted = false; throw new Exception ("DHAThinner (): Could not get parameters in DHAThinnerDialog", e); } } dlg.dispose (); return constructionCompleted; }
/* * Capsis 4 - Computer-Aided Projections of Strategies in Silviculture * * Copyright (C) 2000-2003 Francois de Coligny * * 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; either version * 2.1 of the License, or (at your option) any later version. * * 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. * * You should have received a copy of the GNU Lesser General Public License along with this library; * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA */ package capsis.extension.intervener; import java.awt.BorderLayout; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.text.NumberFormat; import java.util.Locale; import javax.swing.ButtonGroup; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.JTextField; import jeeb.lib.util.AmapDialog; import jeeb.lib.util.Check; import jeeb.lib.util.ColumnPanel; import jeeb.lib.util.MessageDialog; import jeeb.lib.util.LinePanel; import jeeb.lib.util.Translator; import capsis.commongui.command.tmp.Helper; import capsis.defaulttype.Tree; /** * This dialog box is used to set DHAThinner parameters in interactive context. * * @author F. de Coligny - march 2002 */ public class DHAThinnerDialog extends AmapDialog implements ActionListener { // 24.6.2002 - fc - bug correction (phd) : getMin () and getMax () returned int values, now they // return float. DHAThinner version passed to 1.1. private NumberFormat nf; private DHAThinner thinner; private ButtonGroup group1; private JRadioButton dbh; private JRadioButton height; private JRadioButton age; private JTextField min; private JTextField max; protected JButton ok; protected JButton cancel; protected JButton help; /** * Constructor */ public DHAThinnerDialog (DHAThinner thinner) { super (); this.thinner = thinner; // To show numbers in a nice way nf = NumberFormat.getInstance (Locale.ENGLISH); nf.setGroupingUsed (false); nf.setMaximumFractionDigits (3); createUI (); presetMinMax (); setTitle (Translator.swap ("DHAThinnerDialog")); setModal (true); // location is set by the AmapDialog superclass pack (); // uses component's preferredSize show (); } /** * Accessor for context. */ public int getContext () { if (dbh.isSelected ()) { return DHAThinner.DBH; } else if (height.isSelected ()) { return DHAThinner.HEIGHT; } else { return DHAThinner.AGE; } } /** * Accessor for min value. */ public float getMin () { if (min.getText ().trim ().length () == 0) { return 0; } return (float) Check.doubleValue (min.getText ().trim ()); } /** * Accessor for max value. */ public float getMax () { if (max.getText ().trim ().length () == 0) { return Float.MAX_VALUE; } return (float) Check.doubleValue (max.getText ().trim ()); } /** * Action on ok button. */ private void okAction () { boolean minIsEmpty = Check.isEmpty (min.getText ().trim ()); boolean maxIsEmpty = Check.isEmpty (max.getText ().trim ()); // Checks... if (minIsEmpty && maxIsEmpty) { MessageDialog.print (this, Translator.swap ("DHAThinnerDialog.someValueIsNeeded")); return; } // Age must be an int if (age.isSelected ()) { if (!minIsEmpty && !Check.isInt (min.getText ().trim ())) { MessageDialog.print (this, Translator.swap ("DHAThinnerDialog.ageMustBeAnInteger")); return; } if (!maxIsEmpty && !Check.isInt (max.getText ().trim ())) { MessageDialog.print (this, Translator.swap ("DHAThinnerDialog.ageMustBeAnInteger")); return; } // Dbh and height must be doubles } else { if (!minIsEmpty && !Check.isDouble (min.getText ().trim ())) { MessageDialog.print (this, Translator.swap ("DHAThinnerDialog.bothValuesMustBeNumbers")); return; } if (!maxIsEmpty && !Check.isDouble (max.getText ().trim ())) { MessageDialog.print (this, Translator.swap ("DHAThinnerDialog.bothValuesMustBeNumbers")); return; } } // Min must be lower than max if (!minIsEmpty && !maxIsEmpty) { if (Check.doubleValue (min.getText ().trim ()) > Check.doubleValue (max.getText ().trim ())) { MessageDialog.print (this, Translator.swap ("DHAThinnerDialog.minMustBeLowerThanMax")); return; } } // All has been checked successfully, set the dialog invisible // and go back to caller (will check for validity and dispose the dialog) setValidDialog (true); } /** * Action on cancel button. */ private void cancelAction () { // Set the dialog invisible // and go back to caller (will check for validity and dispose the dialog) setValidDialog (false); } /** * Someone hit a button. */ public void actionPerformed (ActionEvent evt) { if (evt.getSource () instanceof JRadioButton) { presetMinMax (); } else if (evt.getSource ().equals (ok)) { okAction (); } else if (evt.getSource ().equals (cancel)) { cancelAction (); } else if (evt.getSource ().equals (help)) { Helper.helpFor (this); } } /** * Writes the available min max values in the min and max textfields as an information * for the user. */ private void presetMinMax () { if (thinner.concernedTrees == null || thinner.concernedTrees.isEmpty ()) { return; } double m = Double.MAX_VALUE; double M = -Double.MAX_VALUE; double v = 0; for (Tree t : thinner.concernedTrees) { if (dbh.isSelected ()) { v = t.getDbh (); } else if (height.isSelected ()) { v = t.getHeight (); } else if (age.isSelected ()) { v = t.getAge (); } m = Math.min (m, v); M = Math.max (M, v); } min.setText (nf.format (m)); max.setText (nf.format (M)); } /** * Creates the dialog box user interface. */ private void createUI () { // All lines will be inserted in this main column ColumnPanel panel = new ColumnPanel (); // Choose the mode: diameter, height OR age LinePanel l1 = new LinePanel (); l1.add (new JLabel (Translator.swap ("DHAThinnerDialog.cutTreesWith") + " :")); l1.addGlue (); panel.add (l1); // Radio buttons LinePanel l10 = new LinePanel (); LinePanel l11 = new LinePanel (); LinePanel l12 = new LinePanel (); dbh = new JRadioButton (Translator.swap ("DHAThinnerDialog.dbh")); dbh.addActionListener (this); height = new JRadioButton (Translator.swap ("DHAThinnerDialog.height")); height.addActionListener (this); age = new JRadioButton (Translator.swap ("DHAThinnerDialog.age")); age.addActionListener (this); // Add the radio buttons in their group group1 = new ButtonGroup (); group1.add (dbh); group1.add (height); group1.add (age); // Choose the default selection group1.setSelected (dbh.getModel (), true); l10.add (dbh); l10.addGlue (); l11.add (height); l11.addGlue (); l12.add (age); l12.addGlue (); panel.add (l10); panel.add (l11); panel.add (l12); // Min and max fields on a single line LinePanel l21 = new LinePanel (); min = new JTextField (5); max = new JTextField (5); l21.add (new JLabel (Translator.swap ("DHAThinnerDialog.between") + " : ")); l21.add (min); l21.add (new JLabel (" " + Translator.swap ("DHAThinnerDialog.and") + " : ")); l21.add (max); l21.addStrut0 (); panel.add (l21); panel.addGlue (); // Put the main panel at the top (north) of the user interface getContentPane ().setLayout (new BorderLayout ()); getContentPane ().add (panel, BorderLayout.NORTH); // Control panel (Ok Cancel Help) LinePanel controlPanel = new LinePanel (); ok = new JButton (Translator.swap ("Shared.ok")); cancel = new JButton (Translator.swap ("Shared.cancel")); help = new JButton (Translator.swap ("Shared.help")); controlPanel.addGlue (); // adding glue first -> the buttons will be right justified controlPanel.add (ok); controlPanel.add (cancel); controlPanel.add (help); controlPanel.addStrut0 (); ok.addActionListener (this); cancel.addActionListener (this); help.addActionListener (this); // Put the control panel at the bottom (south) getContentPane ().add (controlPanel, BorderLayout.SOUTH); // Set Ok as default (see AmapDialog) setDefaultButton (ok); } }