001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.dialogs.changeset.query; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.BorderLayout; 007import java.awt.Color; 008import java.awt.GridBagConstraints; 009import java.awt.GridBagLayout; 010import java.awt.Insets; 011import java.awt.event.ItemEvent; 012import java.awt.event.ItemListener; 013import java.text.DateFormat; 014import java.text.ParseException; 015import java.util.Date; 016import java.util.GregorianCalendar; 017import java.util.Locale; 018 019import javax.swing.BorderFactory; 020import javax.swing.ButtonGroup; 021import javax.swing.JCheckBox; 022import javax.swing.JLabel; 023import javax.swing.JOptionPane; 024import javax.swing.JPanel; 025import javax.swing.JRadioButton; 026import javax.swing.JScrollPane; 027import javax.swing.text.JTextComponent; 028 029import org.openstreetmap.josm.Main; 030import org.openstreetmap.josm.gui.HelpAwareOptionPane; 031import org.openstreetmap.josm.gui.JosmUserIdentityManager; 032import org.openstreetmap.josm.gui.help.HelpUtil; 033import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator; 034import org.openstreetmap.josm.gui.widgets.BoundingBoxSelectionPanel; 035import org.openstreetmap.josm.gui.widgets.JMultilineLabel; 036import org.openstreetmap.josm.gui.widgets.SelectAllOnFocusGainedDecorator; 037import org.openstreetmap.josm.gui.widgets.VerticallyScrollablePanel; 038import org.openstreetmap.josm.io.ChangesetQuery; 039import org.openstreetmap.josm.tools.CheckParameterUtil; 040import org.openstreetmap.josm.gui.widgets.JosmTextField; 041 042 043/** 044 * This panel allows to specify a changeset query 045 * 046 */ 047public class AdvancedChangesetQueryPanel extends JPanel { 048 049 private JCheckBox cbUserRestriction; 050 private JCheckBox cbOpenAndCloseRestrictions; 051 private JCheckBox cbTimeRestrictions; 052 private JCheckBox cbBoundingBoxRestriction; 053 private UserRestrictionPanel pnlUserRestriction; 054 private OpenAndCloseStateRestrictionPanel pnlOpenAndCloseRestriction; 055 private TimeRestrictionPanel pnlTimeRestriction; 056 private BBoxRestrictionPanel pnlBoundingBoxRestriction; 057 058 protected JPanel buildQueryPanel() { 059 ItemListener stateChangeHandler = new RestrictionGroupStateChangeHandler(); 060 JPanel pnl = new VerticallyScrollablePanel(); 061 pnl.setBorder(BorderFactory.createEmptyBorder(5,5,5,5)); 062 pnl.setLayout(new GridBagLayout()); 063 GridBagConstraints gc = new GridBagConstraints(); 064 065 // -- select changesets by a specific user 066 // 067 gc.anchor = GridBagConstraints.NORTHWEST; 068 gc.weightx = 0.0; 069 gc.fill = GridBagConstraints.HORIZONTAL; 070 pnl.add(cbUserRestriction = new JCheckBox(), gc); 071 cbUserRestriction.addItemListener(stateChangeHandler); 072 073 gc.gridx = 1; 074 gc.weightx = 1.0; 075 pnl.add(new JMultilineLabel(tr("Select changesets owned by specific users")),gc); 076 077 gc.gridy = 1; 078 gc.gridx = 1; 079 gc.weightx = 1.0; 080 pnl.add(pnlUserRestriction = new UserRestrictionPanel(), gc); 081 082 // -- restricting the query to open and closed changesets 083 // 084 gc.gridy = 2; 085 gc.gridx = 0; 086 gc.anchor = GridBagConstraints.NORTHWEST; 087 gc.weightx = 0.0; 088 gc.fill = GridBagConstraints.HORIZONTAL; 089 pnl.add(cbOpenAndCloseRestrictions = new JCheckBox(), gc); 090 cbOpenAndCloseRestrictions.addItemListener(stateChangeHandler); 091 092 gc.gridx = 1; 093 gc.weightx = 1.0; 094 pnl.add(new JMultilineLabel(tr("Select changesets depending on whether they are open or closed")),gc); 095 096 gc.gridy = 3; 097 gc.gridx = 1; 098 gc.weightx = 1.0; 099 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}