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 | */
|
---|
8 | package terracer;
|
---|
9 |
|
---|
10 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
11 |
|
---|
12 | import java.awt.Color;
|
---|
13 | import java.awt.Component;
|
---|
14 | import java.awt.Container;
|
---|
15 | import java.awt.event.ActionEvent;
|
---|
16 | import java.awt.event.ActionListener;
|
---|
17 | import java.awt.event.FocusEvent;
|
---|
18 | import java.awt.event.FocusListener;
|
---|
19 | import java.awt.event.ItemEvent;
|
---|
20 | import java.awt.event.ItemListener;
|
---|
21 | import java.util.ArrayList;
|
---|
22 |
|
---|
23 | import javax.swing.JButton;
|
---|
24 | import javax.swing.JTextField;
|
---|
25 | import javax.swing.JOptionPane;
|
---|
26 |
|
---|
27 | import org.openstreetmap.josm.Main;
|
---|
28 | import org.openstreetmap.josm.data.osm.Node;
|
---|
29 | import org.openstreetmap.josm.data.osm.Way;
|
---|
30 | import org.openstreetmap.josm.data.osm.Relation;
|
---|
31 | import org.openstreetmap.josm.actions.JosmAction;
|
---|
32 | import org.openstreetmap.josm.data.osm.Node;
|
---|
33 | import 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 | */
|
---|
45 | public 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 | }
|
---|