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

Last change on this file since 6084 was 6084, checked in by bastiK, 6 years ago

see #8902 - add missing @Override annotations (patch by shinigami)

  • 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.JMultilineLabel;
32import org.openstreetmap.josm.gui.JosmUserIdentityManager;
33import org.openstreetmap.josm.gui.help.HelpUtil;
34import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator;
35import org.openstreetmap.josm.gui.widgets.BoundingBoxSelectionPanel;
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.