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

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

see #9508 - refactor validator preferences + handling of JScrollPane policies ("vertical as needed" / "horizontal as needed" are default policies)

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