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.preferences.server.UserNameValidator; 034import org.openstreetmap.josm.gui.util.GuiHelper; 035import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator; 036import org.openstreetmap.josm.gui.widgets.BoundingBoxSelectionPanel; 037import org.openstreetmap.josm.gui.widgets.JMultilineLabel; 038import org.openstreetmap.josm.gui.widgets.JosmTextField; 039import org.openstreetmap.josm.gui.widgets.SelectAllOnFocusGainedDecorator; 040import org.openstreetmap.josm.gui.widgets.VerticallyScrollablePanel; 041import org.openstreetmap.josm.io.ChangesetQuery; 042import org.openstreetmap.josm.tools.CheckParameterUtil; 043 044/** 045 * This panel allows to specify a changeset query 046 * @since 2689 047 */ 048public class AdvancedChangesetQueryPanel extends JPanel { 049 050 private final JCheckBox cbUserRestriction = new JCheckBox(); 051 private final JCheckBox cbOpenAndCloseRestrictions = new JCheckBox(); 052 private final JCheckBox cbTimeRestrictions = new JCheckBox(); 053 private final JCheckBox cbBoundingBoxRestriction = new JCheckBox(); 054 private final UserRestrictionPanel pnlUserRestriction = new UserRestrictionPanel(); 055 private final OpenAndCloseStateRestrictionPanel pnlOpenAndCloseRestriction = new OpenAndCloseStateRestrictionPanel(); 056 private final TimeRestrictionPanel pnlTimeRestriction = new TimeRestrictionPanel(); 057 private final BBoxRestrictionPanel pnlBoundingBoxRestriction = new BBoxRestrictionPanel(); 058 059 /** 060 * Constructs a new {@code AdvancedChangesetQueryPanel}. 061 */ 062 public AdvancedChangesetQueryPanel() { 063 build(); 064 } 065 066 protected JPanel buildQueryPanel() { 067 ItemListener stateChangeHandler = new RestrictionGroupStateChangeHandler(); 068 JPanel pnl = new VerticallyScrollablePanel(new GridBagLayout()); 069 pnl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); 070 GridBagConstraints gc = new GridBagConstraints(); 071 072 // -- select changesets by a specific user 073 // 074 gc.anchor = GridBagConstraints.NORTHWEST; 075 gc.weightx = 0.0; 076 gc.fill = GridBagConstraints.HORIZONTAL; 077 pnl.add(cbUserRestriction, gc); 078 cbUserRestriction.addItemListener(stateChangeHandler); 079 080 gc.gridx = 1; 081 gc.weightx = 1.0; 082 pnl.add(new JMultilineLabel(tr("Select changesets owned by specific users")), gc); 083 084 gc.gridy = 1; 085 gc.gridx = 1; 086 gc.weightx = 1.0; 087 pnl.add(pnlUserRestriction, gc); 088 089 // -- restricting the query to open and closed changesets 090 // 091 gc.gridy = 2; 092 gc.gridx = 0; 093 gc.anchor = GridBagConstraints.NORTHWEST; 094 gc.weightx = 0.0; 095 gc.fill = GridBagConstraints.HORIZONTAL; 096 pnl.add(cbOpenAndCloseRestrictions, gc); 097 cbOpenAndCloseRestrictions.addItemListener(stateChangeHandler); 098 099 gc.gridx = 1; 100 gc.weightx = 1.0; 101 pnl.add(new JMultilineLabel(tr("Select changesets depending on whether they are open or closed")), gc); 102 103 gc.gridy = 3; 104 gc.gridx = 1; 105 gc.weightx = 1.0; 106 pnl.add(pnlOpenAndCloseRestriction, gc); 107 108 // -- restricting the query to a specific time 109 // 110 gc.gridy = 4; 111 gc.gridx = 0; 112 gc.anchor = GridBagConstraints.NORTHWEST; 113 gc.weightx = 0.0; 114 gc.fill = GridBagConstraints.HORIZONTAL; 115 pnl.add(cbTimeRestrictions, gc); 116 cbTimeRestrictions.addItemListener(stateChangeHandler); 117 118 gc.gridx = 1; 119 gc.weightx = 1.0; 120 pnl.add(new JMultilineLabel(tr("Select changesets based on the date/time they have been created or closed")), gc); 121 122 gc.gridy = 5; 123 gc.gridx = 1; 124 gc.weightx = 1.0; 125 pnl.add(pnlTimeRestriction, gc); 126 127 128 // -- restricting the query to a specific bounding box 129 // 130 gc.gridy = 6; 131 gc.gridx = 0; 132 gc.anchor = GridBagConstraints.NORTHWEST; 133 gc.weightx = 0.0; 134 gc.fill = GridBagConstraints.HORIZONTAL; 135 pnl.add(cbBoundingBoxRestriction, gc); 136 cbBoundingBoxRestriction.addItemListener(stateChangeHandler); 137 138 gc.gridx = 1; 139 gc.weightx = 1.0; 140 pnl.add(new JMultilineLabel(tr("Select only changesets related to a specific bounding box")), gc); 141 142 gc.gridy = 7; 143 gc.gridx = 1; 144 gc.weightx = 1.0; 145 pnl.add(pnlBoundingBoxRestriction, gc); 146 147 148 gc.gridy = 8; 149 gc.gridx = 0; 150 gc.gridwidth = 2; 151 gc.fill = GridBagConstraints.BOTH; 152 gc.weightx = 1.0; 153 gc.weighty = 1.0; 154 pnl.add(new JPanel(), gc); 155 156 return pnl; 157 } 158 159 protected final void build() { 160 setLayout(new BorderLayout()); 161 JScrollPane spQueryPanel = GuiHelper.embedInVerticalScrollPane(buildQueryPanel()); 162 add(spQueryPanel, BorderLayout.CENTER); 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) 243 return; 244 pnlUserRestriction.setVisible(cbUserRestriction.isSelected()); 245 } 246 247 protected void openCloseRestrictionStateChanged() { 248 if (pnlOpenAndCloseRestriction == null) 249 return; 250 pnlOpenAndCloseRestriction.setVisible(cbOpenAndCloseRestrictions.isSelected()); 251 } 252 253 protected void timeRestrictionsStateChanged() { 254 if (pnlTimeRestriction == null) 255 return; 256 pnlTimeRestriction.setVisible(cbTimeRestrictions.isSelected()); 257 } 258 259 protected void boundingBoxRestrictionChanged() { 260 if (pnlBoundingBoxRestriction == null) 261 return; 262 pnlBoundingBoxRestriction.setVisible(cbBoundingBoxRestriction.isSelected()); 263 } 264 265 @Override 266 public void itemStateChanged(ItemEvent e) { 267 if (e.getSource() == cbUserRestriction) { 268 userRestrictionStateChanged(); 269 } else if (e.getSource() == cbOpenAndCloseRestrictions) { 270 openCloseRestrictionStateChanged(); 271 } else if (e.getSource() == cbTimeRestrictions) { 272 timeRestrictionsStateChanged(); 273 } else if (e.getSource() == cbBoundingBoxRestriction) { 274 boundingBoxRestrictionChanged(); 275 } 276 validate(); 277 repaint(); 278 } 279 } 280 281 /** 282 * This is the panel for selecting whether the changeset query should be restricted to 283 * open or closed changesets 284 */ 285 private static class OpenAndCloseStateRestrictionPanel extends JPanel { 286 287 private final JRadioButton rbOpenOnly = new JRadioButton(); 288 private final JRadioButton rbClosedOnly = new JRadioButton(); 289 private final JRadioButton rbBoth = new JRadioButton(); 290 291 OpenAndCloseStateRestrictionPanel() { 292 build(); 293 } 294 295 protected void build() { 296 setLayout(new GridBagLayout()); 297 setBorder(BorderFactory.createCompoundBorder( 298 BorderFactory.createEmptyBorder(3, 3, 3, 3), 299 BorderFactory.createCompoundBorder( 300 BorderFactory.createLineBorder(Color.GRAY), 301 BorderFactory.createEmptyBorder(5, 5, 5, 5) 302 ) 303 )); 304 GridBagConstraints gc = new GridBagConstraints(); 305 gc.anchor = GridBagConstraints.NORTHWEST; 306 gc.fill = GridBagConstraints.HORIZONTAL; 307 gc.weightx = 0.0; 308 add(rbOpenOnly, gc); 309 310 gc.gridx = 1; 311 gc.weightx = 1.0; 312 add(new JMultilineLabel(tr("Query open changesets only")), gc); 313 314 gc.gridy = 1; 315 gc.gridx = 0; 316 gc.weightx = 0.0; 317 add(rbClosedOnly, gc); 318 319 gc.gridx = 1; 320 gc.weightx = 1.0; 321 add(new JMultilineLabel(tr("Query closed changesets only")), gc); 322 323 gc.gridy = 2; 324 gc.gridx = 0; 325 gc.weightx = 0.0; 326 add(rbBoth, gc); 327 328 gc.gridx = 1; 329 gc.weightx = 1.0; 330 add(new JMultilineLabel(tr("Query both open and closed changesets")), gc); 331 332 ButtonGroup bgRestrictions = new ButtonGroup(); 333 bgRestrictions.add(rbBoth); 334 bgRestrictions.add(rbClosedOnly); 335 bgRestrictions.add(rbOpenOnly); 336 } 337 338 public void startUserInput() { 339 restoreFromSettings(); 340 } 341 342 public void fillInQuery(ChangesetQuery query) { 343 if (rbBoth.isSelected()) { 344 query.beingClosed(true); 345 query.beingOpen(true); 346 } else if (rbOpenOnly.isSelected()) { 347 query.beingOpen(true); 348 } else if (rbClosedOnly.isSelected()) { 349 query.beingClosed(true); 350 } 351 } 352 353 public void rememberSettings() { 354 String prefRoot = "changeset-query.advanced.open-restrictions"; 355 if (rbBoth.isSelected()) { 356 Main.pref.put(prefRoot + ".query-type", "both"); 357 } else if (rbOpenOnly.isSelected()) { 358 Main.pref.put(prefRoot + ".query-type", "open"); 359 } else if (rbClosedOnly.isSelected()) { 360 Main.pref.put(prefRoot + ".query-type", "closed"); 361 } 362 } 363 364 public void restoreFromSettings() { 365 String prefRoot = "changeset-query.advanced.open-restrictions"; 366 String v = Main.pref.get(prefRoot + ".query-type", "open"); 367 rbBoth.setSelected("both".equals(v)); 368 rbOpenOnly.setSelected("open".equals(v)); 369 rbClosedOnly.setSelected("closed".equals(v)); 370 } 371 } 372 373 /** 374 * This is the panel for selecting whether the query should be restricted to a specific user 375 */ 376 private static class UserRestrictionPanel extends JPanel { 377 private final ButtonGroup bgUserRestrictions = new ButtonGroup(); 378 private final JRadioButton rbRestrictToMyself = new JRadioButton(); 379 private final JRadioButton rbRestrictToUid = new JRadioButton(); 380 private final JRadioButton rbRestrictToUserName = new JRadioButton(); 381 private final JosmTextField tfUid = new JosmTextField(10); 382 private transient UidInputFieldValidator valUid; 383 private final JosmTextField tfUserName = new JosmTextField(10); 384 private transient UserNameValidator valUserName; 385 private final JMultilineLabel lblRestrictedToMyself = new JMultilineLabel(tr("Only changesets owned by myself")); 386 387 UserRestrictionPanel() { 388 build(); 389 } 390 391 protected JPanel buildUidInputPanel() { 392 JPanel pnl = new JPanel(new GridBagLayout()); 393 GridBagConstraints gc = new GridBagConstraints(); 394 gc.fill = GridBagConstraints.HORIZONTAL; 395 gc.weightx = 0.0; 396 gc.insets = new Insets(0, 0, 0, 3); 397 pnl.add(new JLabel(tr("User ID:")), gc); 398 399 gc.gridx = 1; 400 pnl.add(tfUid, gc); 401 SelectAllOnFocusGainedDecorator.decorate(tfUid); 402 valUid = UidInputFieldValidator.decorate(tfUid); 403 404 // grab remaining space 405 gc.gridx = 2; 406 gc.weightx = 1.0; 407 pnl.add(new JPanel(), gc); 408 return pnl; 409 } 410 411 protected JPanel buildUserNameInputPanel() { 412 JPanel pnl = new JPanel(new GridBagLayout()); 413 GridBagConstraints gc = new GridBagConstraints(); 414 gc.fill = GridBagConstraints.HORIZONTAL; 415 gc.weightx = 0.0; 416 gc.insets = new Insets(0, 0, 0, 3); 417 pnl.add(new JLabel(tr("User name:")), gc); 418 419 gc.gridx = 1; 420 pnl.add(tfUserName, gc); 421 SelectAllOnFocusGainedDecorator.decorate(tfUserName); 422 valUserName = new UserNameValidator(tfUserName); 423 424 // grab remaining space 425 gc.gridx = 2; 426 gc.weightx = 1.0; 427 pnl.add(new JPanel(), gc); 428 return pnl; 429 } 430 431 protected void build() { 432 setLayout(new GridBagLayout()); 433 setBorder(BorderFactory.createCompoundBorder( 434 BorderFactory.createEmptyBorder(3, 3, 3, 3), 435 BorderFactory.createCompoundBorder( 436 BorderFactory.createLineBorder(Color.GRAY), 437 BorderFactory.createEmptyBorder(5, 5, 5, 5) 438 ) 439 )); 440 441 ItemListener userRestrictionChangeHandler = new UserRestrictionChangedHandler(); 442 GridBagConstraints gc = new GridBagConstraints(); 443 gc.anchor = GridBagConstraints.NORTHWEST; 444 gc.gridx = 0; 445 gc.fill = GridBagConstraints.HORIZONTAL; 446 gc.weightx = 0.0; 447 add(rbRestrictToMyself, gc); 448 rbRestrictToMyself.addItemListener(userRestrictionChangeHandler); 449 450 gc.gridx = 1; 451 gc.fill = GridBagConstraints.HORIZONTAL; 452 gc.weightx = 1.0; 453 add(lblRestrictedToMyself, gc); 454 455 gc.gridx = 0; 456 gc.gridy = 1; 457 gc.fill = GridBagConstraints.HORIZONTAL; 458 gc.weightx = 0.0; 459 add(rbRestrictToUid, gc); 460 rbRestrictToUid.addItemListener(userRestrictionChangeHandler); 461 462 gc.gridx = 1; 463 gc.fill = GridBagConstraints.HORIZONTAL; 464 gc.weightx = 1.0; 465 add(new JMultilineLabel(tr("Only changesets owned by the user with the following user ID")), gc); 466 467 gc.gridx = 1; 468 gc.gridy = 2; 469 gc.fill = GridBagConstraints.HORIZONTAL; 470 gc.weightx = 1.0; 471 add(buildUidInputPanel(), gc); 472 473 gc.gridx = 0; 474 gc.gridy = 3; 475 gc.fill = GridBagConstraints.HORIZONTAL; 476 gc.weightx = 0.0; 477 add(rbRestrictToUserName, gc); 478 rbRestrictToUserName.addItemListener(userRestrictionChangeHandler); 479 480 gc.gridx = 1; 481 gc.fill = GridBagConstraints.HORIZONTAL; 482 gc.weightx = 1.0; 483 add(new JMultilineLabel(tr("Only changesets owned by the user with the following user name")), gc); 484 485 gc.gridx = 1; 486 gc.gridy = 4; 487 gc.fill = GridBagConstraints.HORIZONTAL; 488 gc.weightx = 1.0; 489 add(buildUserNameInputPanel(), gc); 490 491 bgUserRestrictions.add(rbRestrictToMyself); 492 bgUserRestrictions.add(rbRestrictToUid); 493 bgUserRestrictions.add(rbRestrictToUserName); 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 if query is null 517 * @throws IllegalStateException if one of the available values for query parameters in 518 * this panel isn't valid 519 */ 520 public void fillInQuery(ChangesetQuery query) { 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( 530 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( 540 tr("Cannot restrict the changeset query to the user name ''{0}''", tfUserName.getText())); 541 query.forUser(tfUserName.getText()); 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 final JRadioButton rbClosedAfter = new JRadioButton(); 640 private final JRadioButton rbClosedAfterAndCreatedBefore = new JRadioButton(); 641 private final JosmTextField tfClosedAfterDate1 = new JosmTextField(); 642 private transient DateValidator valClosedAfterDate1; 643 private final JosmTextField tfClosedAfterTime1 = new JosmTextField(); 644 private transient TimeValidator valClosedAfterTime1; 645 private final JosmTextField tfClosedAfterDate2 = new JosmTextField(); 646 private transient DateValidator valClosedAfterDate2; 647 private final JosmTextField tfClosedAfterTime2 = new JosmTextField(); 648 private transient TimeValidator valClosedAfterTime2; 649 private final JosmTextField tfCreatedBeforeDate = new JosmTextField(); 650 private transient DateValidator valCreatedBeforeDate; 651 private final JosmTextField tfCreatedBeforeTime = new JosmTextField(); 652 private transient TimeValidator valCreatedBeforeTime; 653 654 TimeRestrictionPanel() { 655 build(); 656 } 657 658 protected JPanel buildClosedAfterInputPanel() { 659 JPanel pnl = new JPanel(new GridBagLayout()); 660 GridBagConstraints gc = new GridBagConstraints(); 661 gc.fill = GridBagConstraints.HORIZONTAL; 662 gc.weightx = 0.0; 663 gc.insets = new Insets(0, 0, 0, 3); 664 pnl.add(new JLabel(tr("Date: ")), gc); 665 666 gc.gridx = 1; 667 gc.weightx = 0.7; 668 pnl.add(tfClosedAfterDate1, gc); 669 SelectAllOnFocusGainedDecorator.decorate(tfClosedAfterDate1); 670 valClosedAfterDate1 = DateValidator.decorate(tfClosedAfterDate1); 671 tfClosedAfterDate1.setToolTipText(valClosedAfterDate1.getStandardTooltipTextAsHtml()); 672 673 gc.gridx = 2; 674 gc.weightx = 0.0; 675 pnl.add(new JLabel(tr("Time:")), gc); 676 677 gc.gridx = 3; 678 gc.weightx = 0.3; 679 pnl.add(tfClosedAfterTime1, gc); 680 SelectAllOnFocusGainedDecorator.decorate(tfClosedAfterTime1); 681 valClosedAfterTime1 = TimeValidator.decorate(tfClosedAfterTime1); 682 tfClosedAfterTime1.setToolTipText(valClosedAfterTime1.getStandardTooltipTextAsHtml()); 683 return pnl; 684 } 685 686 protected JPanel buildClosedAfterAndCreatedBeforeInputPanel() { 687 JPanel pnl = new JPanel(new GridBagLayout()); 688 GridBagConstraints gc = new GridBagConstraints(); 689 gc.fill = GridBagConstraints.HORIZONTAL; 690 gc.weightx = 0.0; 691 gc.insets = new Insets(0, 0, 0, 3); 692 pnl.add(new JLabel(tr("Closed after - ")), gc); 693 694 gc.gridx = 1; 695 gc.fill = GridBagConstraints.HORIZONTAL; 696 gc.weightx = 0.0; 697 gc.insets = new Insets(0, 0, 0, 3); 698 pnl.add(new JLabel(tr("Date:")), gc); 699 700 gc.gridx = 2; 701 gc.weightx = 0.7; 702 pnl.add(tfClosedAfterDate2, gc); 703 SelectAllOnFocusGainedDecorator.decorate(tfClosedAfterDate2); 704 valClosedAfterDate2 = DateValidator.decorate(tfClosedAfterDate2); 705 tfClosedAfterDate2.setToolTipText(valClosedAfterDate2.getStandardTooltipTextAsHtml()); 706 gc.gridx = 3; 707 gc.weightx = 0.0; 708 pnl.add(new JLabel(tr("Time:")), gc); 709 710 gc.gridx = 4; 711 gc.weightx = 0.3; 712 pnl.add(tfClosedAfterTime2, gc); 713 SelectAllOnFocusGainedDecorator.decorate(tfClosedAfterTime2); 714 valClosedAfterTime2 = TimeValidator.decorate(tfClosedAfterTime2); 715 tfClosedAfterTime2.setToolTipText(valClosedAfterTime2.getStandardTooltipTextAsHtml()); 716 717 gc.gridy = 1; 718 gc.gridx = 0; 719 gc.fill = GridBagConstraints.HORIZONTAL; 720 gc.weightx = 0.0; 721 gc.insets = new Insets(0, 0, 0, 3); 722 pnl.add(new JLabel(tr("Created before - ")), gc); 723 724 gc.gridx = 1; 725 gc.fill = GridBagConstraints.HORIZONTAL; 726 gc.weightx = 0.0; 727 gc.insets = new Insets(0, 0, 0, 3); 728 pnl.add(new JLabel(tr("Date:")), gc); 729 730 gc.gridx = 2; 731 gc.weightx = 0.7; 732 pnl.add(tfCreatedBeforeDate, gc); 733 SelectAllOnFocusGainedDecorator.decorate(tfCreatedBeforeDate); 734 valCreatedBeforeDate = DateValidator.decorate(tfCreatedBeforeDate); 735 tfCreatedBeforeDate.setToolTipText(valCreatedBeforeDate.getStandardTooltipTextAsHtml()); 736 737 gc.gridx = 3; 738 gc.weightx = 0.0; 739 pnl.add(new JLabel(tr("Time:")), gc); 740 741 gc.gridx = 4; 742 gc.weightx = 0.3; 743 pnl.add(tfCreatedBeforeTime, gc); 744 SelectAllOnFocusGainedDecorator.decorate(tfCreatedBeforeTime); 745 valCreatedBeforeTime = TimeValidator.decorate(tfCreatedBeforeTime); 746 tfCreatedBeforeTime.setToolTipText(valCreatedBeforeDate.getStandardTooltipTextAsHtml()); 747 748 return pnl; 749 } 750 751 protected void build() { 752 setLayout(new GridBagLayout()); 753 setBorder(BorderFactory.createCompoundBorder( 754 BorderFactory.createEmptyBorder(3, 3, 3, 3), 755 BorderFactory.createCompoundBorder( 756 BorderFactory.createLineBorder(Color.GRAY), 757 BorderFactory.createEmptyBorder(5, 5, 5, 5) 758 ) 759 )); 760 761 // -- changesets closed after a specific date/time 762 // 763 GridBagConstraints gc = new GridBagConstraints(); 764 gc.anchor = GridBagConstraints.NORTHWEST; 765 gc.gridx = 0; 766 gc.fill = GridBagConstraints.HORIZONTAL; 767 gc.weightx = 0.0; 768 add(rbClosedAfter, gc); 769 770 gc.gridx = 1; 771 gc.fill = GridBagConstraints.HORIZONTAL; 772 gc.weightx = 1.0; 773 add(new JMultilineLabel(tr("Only changesets closed after the following date/time")), gc); 774 775 gc.gridx = 1; 776 gc.gridy = 1; 777 gc.fill = GridBagConstraints.HORIZONTAL; 778 gc.weightx = 1.0; 779 add(buildClosedAfterInputPanel(), gc); 780 781 // -- changesets closed after a specific date/time and created before a specific date time 782 // 783 gc = new GridBagConstraints(); 784 gc.anchor = GridBagConstraints.NORTHWEST; 785 gc.gridy = 2; 786 gc.gridx = 0; 787 gc.fill = GridBagConstraints.HORIZONTAL; 788 gc.weightx = 0.0; 789 add(rbClosedAfterAndCreatedBefore, gc); 790 791 gc.gridx = 1; 792 gc.fill = GridBagConstraints.HORIZONTAL; 793 gc.weightx = 1.0; 794 add(new JMultilineLabel(tr("Only changesets closed after and created before a specific date/time")), gc); 795 796 gc.gridx = 1; 797 gc.gridy = 3; 798 gc.fill = GridBagConstraints.HORIZONTAL; 799 gc.weightx = 1.0; 800 add(buildClosedAfterAndCreatedBeforeInputPanel(), gc); 801 802 ButtonGroup bg = new ButtonGroup(); 803 bg.add(rbClosedAfter); 804 bg.add(rbClosedAfterAndCreatedBefore); 805 806 ItemListener restrictionChangeHandler = new TimeRestrictionChangedHandler(); 807 rbClosedAfter.addItemListener(restrictionChangeHandler); 808 rbClosedAfterAndCreatedBefore.addItemListener(restrictionChangeHandler); 809 810 rbClosedAfter.setSelected(true); 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) { 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()) 867 return; 868 HelpAwareOptionPane.showOptionDialog( 869 this, 870 tr( 871 "<html>Please enter valid date/time values to restrict<br>" 872 + "the query to a specific time range.</html>" 873 ), 874 tr("Invalid date/time values"), 875 JOptionPane.ERROR_MESSAGE, 876 HelpUtil.ht("/Dialog/ChangesetQueryDialog#InvalidDateTimeValues") 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 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()) 953 return; 954 HelpAwareOptionPane.showOptionDialog( 955 this, 956 tr( 957 "<html>Please enter valid longitude/latitude values to restrict<br>" + 958 "the changeset query to a specific bounding box.</html>" 959 ), 960 tr("Invalid bounding box"), 961 JOptionPane.ERROR_MESSAGE, 962 HelpUtil.ht("/Dialog/ChangesetQueryDialog#InvalidBoundingBox") 963 ); 964 } 965 } 966 967 /** 968 * Validator for user ids entered in a {@link JTextComponent}. 969 * 970 */ 971 private static class UidInputFieldValidator extends AbstractTextComponentValidator { 972 UidInputFieldValidator(JTextComponent tc) { 973 super(tc); 974 } 975 976 public static UidInputFieldValidator decorate(JTextComponent tc) { 977 return new UidInputFieldValidator(tc); 978 } 979 980 @Override 981 public boolean isValid() { 982 return getUid() > 0; 983 } 984 985 @Override 986 public void validate() { 987 String value = getComponent().getText(); 988 if (value == null || value.trim().isEmpty()) { 989 feedbackInvalid(""); 990 return; 991 } 992 try { 993 int uid = Integer.parseInt(value); 994 if (uid <= 0) { 995 feedbackInvalid(tr("The current value is not a valid user ID. Please enter an integer value > 0")); 996 return; 997 } 998 } catch (NumberFormatException e) { 999 feedbackInvalid(tr("The current value is not a valid user ID. Please enter an integer value > 0")); 1000 return; 1001 } 1002 feedbackValid(tr("Please enter an integer value > 0")); 1003 } 1004 1005 public int getUid() { 1006 String value = getComponent().getText(); 1007 if (value == null || value.trim().isEmpty()) return 0; 1008 try { 1009 int uid = Integer.parseInt(value.trim()); 1010 if (uid > 0) 1011 return uid; 1012 return 0; 1013 } catch (NumberFormatException e) { 1014 return 0; 1015 } 1016 } 1017 } 1018 1019 /** 1020 * Validates dates entered as text in a {@link JTextComponent}. Validates the input 1021 * on the fly and gives feedback about whether the date is valid or not. 1022 * 1023 * Dates can be entered in one of four standard formats defined for the current locale. 1024 */ 1025 private static class DateValidator extends AbstractTextComponentValidator { 1026 DateValidator(JTextComponent tc) { 1027 super(tc); 1028 } 1029 1030 public static DateValidator decorate(JTextComponent tc) { 1031 return new DateValidator(tc); 1032 } 1033 1034 @Override 1035 public boolean isValid() { 1036 return getDate() != null; 1037 } 1038 1039 public String getStandardTooltipTextAsHtml() { 1040 return "<html>" + getStandardTooltipText() + "</html>"; 1041 } 1042 1043 public String getStandardTooltipText() { 1044 Date date = new Date(); 1045 return tr( 1046 "Please enter a date in the usual format for your locale.<br>" 1047 + "Example: {0}<br>" 1048 + "Example: {1}<br>" 1049 + "Example: {2}<br>" 1050 + "Example: {3}<br>", 1051 DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault()).format(date), 1052 DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault()).format(date), 1053 DateFormat.getDateInstance(DateFormat.LONG, Locale.getDefault()).format(date), 1054 DateFormat.getDateInstance(DateFormat.FULL, Locale.getDefault()).format(date) 1055 ); 1056 } 1057 1058 @Override 1059 public void validate() { 1060 if (!isValid()) { 1061 String msg = "<html>The current value isn't a valid date.<br>" + getStandardTooltipText()+ "</html>"; 1062 feedbackInvalid(msg); 1063 return; 1064 } else { 1065 String msg = "<html>" + getStandardTooltipText() + "</html>"; 1066 feedbackValid(msg); 1067 } 1068 } 1069 1070 public Date getDate() { 1071 for (int format: new int[] {DateFormat.SHORT, DateFormat.MEDIUM, DateFormat.LONG, DateFormat.FULL}) { 1072 DateFormat df = DateFormat.getDateInstance(format); 1073 try { 1074 return df.parse(getComponent().getText()); 1075 } catch (ParseException e) { 1076 // Try next format 1077 if (Main.isTraceEnabled()) { 1078 Main.trace(e.getMessage()); 1079 } 1080 } 1081 } 1082 return null; 1083 } 1084 } 1085 1086 /** 1087 * Validates time values entered as text in a {@link JTextComponent}. Validates the input 1088 * on the fly and gives feedback about whether the time value is valid or not. 1089 * 1090 * Time values can be entered in one of four standard formats defined for the current locale. 1091 */ 1092 private static class TimeValidator extends AbstractTextComponentValidator { 1093 TimeValidator(JTextComponent tc) { 1094 super(tc); 1095 } 1096 1097 public static TimeValidator decorate(JTextComponent tc) { 1098 return new TimeValidator(tc); 1099 } 1100 1101 @Override 1102 public boolean isValid() { 1103 if (getComponent().getText().trim().isEmpty()) 1104 return true; 1105 return getDate() != null; 1106 } 1107 1108 public String getStandardTooltipTextAsHtml() { 1109 return "<html>" + getStandardTooltipText() + "</html>"; 1110 } 1111 1112 public String getStandardTooltipText() { 1113 Date date = new Date(); 1114 return tr( 1115 "Please enter a valid time in the usual format for your locale.<br>" 1116 + "Example: {0}<br>" 1117 + "Example: {1}<br>" 1118 + "Example: {2}<br>" 1119 + "Example: {3}<br>", 1120 DateFormat.getTimeInstance(DateFormat.SHORT, Locale.getDefault()).format(date), 1121 DateFormat.getTimeInstance(DateFormat.MEDIUM, Locale.getDefault()).format(date), 1122 DateFormat.getTimeInstance(DateFormat.LONG, Locale.getDefault()).format(date), 1123 DateFormat.getTimeInstance(DateFormat.FULL, Locale.getDefault()).format(date) 1124 ); 1125 } 1126 1127 @Override 1128 public void validate() { 1129 if (!isValid()) { 1130 String msg = "<html>The current value isn't a valid time.<br>" + getStandardTooltipText() + "</html>"; 1131 feedbackInvalid(msg); 1132 return; 1133 } else { 1134 String msg = "<html>" + getStandardTooltipText() + "</html>"; 1135 feedbackValid(msg); 1136 } 1137 } 1138 1139 public Date getDate() { 1140 if (getComponent().getText().trim().isEmpty()) 1141 return null; 1142 1143 for (int style : new int[]{DateFormat.SHORT, DateFormat.MEDIUM, DateFormat.LONG, DateFormat.FULL}) { 1144 try { 1145 return DateFormat.getTimeInstance(style, Locale.getDefault()).parse(getComponent().getText()); 1146 } catch (ParseException e) { 1147 continue; 1148 } 1149 } 1150 return null; 1151 } 1152 } 1153}