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.util.GuiHelper; 034import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator; 035import org.openstreetmap.josm.gui.widgets.BoundingBoxSelectionPanel; 036import org.openstreetmap.josm.gui.widgets.JMultilineLabel; 037import org.openstreetmap.josm.gui.widgets.JosmTextField; 038import org.openstreetmap.josm.gui.widgets.SelectAllOnFocusGainedDecorator; 039import org.openstreetmap.josm.gui.widgets.VerticallyScrollablePanel; 040import org.openstreetmap.josm.io.ChangesetQuery; 041import org.openstreetmap.josm.tools.CheckParameterUtil; 042 043 044/** 045 * This panel allows to specify a changeset query 046 * 047 */ 048public class AdvancedChangesetQueryPanel extends JPanel { 049 050 private JCheckBox cbUserRestriction; 051 private JCheckBox cbOpenAndCloseRestrictions; 052 private JCheckBox cbTimeRestrictions; 053 private JCheckBox cbBoundingBoxRestriction; 054 private UserRestrictionPanel pnlUserRestriction; 055 private OpenAndCloseStateRestrictionPanel pnlOpenAndCloseRestriction; 056 private TimeRestrictionPanel pnlTimeRestriction; 057 private BBoxRestrictionPanel pnlBoundingBoxRestriction; 058 059 protected JPanel buildQueryPanel() { 060 ItemListener stateChangeHandler = new RestrictionGroupStateChangeHandler(); 061 JPanel pnl = new VerticallyScrollablePanel(); 062 pnl.setBorder(BorderFactory.createEmptyBorder(5,5,5,5)); 063 pnl.setLayout(new GridBagLayout()); 064 GridBagConstraints gc = new GridBagConstraints(); 065 066 // -- select changesets by a specific user 067 // 068 gc.anchor = GridBagConstraints.NORTHWEST; 069 gc.weightx = 0.0; 070 gc.fill = GridBagConstraints.HORIZONTAL; 071 pnl.add(cbUserRestriction = new JCheckBox(), gc); 072 cbUserRestriction.addItemListener(stateChangeHandler); 073 074 gc.gridx = 1; 075 gc.weightx = 1.0; 076 pnl.add(new JMultilineLabel(tr("Select changesets owned by specific users")),gc); 077 078 gc.gridy = 1; 079 gc.gridx = 1; 080 gc.weightx = 1.0; 081 pnl.add(pnlUserRestriction = new UserRestrictionPanel(), gc); 082 083 // -- restricting the query to open and closed changesets 084 // 085 gc.gridy = 2; 086 gc.gridx = 0; 087 gc.anchor = GridBagConstraints.NORTHWEST; 088 gc.weightx = 0.0; 089 gc.fill = GridBagConstraints.HORIZONTAL; 090 pnl.add(cbOpenAndCloseRestrictions = new JCheckBox(), gc); 091 cbOpenAndCloseRestrictions.addItemListener(stateChangeHandler); 092 093 gc.gridx = 1; 094 gc.weightx = 1.0; 095 pnl.add(new JMultilineLabel(tr("Select changesets depending on whether they are open or closed")),gc); 096 097 gc.gridy = 3; 098 gc.gridx = 1; 099 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 final 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 private static 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("both".equals(v)); 365 rbOpenOnly.setSelected("open".equals(v)); 366 rbClosedOnly.setSelected("closed".equals(v)); 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 private static 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 ("mine".equals(v)) { 602 JosmUserIdentityManager im = JosmUserIdentityManager.getInstance(); 603 if (im.isAnonymous()) { 604 rbRestrictToUid.setSelected(true); 605 } else { 606 rbRestrictToMyself.setSelected(true); 607 } 608 } else if ("uid".equals(v)) { 609 rbRestrictToUid.setSelected(true); 610 } else if ("username".equals(v)) { 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 private static 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("closed-after".equals(v)); 899 rbClosedAfterAndCreatedBefore.setSelected("closed-after-created-before".equals(v)); 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 private static 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 a {@link JTextComponent}. 968 * 969 */ 970 private static class UidInputFieldValidator extends AbstractTextComponentValidator { 971 public static 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 private static class UserNameInputValidator extends AbstractTextComponentValidator { 1018 public static 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 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 private static class DateValidator extends AbstractTextComponentValidator { 1049 public static 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 Date date = new Date(); 1068 return tr( 1069 "Please enter a date in the usual format for your locale.<br>" 1070 + "Example: {0}<br>" 1071 + "Example: {1}<br>" 1072 + "Example: {2}<br>" 1073 + "Example: {3}<br>", 1074 DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault()).format(date), 1075 DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault()).format(date), 1076 DateFormat.getDateInstance(DateFormat.LONG, Locale.getDefault()).format(date), 1077 DateFormat.getDateInstance(DateFormat.FULL, Locale.getDefault()).format(date) 1078 ); 1079 } 1080 1081 @Override 1082 public void validate() { 1083 if (!isValid()) { 1084 String msg = "<html>The current value isn't a valid date.<br>" + getStandardTooltipText()+ "</html>"; 1085 feedbackInvalid(msg); 1086 return; 1087 } else { 1088 String msg = "<html>" + getStandardTooltipText() + "</html>"; 1089 feedbackValid(msg); 1090 } 1091 } 1092 1093 public Date getDate() { 1094 for (int format: new int[] {DateFormat.SHORT, DateFormat.MEDIUM, DateFormat.LONG, DateFormat.FULL}) { 1095 DateFormat df = DateFormat.getDateInstance(format); 1096 try { 1097 return df.parse(getComponent().getText()); 1098 } catch (ParseException e) { 1099 // Try next format 1100 } 1101 } 1102 return null; 1103 } 1104 } 1105 1106 /** 1107 * Validates time values entered as text in a {@link JTextComponent}. Validates the input 1108 * on the fly and gives feedback about whether the time value is valid or not. 1109 * 1110 * Time values can be entered in one of four standard formats defined for the current locale. 1111 */ 1112 private static class TimeValidator extends AbstractTextComponentValidator { 1113 public static TimeValidator decorate(JTextComponent tc) { 1114 return new TimeValidator(tc); 1115 } 1116 1117 public TimeValidator(JTextComponent tc) { 1118 super(tc); 1119 } 1120 1121 @Override 1122 public boolean isValid() { 1123 if (getComponent().getText().trim().length() == 0) return true; 1124 return getDate() != null; 1125 } 1126 1127 public String getStandardTooltipTextAsHtml() { 1128 return "<html>" + getStandardTooltipText() + "</html>"; 1129 } 1130 1131 public String getStandardTooltipText() { 1132 Date date = new Date(); 1133 return tr( 1134 "Please enter a valid time in the usual format for your locale.<br>" 1135 + "Example: {0}<br>" 1136 + "Example: {1}<br>" 1137 + "Example: {2}<br>" 1138 + "Example: {3}<br>", 1139 DateFormat.getTimeInstance(DateFormat.SHORT, Locale.getDefault()).format(date), 1140 DateFormat.getTimeInstance(DateFormat.MEDIUM, Locale.getDefault()).format(date), 1141 DateFormat.getTimeInstance(DateFormat.LONG, Locale.getDefault()).format(date), 1142 DateFormat.getTimeInstance(DateFormat.FULL, Locale.getDefault()).format(date) 1143 ); 1144 } 1145 1146 @Override 1147 public void validate() { 1148 1149 if (!isValid()) { 1150 String msg = "<html>The current value isn't a valid time.<br>" + getStandardTooltipText() + "</html>"; 1151 feedbackInvalid(msg); 1152 return; 1153 } else { 1154 String msg = "<html>" + getStandardTooltipText() + "</html>"; 1155 feedbackValid(msg); 1156 } 1157 } 1158 1159 public Date getDate() { 1160 if (getComponent().getText().trim().length() == 0) 1161 return null; 1162 1163 for (int style : new int[]{DateFormat.SHORT, DateFormat.MEDIUM, DateFormat.LONG, DateFormat.FULL}) { 1164 try { 1165 return DateFormat.getTimeInstance(style, Locale.getDefault()).parse(getComponent().getText()); 1166 } catch(ParseException e) { 1167 continue; 1168 } 1169 } 1170 return null; 1171 } 1172 } 1173}