source: osm/applications/editors/josm/plugins/terracer/src/terracer/HouseNumberInputHandler.java@ 24956

Last change on this file since 24956 was 24956, checked in by frederik, 14 years ago

fix bug that would produce xml in the addr:street field when you hit enter in the auto-completion streetname box.

File size: 14.8 KB
Line 
1/**
2 * Terracer: A JOSM Plugin for terraced houses.
3 *
4 * Copyright 2009 CloudMade Ltd.
5 *
6 * Released under the GPLv2, see LICENSE file for details.
7 */
8package terracer;
9
10import static org.openstreetmap.josm.tools.I18n.tr;
11
12import java.awt.Color;
13import java.awt.Component;
14import java.awt.Container;
15import java.awt.event.ActionEvent;
16import java.awt.event.ActionListener;
17import java.awt.event.FocusEvent;
18import java.awt.event.FocusListener;
19import java.awt.event.ItemEvent;
20import java.awt.event.ItemListener;
21import java.util.ArrayList;
22
23import javax.swing.JButton;
24import javax.swing.JTextField;
25import javax.swing.JOptionPane;
26
27import org.openstreetmap.josm.Main;
28import org.openstreetmap.josm.data.osm.Node;
29import org.openstreetmap.josm.data.osm.Way;
30import org.openstreetmap.josm.data.osm.Relation;
31import org.openstreetmap.josm.actions.JosmAction;
32import org.openstreetmap.josm.data.osm.Node;
33import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionListItem;
34
35/**
36 * The Class HouseNumberInputHandler contains all the logic
37 * behind the house number input dialog.
38 *
39 * From a refactoring viewpoint, this class is indeed more interested in the fields
40 * of the HouseNumberInputDialog. This is desired design, as the HouseNumberInputDialog
41 * is already cluttered with auto-generated layout code.
42 *
43 * @author casualwalker
44 */
45public class HouseNumberInputHandler extends JosmAction implements ActionListener, FocusListener, ItemListener {
46 private TerracerAction terracerAction;
47 private Way outline, street;
48 private String streetName;
49 private Node init;
50 private Relation associatedStreet;
51 private ArrayList<Node> housenumbers;
52 public HouseNumberInputDialog dialog;
53
54 /**
55 * Instantiates a new house number input handler.
56 *
57 * @param terracerAction the terracer action
58 * @param outline the closed, quadrilateral way to terrace.
59 * @param init The node that hints at which side to start the numbering
60 * @param street the street, the buildings belong to (may be null)
61 * @param streetName the name of the street, derived from either the street line or
62 * the house numbers which are guaranteed to have the same name
63 * attached (may be null)
64 * @param associatedStreet a relation where we can add the houses (may be null)
65 * @param housenumbers a list of house number nodes in this outline (may be empty)
66 * @param title the title
67 */
68 public HouseNumberInputHandler(final TerracerAction terracerAction,
69 final Way outline, final Node init, final Way street, final String streetName,
70 final Relation associatedStreet,
71 final ArrayList<Node> housenumbers, final String title) {
72 this.terracerAction = terracerAction;
73 this.outline = outline;
74 this.init = init;
75 this.street = street;
76 this.streetName = streetName;
77 this.associatedStreet = associatedStreet;
78 this.housenumbers = housenumbers;
79
80 // This dialog is started modal
81 this.dialog = new HouseNumberInputDialog(this, street, streetName,
82 associatedStreet != null, housenumbers);
83
84 // We're done
85 }
86
87 /**
88 * Find a button with a certain caption.
89 * Loops recursively through all objects to find all buttons.
90 * Function returns on the first match.
91 *
92 * @param root A container object that is recursively searched for other containers or buttons
93 * @param caption The caption of the button that is being searched
94 *
95 * @return The first button that matches the caption or null if not found
96 */
97 private static JButton getButton(Container root, String caption) {
98 Component children[] = root.getComponents();
99 for (Component child : children) {
100 JButton b;
101 if (child instanceof JButton) {
102 b = (JButton) child;
103 if (caption.equals(b.getText())) return b;
104 } else if (child instanceof Container) {
105 b = getButton((Container) child, caption);
106 if (b != null) return b;
107 }
108 }
109 return null;
110 }
111
112 /**
113 * Validate the current input fields.
114 * When the validation fails, a red message is
115 * displayed and the OK button is disabled.
116 *
117 * Should be triggered each time the input changes.
118 */
119 private boolean validateInput() {
120 boolean isOk = true;
121 StringBuffer message = new StringBuffer();
122
123 isOk = isOk && checkNumberOrder(message);
124 isOk = isOk && checkSegmentsFromHousenumber(message);
125 isOk = isOk && checkSegments(message);
126
127 // Allow non numeric characters for the low number as long as there is
128 // no high number of the segmentcount is 1
129 if (dialog.hi.getText().length() > 0 | segments() > 1) {
130 isOk = isOk
131 && checkNumberStringField(dialog.lo, tr("Lowest number"),
132 message);
133 }
134 isOk = isOk
135 && checkNumberStringField(dialog.hi, tr("Highest number"),
136 message);
137 isOk = isOk
138 && checkNumberStringField(dialog.segments, tr("Segments"),
139 message);
140
141 if (isOk) {
142 JButton okButton = getButton(dialog, "OK");
143 if (okButton != null)
144 okButton.setEnabled(true);
145
146 // For some reason the messageLabel doesn't want to show up
147 dialog.messageLabel.setForeground(Color.black);
148 dialog.messageLabel.setText(tr(HouseNumberInputDialog.DEFAULT_MESSAGE));
149 return true;
150 } else {
151 JButton okButton = getButton(dialog, "OK");
152 if (okButton != null)
153 okButton.setEnabled(false);
154
155 // For some reason the messageLabel doesn't want to show up, so a
156 // MessageDialog is shown instead. Someone more knowledgeable might fix this.
157 dialog.messageLabel.setForeground(Color.red);
158 dialog.messageLabel.setText(message.toString());
159 // JOptionPane.showMessageDialog(null, message.toString(),
160 // tr("Error"), JOptionPane.ERROR_MESSAGE);
161
162 return false;
163 }
164 }
165
166 /**
167 * Checks, if the lowest house number is indeed lower than the
168 * highest house number.
169 * This check applies only, if the house number fields are used at all.
170 *
171 * @param message the message
172 *
173 * @return true, if successful
174 */
175 private boolean checkNumberOrder(final StringBuffer message) {
176 if (numberFrom() != null && numberTo() != null) {
177 if (numberFrom().intValue() > numberTo().intValue()) {
178 appendMessageNewLine(message);
179 message.append(tr("Lowest housenumber cannot be higher than highest housenumber"));
180 return false;
181 }
182 }
183 return true;
184 }
185
186 /**
187 * Obtain the number segments from the house number fields and check,
188 * if they are valid.
189 *
190 * Also disables the segments field, if the house numbers contain
191 * valid information.
192 *
193 * @param message the message
194 *
195 * @return true, if successful
196 */
197 private boolean checkSegmentsFromHousenumber(final StringBuffer message) {
198 if (!dialog.numbers.isVisible()) {
199 dialog.segments.setEditable(true);
200
201 if (numberFrom() != null && numberTo() != null) {
202 int segments = numberTo().intValue() - numberFrom().intValue();
203
204 if (segments % stepSize() != 0) {
205 appendMessageNewLine(message);
206 message
207 .append(tr("Housenumbers do not match odd/even setting"));
208 return false;
209 }
210
211 int steps = segments / stepSize();
212 steps++; // difference 0 means 1 building, see
213 // TerracerActon.terraceBuilding
214 dialog.segments.setText(String.valueOf(steps));
215 dialog.segments.setEditable(false);
216
217 }
218 }
219 return true;
220 }
221
222 /**
223 * Check the number of segments.
224 * It must be a number and greater than 1.
225 *
226 * @param message the message
227 *
228 * @return true, if successful
229 */
230 private boolean checkSegments(final StringBuffer message) {
231 if (segments() == null || segments().intValue() < 1) {
232 appendMessageNewLine(message);
233 message.append(tr("Segment must be a number greater 1"));
234 return false;
235
236 }
237 return true;
238 }
239
240 /**
241 * Check, if a string field contains a positive integer.
242 *
243 * @param field the field
244 * @param label the label
245 * @param message the message
246 *
247 * @return true, if successful
248 */
249 private boolean checkNumberStringField(final JTextField field,
250 final String label, final StringBuffer message) {
251 final String content = field.getText();
252 if (content != null && content.length() != 0) {
253 try {
254 int i = Integer.parseInt(content);
255 if (i < 0) {
256 appendMessageNewLine(message);
257 message.append(tr("{0} must be greater than 0", label));
258 return false;
259 }
260 } catch (NumberFormatException e) {
261 appendMessageNewLine(message);
262 message.append(tr("{0} is not a number", label));
263 return false;
264 }
265
266 }
267 return true;
268 }
269
270 /**
271 * Append a new line to the message, if the message is not empty.
272 *
273 * @param message the message
274 */
275 private void appendMessageNewLine(final StringBuffer message) {
276 if (message.length() > 0) {
277 message.append("\n");
278 }
279 }
280
281 /*
282 * (non-Javadoc)
283 *
284 * @see java.awt.event.ItemListener#itemStateChanged(java.awt.event.ItemEvent)
285 * Called when the user selects from a pulldown selection
286 */
287 public void itemStateChanged(ItemEvent e) {
288 validateInput();
289 }
290
291 /*
292 * (non-Javadoc)
293 *
294 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
295 */
296 public void actionPerformed(final ActionEvent e) {
297 // OK or Cancel button-actions
298 if (e.getSource() instanceof JButton) {
299 JButton button = (JButton) e.getSource();
300 if (tr("OK").equals(button.getActionCommand()) & button.isEnabled()) {
301 if (validateInput()) {
302 saveValues();
303
304 terracerAction.terraceBuilding(
305 outline,
306 init,
307 street,
308 associatedStreet,
309 segments(),
310 dialog.lo.getText(),
311 dialog.hi.getText(),
312 stepSize(),
313 housenumbers,
314 streetName(),
315 doHandleRelation(),
316 doDeleteOutline());
317
318 this.dialog.dispose();
319 }
320 } else if ("Cancel".equals(button.getActionCommand())) {
321 this.dialog.dispose();
322 }
323 } else {
324 // Anything else is a change in the input (we don't get here though)
325 validateInput();
326 }
327 }
328
329 /**
330 * Calculate the step size between two house numbers,
331 * based on the interpolation setting.
332 *
333 * @return the stepSize (1 for all, 2 for odd /even)
334 */
335 public Integer stepSize() {
336 return (dialog.interpolation.getSelectedItem().equals(tr("All"))) ? 1
337 : 2;
338 }
339
340 /**
341 * Gets the number of segments, if set.
342 *
343 * @return the number of segments or null, if not set / invalid.
344 */
345 public Integer segments() {
346 try {
347 return Integer.parseInt(dialog.segments.getText());
348 } catch (NumberFormatException ex) {
349 return null;
350 }
351 }
352
353 /**
354 * Gets the lowest house number.
355 *
356 * @return the number of lowest house number or null, if not set / invalid.
357 */
358 public Integer numberFrom() {
359 try {
360 return Integer.parseInt(dialog.lo.getText());
361 } catch (NumberFormatException ex) {
362 return null;
363 }
364 }
365
366 /**
367 * Gets the highest house number.
368 *
369 * @return the number of highest house number or null, if not set / invalid.
370 */
371 public Integer numberTo() {
372 try {
373 return Integer.parseInt(dialog.hi.getText());
374 } catch (NumberFormatException ex) {
375 return null;
376 }
377 }
378
379 /**
380 * Gets the street name.
381 *
382 * @return the street name or null, if not set / invalid.
383 */
384 public String streetName() {
385 if (streetName != null)
386 return streetName;
387
388 Object selected = dialog.streetComboBox.getSelectedItem();
389 if (selected == null) {
390 return null;
391 } else {
392 String name;
393 if (selected instanceof AutoCompletionListItem)
394 {
395 name = ((AutoCompletionListItem)selected).getValue();
396 }
397 else
398 {
399 name = selected.toString();
400 }
401
402 if (name.length() == 0) {
403 return null;
404 } else {
405 return name;
406 }
407 }
408 }
409
410 /**
411 * Whether the user likes to create a relation or add to
412 * an existing one.
413 */
414 public boolean doHandleRelation() {
415 if (this.dialog == null) {
416 JOptionPane.showMessageDialog(null, "dialog", "alert", JOptionPane.ERROR_MESSAGE);
417 }
418 if (this.dialog.handleRelationCheckBox == null) {
419 JOptionPane.showMessageDialog(null, "checkbox", "alert", JOptionPane.ERROR_MESSAGE);
420 return true;
421 } else {
422 return this.dialog.handleRelationCheckBox.isSelected();
423 }
424 }
425
426 /**
427 * Whether the user likes to delete the outline way.
428 */
429 public boolean doDeleteOutline() {
430 return dialog.deleteOutlineCheckBox.isSelected();
431 }
432
433 /*
434 * (non-Javadoc)
435 *
436 * @see java.awt.event.FocusListener#focusGained(java.awt.event.FocusEvent)
437 */
438 public void focusGained(FocusEvent e) {
439 // Empty, but placeholder is required
440 }
441
442 /*
443 * (non-Javadoc)
444 *
445 * @see java.awt.event.FocusListener#focusLost(java.awt.event.FocusEvent)
446 */
447 public void focusLost(FocusEvent e) {
448 if (e.getOppositeComponent() == null)
449 return;
450
451 validateInput();
452 }
453
454 /**
455 * Saves settings.
456 */
457 public void saveValues() {
458 Main.pref.put(HouseNumberInputDialog.HANDLE_RELATION, doHandleRelation());
459 Main.pref.put(HouseNumberInputDialog.DELETE_OUTLINE, doDeleteOutline());
460 Main.pref.put(HouseNumberInputDialog.INTERPOLATION, stepSize().toString());
461 }
462}
Note: See TracBrowser for help on using the repository browser.