source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/changeset/query/AdvancedChangesetQueryPanel.java@ 6340

Last change on this file since 6340 was 6340, checked in by Don-vip, 10 years ago

refactor of some GUI/widgets classes (impacts some plugins):

  • gui.BookmarkList moves to gui.download as it is only meant to be used by gui.download.BookmarkSelection
  • tools.UrlLabel moves to gui.widgets
  • gui.JMultilineLabel, gui.MultiplitLayout, gui.MultiSplitPane move to gui.widgets
  • Property svn:eol-style set to native
File size: 46.3 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs.changeset.query;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.BorderLayout;
7import java.awt.Color;
8import java.awt.GridBagConstraints;
9import java.awt.GridBagLayout;
10import java.awt.Insets;
11import java.awt.event.ItemEvent;
12import java.awt.event.ItemListener;
13import java.text.DateFormat;
14import java.text.ParseException;
15import java.util.Date;
16import java.util.GregorianCalendar;
17import java.util.Locale;
18
19import javax.swing.BorderFactory;
20import javax.swing.ButtonGroup;
21import javax.swing.JCheckBox;
22import javax.swing.JLabel;
23import javax.swing.JOptionPane;
24import javax.swing.JPanel;
25import javax.swing.JRadioButton;
26import javax.swing.JScrollPane;
27import javax.swing.text.JTextComponent;
28
29import org.openstreetmap.josm.Main;
30import org.openstreetmap.josm.gui.HelpAwareOptionPane;
31import org.openstreetmap.josm.gui.JosmUserIdentityManager;
32import org.openstreetmap.josm.gui.help.HelpUtil;
33import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator;
34import org.openstreetmap.josm.gui.widgets.BoundingBoxSelectionPanel;
35import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
36import org.openstreetmap.josm.gui.widgets.SelectAllOnFocusGainedDecorator;
37import org.openstreetmap.josm.gui.widgets.VerticallyScrollablePanel;
38import org.openstreetmap.josm.io.ChangesetQuery;
39import org.openstreetmap.josm.tools.CheckParameterUtil;
40import org.openstreetmap.josm.gui.widgets.JosmTextField;
41
42
43/**
44 * This panel allows to specify a changeset query
45 *
46 */
47public class AdvancedChangesetQueryPanel extends JPanel {
48
49 private JCheckBox cbUserRestriction;
50 private JCheckBox cbOpenAndCloseRestrictions;
51 private JCheckBox cbTimeRestrictions;
52 private JCheckBox cbBoundingBoxRestriction;
53 private UserRestrictionPanel pnlUserRestriction;
54 private OpenAndCloseStateRestrictionPanel pnlOpenAndCloseRestriction;
55 private TimeRestrictionPanel pnlTimeRestriction;
56 private BBoxRestrictionPanel pnlBoundingBoxRestriction;
57
58 protected JPanel buildQueryPanel() {
59 ItemListener stateChangeHandler = new RestrictionGroupStateChangeHandler();
60 JPanel pnl = new VerticallyScrollablePanel();
61 pnl.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
62 pnl.setLayout(new GridBagLayout());
63 GridBagConstraints gc = new GridBagConstraints();
64
65 // -- select changesets by a specific user
66 //
67 gc.anchor = GridBagConstraints.NORTHWEST;
68 gc.weightx = 0.0;
69 gc.fill = GridBagConstraints.HORIZONTAL;
70 pnl.add(cbUserRestriction = new JCheckBox(), gc);
71 cbUserRestriction.addItemListener(stateChangeHandler);
72
73 gc.gridx = 1;
74 gc.weightx = 1.0;
75 pnl.add(new JMultilineLabel(tr("Select changesets owned by specific users")),gc);
76
77 gc.gridy = 1;
78 gc.gridx = 1;
79 gc.weightx = 1.0;
80 pnl.add(pnlUserRestriction = new UserRestrictionPanel(), gc);
81
82 // -- restricting the query to open and closed changesets
83 //
84 gc.gridy = 2;
85 gc.gridx = 0;
86 gc.anchor = GridBagConstraints.NORTHWEST;
87 gc.weightx = 0.0;
88 gc.fill = GridBagConstraints.HORIZONTAL;
89 pnl.add(cbOpenAndCloseRestrictions = new JCheckBox(), gc);
90 cbOpenAndCloseRestrictions.addItemListener(stateChangeHandler);
91
92 gc.gridx = 1;
93 gc.weightx = 1.0;
94 pnl.add(new JMultilineLabel(tr("Select changesets depending on whether they are open or closed")),gc);
95
96 gc.gridy = 3;
97 gc.gridx = 1;
98 gc.weightx = 1.0;
99 pnl.add(pnlOpenAndCloseRestriction = new OpenAndCloseStateRestrictionPanel(), gc);
100
101 // -- restricting the query to a specific time
102 //
103 gc.gridy = 4;
104 gc.gridx = 0;
105 gc.anchor = GridBagConstraints.NORTHWEST;
106 gc.weightx = 0.0;
107 gc.fill = GridBagConstraints.HORIZONTAL;
108 pnl.add(cbTimeRestrictions = new JCheckBox(), gc);
109 cbTimeRestrictions.addItemListener(stateChangeHandler);
110
111 gc.gridx = 1;
112 gc.weightx = 1.0;
113 pnl.add(new JMultilineLabel(tr("Select changesets based on the date/time they have been created or closed")),gc);
114
115 gc.gridy = 5;
116 gc.gridx = 1;
117 gc.weightx = 1.0;
118 pnl.add(pnlTimeRestriction = new TimeRestrictionPanel(), gc);
119
120
121 // -- restricting the query to a specific bounding box
122 //
123 gc.gridy = 6;
124 gc.gridx = 0;
125 gc.anchor = GridBagConstraints.NORTHWEST;
126 gc.weightx = 0.0;
127 gc.fill = GridBagConstraints.HORIZONTAL;
128 pnl.add(cbBoundingBoxRestriction = new JCheckBox(), gc);
129 cbBoundingBoxRestriction.addItemListener(stateChangeHandler);
130
131 gc.gridx = 1;
132 gc.weightx = 1.0;
133 pnl.add(new JMultilineLabel(tr("Select only changesets related to a specific bounding box")),gc);
134
135 gc.gridy = 7;
136 gc.gridx = 1;
137 gc.weightx = 1.0;
138 pnl.add(pnlBoundingBoxRestriction = new BBoxRestrictionPanel(), gc);
139
140
141 gc.gridy = 8;
142 gc.gridx = 0;
143 gc.gridwidth = 2;
144 gc.fill =GridBagConstraints.BOTH;
145 gc.weightx = 1.0;
146 gc.weighty = 1.0;
147 pnl.add(new JPanel(), gc);
148
149 return pnl;
150 }
151
152 protected void build() {
153 setLayout(new BorderLayout());
154 JScrollPane spQueryPanel = new JScrollPane(buildQueryPanel());
155 add(spQueryPanel, BorderLayout.CENTER);
156 spQueryPanel.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
157 spQueryPanel.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
158
159 }
160
161 public AdvancedChangesetQueryPanel() {
162 build();
163 }
164
165 public void startUserInput() {
166 restoreFromSettings();
167 pnlBoundingBoxRestriction.setVisible(cbBoundingBoxRestriction.isSelected());
168 pnlOpenAndCloseRestriction.setVisible(cbOpenAndCloseRestrictions.isSelected());
169 pnlTimeRestriction.setVisible(cbTimeRestrictions.isSelected());
170 pnlUserRestriction.setVisible(cbUserRestriction.isSelected());
171 pnlOpenAndCloseRestriction.startUserInput();
172 pnlUserRestriction.startUserInput();
173 pnlTimeRestriction.startUserInput();
174 }
175
176 public void displayMessageIfInvalid() {
177 if (cbUserRestriction.isSelected()) {
178 if (! pnlUserRestriction.isValidChangesetQuery()) {
179 pnlUserRestriction.displayMessageIfInvalid();
180 }
181 } else if (cbTimeRestrictions.isSelected()) {
182 if (!pnlTimeRestriction.isValidChangesetQuery()) {
183 pnlTimeRestriction.displayMessageIfInvalid();
184 }
185 } else if (cbBoundingBoxRestriction.isSelected()) {
186 if (!pnlBoundingBoxRestriction.isValidChangesetQuery()) {
187 pnlBoundingBoxRestriction.displayMessageIfInvalid();
188 }
189 }
190 }
191
192 /**
193 * Builds the changeset query based on the data entered in the form.
194 *
195 * @return the changeset query. null, if the data entered doesn't represent
196 * a valid changeset query.
197 */
198 public ChangesetQuery buildChangesetQuery() {
199 ChangesetQuery query = new ChangesetQuery();
200 if (cbUserRestriction.isSelected()) {
201 if (! pnlUserRestriction.isValidChangesetQuery())
202 return null;
203 pnlUserRestriction.fillInQuery(query);
204 }
205 if (cbOpenAndCloseRestrictions.isSelected()) {
206 // don't have to check whether it's valid. It always is.
207 pnlOpenAndCloseRestriction.fillInQuery(query);
208 }
209 if (cbBoundingBoxRestriction.isSelected()) {
210 if (!pnlBoundingBoxRestriction.isValidChangesetQuery())
211 return null;
212 pnlBoundingBoxRestriction.fillInQuery(query);
213 }
214 if (cbTimeRestrictions.isSelected()) {
215 if (!pnlTimeRestriction.isValidChangesetQuery())
216 return null;
217 pnlTimeRestriction.fillInQuery(query);
218 }
219 return query;
220 }
221
222 public void rememberSettings() {
223 Main.pref.put("changeset-query.advanced.user-restrictions", cbUserRestriction.isSelected());
224 Main.pref.put("changeset-query.advanced.open-restrictions", cbOpenAndCloseRestrictions.isSelected());
225 Main.pref.put("changeset-query.advanced.time-restrictions", cbTimeRestrictions.isSelected());
226 Main.pref.put("changeset-query.advanced.bbox-restrictions", cbBoundingBoxRestriction.isSelected());
227
228 pnlUserRestriction.rememberSettings();
229 pnlOpenAndCloseRestriction.rememberSettings();
230 pnlTimeRestriction.rememberSettings();
231 }
232
233 public void restoreFromSettings() {
234 cbUserRestriction.setSelected(Main.pref.getBoolean("changeset-query.advanced.user-restrictions", false));
235 cbOpenAndCloseRestrictions.setSelected(Main.pref.getBoolean("changeset-query.advanced.open-restrictions", false));
236 cbTimeRestrictions.setSelected(Main.pref.getBoolean("changeset-query.advanced.time-restrictions", false));
237 cbBoundingBoxRestriction.setSelected(Main.pref.getBoolean("changeset-query.advanced.bbox-restrictions", false));
238 }
239
240 class RestrictionGroupStateChangeHandler implements ItemListener {
241 protected void userRestrictionStateChanged() {
242 if (pnlUserRestriction == null) return;
243 pnlUserRestriction.setVisible(cbUserRestriction.isSelected());
244 }
245
246 protected void openCloseRestrictionStateChanged() {
247 if (pnlOpenAndCloseRestriction == null) return;
248 pnlOpenAndCloseRestriction.setVisible(cbOpenAndCloseRestrictions.isSelected());
249 }
250
251 protected void timeRestrictionsStateChanged() {
252 if (pnlTimeRestriction == null) return;
253 pnlTimeRestriction.setVisible(cbTimeRestrictions.isSelected());
254 }
255
256 protected void boundingBoxRestrictionChanged() {
257 if (pnlBoundingBoxRestriction == null) return;
258 pnlBoundingBoxRestriction.setVisible(cbBoundingBoxRestriction.isSelected());
259 }
260
261 @Override
262 public void itemStateChanged(ItemEvent e) {
263 if (e.getSource() == cbUserRestriction) {
264 userRestrictionStateChanged();
265 } else if (e.getSource() == cbOpenAndCloseRestrictions) {
266 openCloseRestrictionStateChanged();
267 } else if (e.getSource() == cbTimeRestrictions) {
268 timeRestrictionsStateChanged();
269 } else if (e.getSource() == cbBoundingBoxRestriction) {
270 boundingBoxRestrictionChanged();
271 }
272 validate();
273 repaint();
274 }
275 }
276
277 /**
278 * This is the panel for selecting whether the changeset query should be restricted to
279 * open or closed changesets
280 */
281 static private class OpenAndCloseStateRestrictionPanel extends JPanel {
282
283 private JRadioButton rbOpenOnly;
284 private JRadioButton rbClosedOnly;
285 private JRadioButton rbBoth;
286
287 protected void build() {
288 setLayout(new GridBagLayout());
289 setBorder(BorderFactory.createCompoundBorder(
290 BorderFactory.createEmptyBorder(3,3,3,3),
291 BorderFactory.createCompoundBorder(
292 BorderFactory.createLineBorder(Color.GRAY),
293 BorderFactory.createEmptyBorder(5,5,5,5)
294 )
295 ));
296 GridBagConstraints gc = new GridBagConstraints();
297 gc.anchor = GridBagConstraints.NORTHWEST;
298 gc.fill = GridBagConstraints.HORIZONTAL;
299 gc.weightx = 0.0;
300 add(rbOpenOnly = new JRadioButton(), gc);
301
302 gc.gridx = 1;
303 gc.weightx = 1.0;
304 add(new JMultilineLabel(tr("Query open changesets only")), gc);
305
306 gc.gridy = 1;
307 gc.gridx = 0;
308 gc.weightx = 0.0;
309 add(rbClosedOnly = new JRadioButton(), gc);
310
311 gc.gridx = 1;
312 gc.weightx = 1.0;
313 add(new JMultilineLabel(tr("Query closed changesets only")), gc);
314
315 gc.gridy = 2;
316 gc.gridx = 0;
317 gc.weightx = 0.0;
318 add(rbBoth = new JRadioButton(), gc);
319
320 gc.gridx = 1;
321 gc.weightx = 1.0;
322 add(new JMultilineLabel(tr("Query both open and closed changesets")), gc);
323
324 ButtonGroup bgRestrictions = new ButtonGroup();
325 bgRestrictions.add(rbBoth);
326 bgRestrictions.add(rbClosedOnly);
327 bgRestrictions.add(rbOpenOnly);
328 }
329
330 public OpenAndCloseStateRestrictionPanel() {
331 build();
332 }
333
334 public void startUserInput() {
335 restoreFromSettings();
336 }
337
338 public void fillInQuery(ChangesetQuery query) {
339 if (rbBoth.isSelected()) {
340 query.beingClosed(true);
341 query.beingOpen(true);
342 } else if (rbOpenOnly.isSelected()) {
343 query.beingOpen(true);
344 } else if (rbClosedOnly.isSelected()) {
345 query.beingClosed(true);
346 }
347 }
348
349 public void rememberSettings() {
350 String prefRoot = "changeset-query.advanced.open-restrictions";
351 if (rbBoth.isSelected()) {
352 Main.pref.put(prefRoot + ".query-type", "both");
353 } else if (rbOpenOnly.isSelected()) {
354 Main.pref.put(prefRoot + ".query-type", "open");
355 } else if (rbClosedOnly.isSelected()) {
356 Main.pref.put(prefRoot + ".query-type", "closed");
357 }
358 }
359
360 public void restoreFromSettings() {
361 String prefRoot = "changeset-query.advanced.open-restrictions";
362 String v = Main.pref.get(prefRoot + ".query-type", "open");
363 rbBoth.setSelected(v.equals("both"));
364 rbOpenOnly.setSelected(v.equals("open"));
365 rbClosedOnly.setSelected(v.equals("closed"));
366 }
367 }
368
369 /**
370 * This is the panel for selecting whether the query should be restricted to a specific
371 * user
372 *
373 */
374 static private class UserRestrictionPanel extends JPanel {
375 private ButtonGroup bgUserRestrictions;
376 private JRadioButton rbRestrictToMyself;
377 private JRadioButton rbRestrictToUid;
378 private JRadioButton rbRestrictToUserName;
379 private JosmTextField tfUid;
380 private UidInputFieldValidator valUid;
381 private JosmTextField tfUserName;
382 private UserNameInputValidator valUserName;
383 private JMultilineLabel lblRestrictedToMyself;
384
385 protected JPanel buildUidInputPanel() {
386 JPanel pnl = new JPanel(new GridBagLayout());
387 GridBagConstraints gc = new GridBagConstraints();
388 gc.fill = GridBagConstraints.HORIZONTAL;
389 gc.weightx = 0.0;
390 gc.insets = new Insets(0,0,0,3);
391 pnl.add(new JLabel(tr("User ID:")), gc);
392
393 gc.gridx = 1;
394 pnl.add(tfUid = new JosmTextField(10),gc);
395 SelectAllOnFocusGainedDecorator.decorate(tfUid);
396 valUid = UidInputFieldValidator.decorate(tfUid);
397
398 // grab remaining space
399 gc.gridx = 2;
400 gc.weightx = 1.0;
401 pnl.add(new JPanel(), gc);
402 return pnl;
403 }
404
405 protected JPanel buildUserNameInputPanel() {
406 JPanel pnl = new JPanel(new GridBagLayout());
407 GridBagConstraints gc = new GridBagConstraints();
408 gc.fill = GridBagConstraints.HORIZONTAL;
409 gc.weightx = 0.0;
410 gc.insets = new Insets(0,0,0,3);
411 pnl.add(new JLabel(tr("User name:")), gc);
412
413 gc.gridx = 1;
414 pnl.add(tfUserName = new JosmTextField(10),gc);
415 SelectAllOnFocusGainedDecorator.decorate(tfUserName);
416 valUserName = UserNameInputValidator.decorate(tfUserName);
417
418 // grab remaining space
419 gc.gridx = 2;
420 gc.weightx = 1.0;
421 pnl.add(new JPanel(), gc);
422 return pnl;
423 }
424
425 protected void build() {
426 setLayout(new GridBagLayout());
427 setBorder(BorderFactory.createCompoundBorder(
428 BorderFactory.createEmptyBorder(3,3,3,3),
429 BorderFactory.createCompoundBorder(
430 BorderFactory.createLineBorder(Color.GRAY),
431 BorderFactory.createEmptyBorder(5,5,5,5)
432 )
433 ));
434
435 ItemListener userRestrictionChangeHandler = new UserRestrictionChangedHandler();
436 GridBagConstraints gc = new GridBagConstraints();
437 gc.anchor = GridBagConstraints.NORTHWEST;
438 gc.gridx = 0;
439 gc.fill= GridBagConstraints.HORIZONTAL;
440 gc.weightx = 0.0;
441 add(rbRestrictToMyself = new JRadioButton(), gc);
442 rbRestrictToMyself.addItemListener(userRestrictionChangeHandler);
443
444 gc.gridx = 1;
445 gc.fill = GridBagConstraints.HORIZONTAL;
446 gc.weightx = 1.0;
447 add(lblRestrictedToMyself = new JMultilineLabel(tr("Only changesets owned by myself")), gc);
448
449 gc.gridx = 0;
450 gc.gridy = 1;
451 gc.fill= GridBagConstraints.HORIZONTAL;
452 gc.weightx = 0.0;
453 add(rbRestrictToUid = new JRadioButton(), gc);
454 rbRestrictToUid.addItemListener(userRestrictionChangeHandler);
455
456 gc.gridx = 1;
457 gc.fill = GridBagConstraints.HORIZONTAL;
458 gc.weightx = 1.0;
459 add(new JMultilineLabel(tr("Only changesets owned by the user with the following user ID")),gc);
460
461 gc.gridx = 1;
462 gc.gridy = 2;
463 gc.fill = GridBagConstraints.HORIZONTAL;
464 gc.weightx = 1.0;
465 add(buildUidInputPanel(),gc);
466
467 gc.gridx = 0;
468 gc.gridy = 3;
469 gc.fill= GridBagConstraints.HORIZONTAL;
470 gc.weightx = 0.0;
471 add(rbRestrictToUserName = new JRadioButton(), gc);
472 rbRestrictToUserName.addItemListener(userRestrictionChangeHandler);
473
474 gc.gridx = 1;
475 gc.fill = GridBagConstraints.HORIZONTAL;
476 gc.weightx = 1.0;
477 add(new JMultilineLabel(tr("Only changesets owned by the user with the following user name")),gc);
478
479 gc.gridx = 1;
480 gc.gridy = 4;
481 gc.fill = GridBagConstraints.HORIZONTAL;
482 gc.weightx = 1.0;
483 add(buildUserNameInputPanel(),gc);
484
485 bgUserRestrictions = new ButtonGroup();
486 bgUserRestrictions.add(rbRestrictToMyself);
487 bgUserRestrictions.add(rbRestrictToUid);
488 bgUserRestrictions.add(rbRestrictToUserName);
489 }
490
491 public UserRestrictionPanel() {
492 build();
493 }
494
495 public void startUserInput() {
496 if (JosmUserIdentityManager.getInstance().isAnonymous()) {
497 lblRestrictedToMyself.setText(tr("Only changesets owned by myself (disabled. JOSM is currently run by an anonymous user)"));
498 rbRestrictToMyself.setEnabled(false);
499 if (rbRestrictToMyself.isSelected()) {
500 rbRestrictToUid.setSelected(true);
501 }
502 } else {
503 lblRestrictedToMyself.setText(tr("Only changesets owned by myself"));
504 rbRestrictToMyself.setEnabled(true);
505 rbRestrictToMyself.setSelected(true);
506 }
507 restoreFromSettings();
508 }
509
510 /**
511 * Sets the query restrictions on <code>query</code> for changeset owner based
512 * restrictions.
513 *
514 * @param query the query. Must not be null.
515 * @throws IllegalArgumentException thrown if query is null
516 * @throws IllegalStateException thrown if one of the available values for query parameters in
517 * this panel isn't valid
518 *
519 */
520 public void fillInQuery(ChangesetQuery query) throws IllegalStateException, IllegalArgumentException {
521 CheckParameterUtil.ensureParameterNotNull(query, "query");
522 if (rbRestrictToMyself.isSelected()) {
523 JosmUserIdentityManager im = JosmUserIdentityManager.getInstance();
524 if (im.isPartiallyIdentified()) {
525 query.forUser(im.getUserName());
526 } else if (im.isFullyIdentified()) {
527 query.forUser(im.getUserId());
528 } else
529 throw new IllegalStateException(tr("Cannot restrict changeset query to the current user because the current user is anonymous"));
530 } else if (rbRestrictToUid.isSelected()) {
531 int uid = valUid.getUid();
532 if (uid > 0) {
533 query.forUser(uid);
534 } else
535 throw new IllegalStateException(tr("Current value ''{0}'' for user ID is not valid", tfUid.getText()));
536 } else if (rbRestrictToUserName.isSelected()) {
537 if (! valUserName.isValid())
538 throw new IllegalStateException(tr("Cannot restrict the changeset query to the user name ''{0}''", tfUserName.getText()));
539 query.forUser(tfUserName.getText());
540 }
541 }
542
543
544 public boolean isValidChangesetQuery() {
545 if (rbRestrictToUid.isSelected())
546 return valUid.isValid();
547 else if (rbRestrictToUserName.isSelected())
548 return valUserName.isValid();
549 return true;
550 }
551
552 protected void alertInvalidUid() {
553 HelpAwareOptionPane.showOptionDialog(
554 this,
555 tr("Please enter a valid user ID"),
556 tr("Invalid user ID"),
557 JOptionPane.ERROR_MESSAGE,
558 HelpUtil.ht("/Dialog/ChangesetQueryDialog#InvalidUserId")
559 );
560 }
561
562 protected void alertInvalidUserName() {
563 HelpAwareOptionPane.showOptionDialog(
564 this,
565 tr("Please enter a non-empty user name"),
566 tr("Invalid user name"),
567 JOptionPane.ERROR_MESSAGE,
568 HelpUtil.ht("/Dialog/ChangesetQueryDialog#InvalidUserName")
569 );
570 }
571
572 public void displayMessageIfInvalid() {
573 if (rbRestrictToUid.isSelected()) {
574 if (!valUid.isValid()) {
575 alertInvalidUid();
576 }
577 } else if (rbRestrictToUserName.isSelected()) {
578 if (!valUserName.isValid()) {
579 alertInvalidUserName();
580 }
581 }
582 }
583
584 public void rememberSettings() {
585 String prefRoot = "changeset-query.advanced.user-restrictions";
586 if (rbRestrictToMyself.isSelected()) {
587 Main.pref.put(prefRoot + ".query-type", "mine");
588 } else if (rbRestrictToUid.isSelected()) {
589 Main.pref.put(prefRoot + ".query-type", "uid");
590 } else if (rbRestrictToUserName.isSelected()) {
591 Main.pref.put(prefRoot + ".query-type", "username");
592 }
593 Main.pref.put(prefRoot + ".uid", tfUid.getText());
594 Main.pref.put(prefRoot + ".username", tfUserName.getText());
595 }
596
597 public void restoreFromSettings() {
598 String prefRoot = "changeset-query.advanced.user-restrictions";
599 String v = Main.pref.get(prefRoot + ".query-type", "mine");
600 if (v.equals("mine")) {
601 JosmUserIdentityManager im = JosmUserIdentityManager.getInstance();
602 if (im.isAnonymous()) {
603 rbRestrictToUid.setSelected(true);
604 } else {
605 rbRestrictToMyself.setSelected(true);
606 }
607 } else if (v.equals("uid")) {
608 rbRestrictToUid.setSelected(true);
609 } else if (v.equals("username")) {
610 rbRestrictToUserName.setSelected(true);
611 }
612 tfUid.setText(Main.pref.get(prefRoot + ".uid", ""));
613 if (!valUid.isValid()) {
614 tfUid.setText("");
615 }
616 tfUserName.setText(Main.pref.get(prefRoot + ".username", ""));
617 }
618
619 class UserRestrictionChangedHandler implements ItemListener {
620 @Override
621 public void itemStateChanged(ItemEvent e) {
622 tfUid.setEnabled(rbRestrictToUid.isSelected());
623 tfUserName.setEnabled(rbRestrictToUserName.isSelected());
624 if (rbRestrictToUid.isSelected()) {
625 tfUid.requestFocusInWindow();
626 } else if (rbRestrictToUserName.isSelected()) {
627 tfUserName.requestFocusInWindow();
628 }
629 }
630 }
631 }
632
633 /**
634 * This is the panel to apply a time restriction to the changeset query
635 */
636 static private class TimeRestrictionPanel extends JPanel {
637
638 private JRadioButton rbClosedAfter;
639 private JRadioButton rbClosedAfterAndCreatedBefore;
640 private JosmTextField tfClosedAfterDate1;
641 private DateValidator valClosedAfterDate1;
642 private JosmTextField tfClosedAfterTime1;
643 private TimeValidator valClosedAfterTime1;
644 private JosmTextField tfClosedAfterDate2;
645 private DateValidator valClosedAfterDate2;
646 private JosmTextField tfClosedAfterTime2;
647 private TimeValidator valClosedAfterTime2;
648 private JosmTextField tfCreatedBeforeDate;
649 private DateValidator valCreatedBeforeDate;
650 private JosmTextField tfCreatedBeforeTime;
651 private TimeValidator valCreatedBeforeTime;
652
653 protected JPanel buildClosedAfterInputPanel() {
654 JPanel pnl = new JPanel(new GridBagLayout());
655 GridBagConstraints gc = new GridBagConstraints();
656 gc.fill = GridBagConstraints.HORIZONTAL;
657 gc.weightx = 0.0;
658 gc.insets = new Insets(0,0,0,3);
659 pnl.add(new JLabel(tr("Date: ")), gc);
660
661 gc.gridx = 1;
662 gc.weightx = 0.7;
663 pnl.add(tfClosedAfterDate1 = new JosmTextField(),gc);
664 SelectAllOnFocusGainedDecorator.decorate(tfClosedAfterDate1);
665 valClosedAfterDate1 = DateValidator.decorate(tfClosedAfterDate1);
666 tfClosedAfterDate1.setToolTipText(valClosedAfterDate1.getStandardTooltipTextAsHtml());
667
668 gc.gridx = 2;
669 gc.weightx = 0.0;
670 pnl.add(new JLabel(tr("Time:")),gc);
671
672 gc.gridx = 3;
673 gc.weightx = 0.3;
674 pnl.add(tfClosedAfterTime1 = new JosmTextField(),gc);
675 SelectAllOnFocusGainedDecorator.decorate(tfClosedAfterTime1);
676 valClosedAfterTime1 = TimeValidator.decorate(tfClosedAfterTime1);
677 tfClosedAfterTime1.setToolTipText(valClosedAfterTime1.getStandardTooltipTextAsHtml());
678 return pnl;
679 }
680
681 protected JPanel buildClosedAfterAndCreatedBeforeInputPanel() {
682 JPanel pnl = new JPanel(new GridBagLayout());
683 GridBagConstraints gc = new GridBagConstraints();
684 gc.fill = GridBagConstraints.HORIZONTAL;
685 gc.weightx = 0.0;
686 gc.insets = new Insets(0,0,0,3);
687 pnl.add(new JLabel(tr("Closed after - ")), gc);
688
689 gc.gridx = 1;
690 gc.fill = GridBagConstraints.HORIZONTAL;
691 gc.weightx = 0.0;
692 gc.insets = new Insets(0,0,0,3);
693 pnl.add(new JLabel(tr("Date:")), gc);
694
695 gc.gridx = 2;
696 gc.weightx = 0.7;
697 pnl.add(tfClosedAfterDate2 = new JosmTextField(),gc);
698 SelectAllOnFocusGainedDecorator.decorate(tfClosedAfterDate2);
699 valClosedAfterDate2 = DateValidator.decorate(tfClosedAfterDate2);
700 tfClosedAfterDate2.setToolTipText(valClosedAfterDate2.getStandardTooltipTextAsHtml());
701 gc.gridx = 3;
702 gc.weightx = 0.0;
703 pnl.add(new JLabel(tr("Time:")),gc);
704
705 gc.gridx = 4;
706 gc.weightx = 0.3;
707 pnl.add(tfClosedAfterTime2 = new JosmTextField(),gc);
708 SelectAllOnFocusGainedDecorator.decorate(tfClosedAfterTime2);
709 valClosedAfterTime2 = TimeValidator.decorate(tfClosedAfterTime2);
710 tfClosedAfterTime2.setToolTipText(valClosedAfterTime2.getStandardTooltipTextAsHtml());
711
712 gc.gridy = 1;
713 gc.gridx = 0;
714 gc.fill = GridBagConstraints.HORIZONTAL;
715 gc.weightx = 0.0;
716 gc.insets = new Insets(0,0,0,3);
717 pnl.add(new JLabel(tr("Created before - ")), gc);
718
719 gc.gridx = 1;
720 gc.fill = GridBagConstraints.HORIZONTAL;
721 gc.weightx = 0.0;
722 gc.insets = new Insets(0,0,0,3);
723 pnl.add(new JLabel(tr("Date:")), gc);
724
725 gc.gridx = 2;
726 gc.weightx = 0.7;
727 pnl.add(tfCreatedBeforeDate = new JosmTextField(),gc);
728 SelectAllOnFocusGainedDecorator.decorate(tfCreatedBeforeDate);
729 valCreatedBeforeDate = DateValidator.decorate(tfCreatedBeforeDate);
730 tfCreatedBeforeDate.setToolTipText(valCreatedBeforeDate.getStandardTooltipTextAsHtml());
731
732 gc.gridx = 3;
733 gc.weightx = 0.0;
734 pnl.add(new JLabel(tr("Time:")),gc);
735
736 gc.gridx = 4;
737 gc.weightx = 0.3;
738 pnl.add(tfCreatedBeforeTime = new JosmTextField(),gc);
739 SelectAllOnFocusGainedDecorator.decorate(tfCreatedBeforeTime);
740 valCreatedBeforeTime = TimeValidator.decorate(tfCreatedBeforeTime);
741 tfCreatedBeforeTime.setToolTipText(valCreatedBeforeDate.getStandardTooltipTextAsHtml());
742
743 return pnl;
744 }
745
746 protected void build() {
747 setLayout(new GridBagLayout());
748 setBorder(BorderFactory.createCompoundBorder(
749 BorderFactory.createEmptyBorder(3,3,3,3),
750 BorderFactory.createCompoundBorder(
751 BorderFactory.createLineBorder(Color.GRAY),
752 BorderFactory.createEmptyBorder(5,5,5,5)
753 )
754 ));
755
756 // -- changesets closed after a specific date/time
757 //
758 GridBagConstraints gc = new GridBagConstraints();
759 gc.anchor = GridBagConstraints.NORTHWEST;
760 gc.gridx = 0;
761 gc.fill= GridBagConstraints.HORIZONTAL;
762 gc.weightx = 0.0;
763 add(rbClosedAfter = new JRadioButton(), gc);
764
765 gc.gridx = 1;
766 gc.fill = GridBagConstraints.HORIZONTAL;
767 gc.weightx = 1.0;
768 add(new JMultilineLabel(tr("Only changesets closed after the following date/time")), gc);
769
770 gc.gridx = 1;
771 gc.gridy = 1;
772 gc.fill = GridBagConstraints.HORIZONTAL;
773 gc.weightx = 1.0;
774 add(buildClosedAfterInputPanel(),gc);
775
776 // -- changesets closed after a specific date/time and created before a specific date time
777 //
778 gc = new GridBagConstraints();
779 gc.anchor = GridBagConstraints.NORTHWEST;
780 gc.gridy = 2;
781 gc.gridx = 0;
782 gc.fill= GridBagConstraints.HORIZONTAL;
783 gc.weightx = 0.0;
784 add(rbClosedAfterAndCreatedBefore = new JRadioButton(), gc);
785
786 gc.gridx = 1;
787 gc.fill = GridBagConstraints.HORIZONTAL;
788 gc.weightx = 1.0;
789 add(new JMultilineLabel(tr("Only changesets closed after and created before a specific date/time")), gc);
790
791 gc.gridx = 1;
792 gc.gridy = 3;
793 gc.fill = GridBagConstraints.HORIZONTAL;
794 gc.weightx = 1.0;
795 add(buildClosedAfterAndCreatedBeforeInputPanel(),gc);
796
797 ButtonGroup bg = new ButtonGroup();
798 bg.add(rbClosedAfter);
799 bg.add(rbClosedAfterAndCreatedBefore);
800
801 ItemListener restrictionChangeHandler = new TimeRestrictionChangedHandler();
802 rbClosedAfter.addItemListener(restrictionChangeHandler);
803 rbClosedAfterAndCreatedBefore.addItemListener(restrictionChangeHandler);
804
805 rbClosedAfter.setSelected(true);
806 }
807
808 public TimeRestrictionPanel() {
809 build();
810 }
811
812 public boolean isValidChangesetQuery() {
813 if (rbClosedAfter.isSelected())
814 return valClosedAfterDate1.isValid() && valClosedAfterTime1.isValid();
815 else if (rbClosedAfterAndCreatedBefore.isSelected())
816 return valClosedAfterDate2.isValid() && valClosedAfterTime2.isValid()
817 && valCreatedBeforeDate.isValid() && valCreatedBeforeTime.isValid();
818 // should not happen
819 return true;
820 }
821
822 class TimeRestrictionChangedHandler implements ItemListener {
823 @Override
824 public void itemStateChanged(ItemEvent e) {
825 tfClosedAfterDate1.setEnabled(rbClosedAfter.isSelected());
826 tfClosedAfterTime1.setEnabled(rbClosedAfter.isSelected());
827
828 tfClosedAfterDate2.setEnabled(rbClosedAfterAndCreatedBefore.isSelected());
829 tfClosedAfterTime2.setEnabled(rbClosedAfterAndCreatedBefore.isSelected());
830 tfCreatedBeforeDate.setEnabled(rbClosedAfterAndCreatedBefore.isSelected());
831 tfCreatedBeforeTime.setEnabled(rbClosedAfterAndCreatedBefore.isSelected());
832 }
833 }
834
835 public void startUserInput() {
836 restoreFromSettings();
837 }
838
839 public void fillInQuery(ChangesetQuery query) throws IllegalStateException{
840 if (!isValidChangesetQuery())
841 throw new IllegalStateException(tr("Cannot build changeset query with time based restrictions. Input is not valid."));
842 if (rbClosedAfter.isSelected()) {
843 GregorianCalendar cal = new GregorianCalendar();
844 Date d1 = valClosedAfterDate1.getDate();
845 Date d2 = valClosedAfterTime1.getDate();
846 cal.setTimeInMillis(d1.getTime() + (d2 == null ? 0 : d2.getTime()));
847 query.closedAfter(cal.getTime());
848 } else if (rbClosedAfterAndCreatedBefore.isSelected()) {
849 GregorianCalendar cal = new GregorianCalendar();
850 Date d1 = valClosedAfterDate2.getDate();
851 Date d2 = valClosedAfterTime2.getDate();
852 cal.setTimeInMillis(d1.getTime() + (d2 == null ? 0 : d2.getTime()));
853 Date d3 = cal.getTime();
854
855 d1 = valCreatedBeforeDate.getDate();
856 d2 = valCreatedBeforeTime.getDate();
857 cal.setTimeInMillis(d1.getTime() + (d2 == null ? 0 : d2.getTime()));
858 Date d4 = cal.getTime();
859
860 query.closedAfterAndCreatedBefore(d3, d4);
861 }
862 }
863
864 public void displayMessageIfInvalid() {
865 if (isValidChangesetQuery()) return;
866 HelpAwareOptionPane.showOptionDialog(
867 this,
868 tr(
869 "<html>Please enter valid date/time values to restrict<br>"
870 + "the query to a specific time range.</html>"
871 ),
872 tr("Invalid date/time values"),
873 JOptionPane.ERROR_MESSAGE,
874 HelpUtil.ht("/Dialog/ChangesetQueryDialog#InvalidDateTimeValues")
875 );
876 }
877
878
879 public void rememberSettings() {
880 String prefRoot = "changeset-query.advanced.time-restrictions";
881 if (rbClosedAfter.isSelected()) {
882 Main.pref.put(prefRoot + ".query-type", "closed-after");
883 } else if (rbClosedAfterAndCreatedBefore.isSelected()) {
884 Main.pref.put(prefRoot + ".query-type", "closed-after-created-before");
885 }
886 Main.pref.put(prefRoot + ".closed-after.date", tfClosedAfterDate1.getText());
887 Main.pref.put(prefRoot + ".closed-after.time", tfClosedAfterTime1.getText());
888 Main.pref.put(prefRoot + ".closed-created.closed.date", tfClosedAfterDate2.getText());
889 Main.pref.put(prefRoot + ".closed-created.closed.time", tfClosedAfterTime2.getText());
890 Main.pref.put(prefRoot + ".closed-created.created.date", tfCreatedBeforeDate.getText());
891 Main.pref.put(prefRoot + ".closed-created.created.time", tfCreatedBeforeTime.getText());
892 }
893
894 public void restoreFromSettings() {
895 String prefRoot = "changeset-query.advanced.open-restrictions";
896 String v = Main.pref.get(prefRoot + ".query-type", "closed-after");
897 rbClosedAfter.setSelected(v.equals("closed-after"));
898 rbClosedAfterAndCreatedBefore.setSelected(v.equals("closed-after-created-before"));
899 if (!rbClosedAfter.isSelected() && !rbClosedAfterAndCreatedBefore.isSelected()) {
900 rbClosedAfter.setSelected(true);
901 }
902 tfClosedAfterDate1.setText(Main.pref.get(prefRoot + ".closed-after.date", ""));
903 tfClosedAfterTime1.setText(Main.pref.get(prefRoot + ".closed-after.time", ""));
904 tfClosedAfterDate2.setText(Main.pref.get(prefRoot + ".closed-created.closed.date", ""));
905 tfClosedAfterTime2.setText(Main.pref.get(prefRoot + ".closed-created.closed.time", ""));
906 tfCreatedBeforeDate.setText(Main.pref.get(prefRoot + ".closed-created.created.date", ""));
907 tfCreatedBeforeTime.setText(Main.pref.get(prefRoot + ".closed-created.created.time", ""));
908 if (!valClosedAfterDate1.isValid()) {
909 tfClosedAfterDate1.setText("");
910 }
911 if (!valClosedAfterTime1.isValid()) {
912 tfClosedAfterTime1.setText("");
913 }
914 if (!valClosedAfterDate2.isValid()) {
915 tfClosedAfterDate2.setText("");
916 }
917 if (!valClosedAfterTime2.isValid()) {
918 tfClosedAfterTime2.setText("");
919 }
920 if (!valCreatedBeforeDate.isValid()) {
921 tfCreatedBeforeDate.setText("");
922 }
923 if (!valCreatedBeforeTime.isValid()) {
924 tfCreatedBeforeTime.setText("");
925 }
926 }
927 }
928
929 static private class BBoxRestrictionPanel extends BoundingBoxSelectionPanel {
930 public BBoxRestrictionPanel() {
931 setBorder(BorderFactory.createCompoundBorder(
932 BorderFactory.createEmptyBorder(3,3,3,3),
933 BorderFactory.createCompoundBorder(
934 BorderFactory.createLineBorder(Color.GRAY),
935 BorderFactory.createEmptyBorder(5,5,5,5)
936 )
937 ));
938 }
939
940 public boolean isValidChangesetQuery() {
941 return getBoundingBox() != null;
942 }
943
944 public void fillInQuery(ChangesetQuery query) {
945 if (!isValidChangesetQuery())
946 throw new IllegalStateException(tr("Cannot restrict the changeset query to a specific bounding box. The input is invalid."));
947 query.inBbox(getBoundingBox());
948 }
949
950 public void displayMessageIfInvalid() {
951 if (isValidChangesetQuery()) return;
952 HelpAwareOptionPane.showOptionDialog(
953 this,
954 tr(
955 "<html>Please enter valid longitude/latitude values to restrict<br>" +
956 "the changeset query to a specific bounding box.</html>"
957 ),
958 tr("Invalid bounding box"),
959 JOptionPane.ERROR_MESSAGE,
960 HelpUtil.ht("/Dialog/ChangesetQueryDialog#InvalidBoundingBox")
961 );
962 }
963 }
964
965 /**
966 * Validator for user ids entered in in a {@link JTextComponent}.
967 *
968 */
969 static private class UidInputFieldValidator extends AbstractTextComponentValidator {
970 static public UidInputFieldValidator decorate(JTextComponent tc) {
971 return new UidInputFieldValidator(tc);
972 }
973
974 public UidInputFieldValidator(JTextComponent tc) {
975 super(tc);
976 }
977
978 @Override
979 public boolean isValid() {
980 return getUid() > 0;
981 }
982
983 @Override
984 public void validate() {
985 String value = getComponent().getText();
986 if (value == null || value.trim().length() == 0) {
987 feedbackInvalid("");
988 return;
989 }
990 try {
991 int uid = Integer.parseInt(value);
992 if (uid <= 0) {
993 feedbackInvalid(tr("The current value is not a valid user ID. Please enter an integer value > 0"));
994 return;
995 }
996 } catch(NumberFormatException e) {
997 feedbackInvalid(tr("The current value is not a valid user ID. Please enter an integer value > 0"));
998 return;
999 }
1000 feedbackValid(tr("Please enter an integer value > 0"));
1001 }
1002
1003 public int getUid() {
1004 String value = getComponent().getText();
1005 if (value == null || value.trim().length() == 0) return 0;
1006 try {
1007 int uid = Integer.parseInt(value.trim());
1008 if (uid > 0) return uid;
1009 return 0;
1010 } catch(NumberFormatException e) {
1011 return 0;
1012 }
1013 }
1014 }
1015
1016 static private class UserNameInputValidator extends AbstractTextComponentValidator {
1017 static public UserNameInputValidator decorate(JTextComponent tc) {
1018 return new UserNameInputValidator(tc);
1019 }
1020
1021 public UserNameInputValidator(JTextComponent tc) {
1022 super(tc);
1023 }
1024
1025 @Override
1026 public boolean isValid() {
1027 return getComponent().getText().trim().length() > 0;
1028 }
1029
1030 @Override
1031 public void validate() {
1032 String value = getComponent().getText();
1033 if (value.trim().length() == 0) {
1034 feedbackInvalid(tr("<html>The current value is not a valid user name.<br>Please enter an non-empty user name.</html>"));
1035 return;
1036 }
1037 feedbackValid(tr("Please enter an non-empty user name"));
1038 }
1039 }
1040
1041 /**
1042 * Validates dates entered as text in in a {@link JTextComponent}. Validates the input
1043 * on the fly and gives feedback about whether the date is valid or not.
1044 *
1045 * Dates can be entered in one of four standard formats defined for the current locale.
1046 */
1047 static private class DateValidator extends AbstractTextComponentValidator {
1048 static public DateValidator decorate(JTextComponent tc) {
1049 return new DateValidator(tc);
1050 }
1051
1052 public DateValidator(JTextComponent tc) {
1053 super(tc);
1054 }
1055
1056 @Override
1057 public boolean isValid() {
1058 return getDate() != null;
1059 }
1060
1061 public String getStandardTooltipTextAsHtml() {
1062 return "<html>" + getStandardTooltipText() + "</html>";
1063 }
1064
1065 public String getStandardTooltipText() {
1066 return tr(
1067 "Please enter a date in the usual format for your locale.<br>"
1068 + "Example: {0}<br>"
1069 + "Example: {1}<br>"
1070 + "Example: {2}<br>"
1071 + "Example: {3}<br>",
1072 DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault()).format(new Date()),
1073 DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault()).format(new Date()),
1074 DateFormat.getDateInstance(DateFormat.LONG, Locale.getDefault()).format(new Date()),
1075 DateFormat.getDateInstance(DateFormat.FULL, Locale.getDefault()).format(new Date())
1076 );
1077 }
1078
1079 @Override
1080 public void validate() {
1081 if (!isValid()) {
1082 String msg = "<html>The current value isn't a valid date.<br>" + getStandardTooltipText()+ "</html>";
1083 feedbackInvalid(msg);
1084 return;
1085 } else {
1086 String msg = "<html>" + getStandardTooltipText() + "</html>";
1087 feedbackValid(msg);
1088 }
1089 }
1090
1091 public Date getDate() {
1092 for (int format: new int[] {DateFormat.SHORT, DateFormat.MEDIUM, DateFormat.LONG, DateFormat.FULL}) {
1093 DateFormat df = DateFormat.getDateInstance(format);
1094 try {
1095 return df.parse(getComponent().getText());
1096 } catch (ParseException e) {
1097 // Try next format
1098 }
1099 }
1100 return null;
1101 }
1102 }
1103
1104 /**
1105 * Validates time values entered as text in in a {@link JTextComponent}. Validates the input
1106 * on the fly and gives feedback about whether the time value is valid or not.
1107 *
1108 * Time values can be entered in one of four standard formats defined for the current locale.
1109 */
1110 static private class TimeValidator extends AbstractTextComponentValidator {
1111 static public TimeValidator decorate(JTextComponent tc) {
1112 return new TimeValidator(tc);
1113 }
1114
1115 public TimeValidator(JTextComponent tc) {
1116 super(tc);
1117 }
1118
1119 @Override
1120 public boolean isValid() {
1121 if (getComponent().getText().trim().length() == 0) return true;
1122 return getDate() != null;
1123 }
1124
1125 public String getStandardTooltipTextAsHtml() {
1126 return "<html>" + getStandardTooltipText() + "</html>";
1127 }
1128
1129 public String getStandardTooltipText() {
1130 return tr(
1131 "Please enter a valid time in the usual format for your locale.<br>"
1132 + "Example: {0}<br>"
1133 + "Example: {1}<br>"
1134 + "Example: {2}<br>"
1135 + "Example: {3}<br>",
1136 DateFormat.getTimeInstance(DateFormat.SHORT, Locale.getDefault()).format(new Date()),
1137 DateFormat.getTimeInstance(DateFormat.MEDIUM, Locale.getDefault()).format(new Date()),
1138 DateFormat.getTimeInstance(DateFormat.LONG, Locale.getDefault()).format(new Date()),
1139 DateFormat.getTimeInstance(DateFormat.FULL, Locale.getDefault()).format(new Date())
1140 );
1141 }
1142
1143 @Override
1144 public void validate() {
1145
1146 if (!isValid()) {
1147 String msg = "<html>The current value isn't a valid time.<br>" + getStandardTooltipText() + "</html>";
1148 feedbackInvalid(msg);
1149 return;
1150 } else {
1151 String msg = "<html>" + getStandardTooltipText() + "</html>";
1152 feedbackValid(msg);
1153 }
1154 }
1155
1156 public Date getDate() {
1157 if (getComponent().getText().trim().length() == 0)
1158 return null;
1159
1160 for (int style : new int[]{DateFormat.SHORT, DateFormat.MEDIUM, DateFormat.LONG, DateFormat.FULL}) {
1161 try {
1162 return DateFormat.getTimeInstance(style, Locale.getDefault()).parse(getComponent().getText());
1163 } catch(ParseException e) {
1164 continue;
1165 }
1166 }
1167 return null;
1168 }
1169 }
1170}
Note: See TracBrowser for help on using the repository browser.