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.time.LocalDate; 014import java.time.LocalTime; 015import java.time.ZoneId; 016import java.time.ZonedDateTime; 017import java.time.format.DateTimeFormatter; 018import java.time.format.DateTimeParseException; 019import java.time.format.FormatStyle; 020import java.util.Date; 021 022import javax.swing.BorderFactory; 023import javax.swing.ButtonGroup; 024import javax.swing.JCheckBox; 025import javax.swing.JLabel; 026import javax.swing.JOptionPane; 027import javax.swing.JPanel; 028import javax.swing.JRadioButton; 029import javax.swing.JScrollPane; 030import javax.swing.text.JTextComponent; 031 032import org.openstreetmap.josm.Main; 033import org.openstreetmap.josm.gui.HelpAwareOptionPane; 034import org.openstreetmap.josm.gui.JosmUserIdentityManager; 035import org.openstreetmap.josm.gui.help.HelpUtil; 036import org.openstreetmap.josm.gui.preferences.server.UserNameValidator; 037import org.openstreetmap.josm.gui.util.GuiHelper; 038import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator; 039import org.openstreetmap.josm.gui.widgets.BoundingBoxSelectionPanel; 040import org.openstreetmap.josm.gui.widgets.JMultilineLabel; 041import org.openstreetmap.josm.gui.widgets.JosmTextField; 042import org.openstreetmap.josm.gui.widgets.SelectAllOnFocusGainedDecorator; 043import org.openstreetmap.josm.gui.widgets.VerticallyScrollablePanel; 044import org.openstreetmap.josm.io.ChangesetQuery; 045import org.openstreetmap.josm.tools.CheckParameterUtil; 046 047/** 048 * This panel allows to specify a changeset query 049 * @since 2689 050 */ 051public class AdvancedChangesetQueryPanel extends JPanel { 052 053 private final JCheckBox cbUserRestriction = new JCheckBox(); 054 private final JCheckBox cbOpenAndCloseRestrictions = new JCheckBox(); 055 private final JCheckBox cbTimeRestrictions = new JCheckBox(); 056 private final JCheckBox cbBoundingBoxRestriction = new JCheckBox(); 057 private final UserRestrictionPanel pnlUserRestriction = new UserRestrictionPanel(); 058 private final OpenAndCloseStateRestrictionPanel pnlOpenAndCloseRestriction = new OpenAndCloseStateRestrictionPanel(); 059 private final TimeRestrictionPanel pnlTimeRestriction = new TimeRestrictionPanel(); 060 private final BBoxRestrictionPanel pnlBoundingBoxRestriction = new BBoxRestrictionPanel(); 061 062 /** 063 * Constructs a new {@code AdvancedChangesetQueryPanel}. 064 */ 065 public AdvancedChangesetQueryPanel() { 066 build(); 067 } 068 069 protected JPanel buildQueryPanel() { 070 ItemListener stateChangeHandler = new RestrictionGroupStateChangeHandler(); 071 JPanel pnl = new VerticallyScrollablePanel(new GridBagLayout()); 072 pnl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); 073 GridBagConstraints gc = new GridBagConstraints(); 074 075 // -- select changesets by a specific user 076 // 077 gc.anchor = GridBagConstraints.NORTHWEST; 078 gc.weightx = 0.0; 079 gc.fill = GridBagConstraints.HORIZONTAL; 080 pnl.add(cbUserRestriction, gc); 081 cbUserRestriction.addItemListener(stateChangeHandler); 082 083 gc.gridx = 1; 084 gc.weightx = 1.0; 085 pnl.add(new JMultilineLabel(tr("Select changesets owned by specific users")), gc); 086 087 gc.gridy = 1; 088 gc.gridx = 1; 089 gc.weightx = 1.0; 090 pnl.add(pnlUserRestriction, gc); 091 092 // -- restricting the query to open and closed changesets 093 // 094 gc.gridy = 2; 095 gc.gridx = 0; 096 gc.anchor = GridBagConstraints.NORTHWEST; 097 gc.weightx = 0.0; 098 gc.fill = GridBagConstraints.HORIZONTAL; 099 pnl.add(cbOpenAndCloseRestrictions, gc); 100 cbOpenAndCloseRestrictions.addItemListener(stateChangeHandler); 101 102 gc.gridx = 1; 103 gc.weightx = 1.0; 104 pnl.add(new JMultilineLabel(tr("Select changesets depending on whether they are open or closed")), gc); 105 106 gc.gridy = 3; 107 gc.gridx = 1; 108 gc.weightx = 1.0; 109 pnl.add(pnlOpenAndCloseRestriction, gc); 110 111 // -- restricting the query to a specific time 112 // 113 gc.gridy = 4; 114 gc.gridx = 0; 115 gc.anchor = GridBagConstraints.NORTHWEST; 116 gc.weightx = 0.0; 117 gc.fill = GridBagConstraints.HORIZONTAL; 118 pnl.add(cbTimeRestrictions, gc); 119 cbTimeRestrictions.addItemListener(stateChangeHandler); 120 121 gc.gridx = 1; 122 gc.weightx = 1.0; 123 pnl.add(new JMultilineLabel(tr("Select changesets based on the date/time they have been created or closed")), gc); 124 125 gc.gridy = 5; 126 gc.gridx = 1; 127 gc.weightx = 1.0; 128 pnl.add(pnlTimeRestriction, gc); 129 130 131 // -- restricting the query to a specific bounding box 132 // 133 gc.gridy = 6; 134 gc.gridx = 0; 135 gc.anchor = GridBagConstraints.NORTHWEST; 136 gc.weightx = 0.0; 137 gc.fill = GridBagConstraints.HORIZONTAL; 138 pnl.add(cbBoundingBoxRestriction, gc); 139 cbBoundingBoxRestriction.addItemListener(stateChangeHandler); 140 141 gc.gridx = 1; 142 gc.weightx = 1.0; 143 pnl.add(new JMultilineLabel(tr("Select only changesets related to a specific bounding box")), gc); 144 145 gc.gridy = 7; 146 gc.gridx = 1; 147 gc.weightx = 1.0; 148 pnl.add(pnlBoundingBoxRestriction, gc); 149 150 151 gc.gridy = 8; 152 gc.gridx = 0; 153 gc.gridwidth = 2; 154 gc.fill = GridBagConstraints.BOTH; 155 gc.weightx = 1.0; 156 gc.weighty = 1.0; 157 pnl.add(new JPanel(), gc); 158 159 return pnl; 160 } 161 162 protected final void build() { 163 setLayout(new BorderLayout()); 164 JScrollPane spQueryPanel = GuiHelper.embedInVerticalScrollPane(buildQueryPanel()); 165 add(spQueryPanel, BorderLayout.CENTER); 166 } 167 168 public void startUserInput() { 169 restoreFromSettings(); 170 pnlBoundingBoxRestriction.setVisible(cbBoundingBoxRestriction.isSelected()); 171 pnlOpenAndCloseRestriction.setVisible(cbOpenAndCloseRestrictions.isSelected()); 172 pnlTimeRestriction.setVisible(cbTimeRestrictions.isSelected()); 173 pnlUserRestriction.setVisible(cbUserRestriction.isSelected()); 174 pnlOpenAndCloseRestriction.startUserInput(); 175 pnlUserRestriction.startUserInput(); 176 pnlTimeRestriction.startUserInput(); 177 } 178 179 public void displayMessageIfInvalid() { 180 if (cbUserRestriction.isSelected()) { 181 if (!pnlUserRestriction.isValidChangesetQuery()) { 182 pnlUserRestriction.displayMessageIfInvalid(); 183 } 184 } else if (cbTimeRestrictions.isSelected()) { 185 if (!pnlTimeRestriction.isValidChangesetQuery()) { 186 pnlTimeRestriction.displayMessageIfInvalid(); 187 } 188 } else if (cbBoundingBoxRestriction.isSelected()) { 189 if (!pnlBoundingBoxRestriction.isValidChangesetQuery()) { 190 pnlBoundingBoxRestriction.displayMessageIfInvalid(); 191 } 192 } 193 } 194 195 /** 196 * Builds the changeset query based on the data entered in the form. 197 * 198 * @return the changeset query. null, if the data entered doesn't represent 199 * a valid changeset query. 200 */ 201 public ChangesetQuery buildChangesetQuery() { 202 ChangesetQuery query = new ChangesetQuery(); 203 if (cbUserRestriction.isSelected()) { 204 if (!pnlUserRestriction.isValidChangesetQuery()) 205 return null; 206 pnlUserRestriction.fillInQuery(query); 207 } 208 if (cbOpenAndCloseRestrictions.isSelected()) { 209 // don't have to check whether it's valid. It always is. 210 pnlOpenAndCloseRestriction.fillInQuery(query); 211 } 212 if (cbBoundingBoxRestriction.isSelected()) { 213 if (!pnlBoundingBoxRestriction.isValidChangesetQuery()) 214 return null; 215 pnlBoundingBoxRestriction.fillInQuery(query); 216 } 217 if (cbTimeRestrictions.isSelected()) { 218 if (!pnlTimeRestriction.isValidChangesetQuery()) 219 return null; 220 pnlTimeRestriction.fillInQuery(query); 221 } 222 return query; 223 } 224 225 public void rememberSettings() { 226 Main.pref.put("changeset-query.advanced.user-restrictions", cbUserRestriction.isSelected()); 227 Main.pref.put("changeset-query.advanced.open-restrictions", cbOpenAndCloseRestrictions.isSelected()); 228 Main.pref.put("changeset-query.advanced.time-restrictions", cbTimeRestrictions.isSelected()); 229 Main.pref.put("changeset-query.advanced.bbox-restrictions", cbBoundingBoxRestriction.isSelected()); 230 231 pnlUserRestriction.rememberSettings(); 232 pnlOpenAndCloseRestriction.rememberSettings(); 233 pnlTimeRestriction.rememberSettings(); 234 } 235 236 public void restoreFromSettings() { 237 cbUserRestriction.setSelected(Main.pref.getBoolean("changeset-query.advanced.user-restrictions", false)); 238 cbOpenAndCloseRestrictions.setSelected(Main.pref.getBoolean("changeset-query.advanced.open-restrictions", false)); 239 cbTimeRestrictions.setSelected(Main.pref.getBoolean("changeset-query.advanced.time-restrictions", false)); 240 cbBoundingBoxRestriction.setSelected(Main.pref.getBoolean("changeset-query.advanced.bbox-restrictions", false)); 241 } 242 243 class RestrictionGroupStateChangeHandler implements ItemListener { 244 protected void userRestrictionStateChanged() { 245 if (pnlUserRestriction == null) 246 return; 247 pnlUserRestriction.setVisible(cbUserRestriction.isSelected()); 248 } 249 250 protected void openCloseRestrictionStateChanged() { 251 if (pnlOpenAndCloseRestriction == null) 252 return; 253 pnlOpenAndCloseRestriction.setVisible(cbOpenAndCloseRestrictions.isSelected()); 254 } 255 256 protected void timeRestrictionsStateChanged() { 257 if (pnlTimeRestriction == null) 258 return; 259 pnlTimeRestriction.setVisible(cbTimeRestrictions.isSelected()); 260 } 261 262 protected void boundingBoxRestrictionChanged() { 263 if (pnlBoundingBoxRestriction == null) 264 return; 265 pnlBoundingBoxRestriction.setVisible(cbBoundingBoxRestriction.isSelected()); 266 } 267 268 @Override 269 public void itemStateChanged(ItemEvent e) { 270 if (e.getSource() == cbUserRestriction) { 271 userRestrictionStateChanged(); 272 } else if (e.getSource() == cbOpenAndCloseRestrictions) { 273 openCloseRestrictionStateChanged(); 274 } else if (e.getSource() == cbTimeRestrictions) { 275 timeRestrictionsStateChanged(); 276 } else if (e.getSource() == cbBoundingBoxRestriction) { 277 boundingBoxRestrictionChanged(); 278 } 279 validate(); 280 repaint(); 281 } 282 } 283 284 /** 285 * This is the panel for selecting whether the changeset query should be restricted to 286 * open or closed changesets 287 */ 288 private static class OpenAndCloseStateRestrictionPanel extends JPanel { 289 290 private final JRadioButton rbOpenOnly = new JRadioButton(); 291 private final JRadioButton rbClosedOnly = new JRadioButton(); 292 private final JRadioButton rbBoth = new JRadioButton(); 293 294 OpenAndCloseStateRestrictionPanel() { 295 build(); 296 } 297 298 protected void build() { 299 setLayout(new GridBagLayout()); 300 setBorder(BorderFactory.createCompoundBorder( 301 BorderFactory.createEmptyBorder(3, 3, 3, 3), 302 BorderFactory.createCompoundBorder( 303 BorderFactory.createLineBorder(Color.GRAY), 304 BorderFactory.createEmptyBorder(5, 5, 5, 5) 305 ) 306 )); 307 GridBagConstraints gc = new GridBagConstraints(); 308 gc.anchor = GridBagConstraints.NORTHWEST; 309 gc.fill = GridBagConstraints.HORIZONTAL; 310 gc.weightx = 0.0; 311 add(rbOpenOnly, gc); 312 313 gc.gridx = 1; 314 gc.weightx = 1.0; 315 add(new JMultilineLabel(tr("Query open changesets only")), gc); 316 317 gc.gridy = 1; 318 gc.gridx = 0; 319 gc.weightx = 0.0; 320 add(rbClosedOnly, gc); 321 322 gc.gridx = 1; 323 gc.weightx = 1.0; 324 add(new JMultilineLabel(tr("Query closed changesets only")), gc); 325 326 gc.gridy = 2; 327 gc.gridx = 0; 328 gc.weightx = 0.0; 329 add(rbBoth, gc); 330 331 gc.gridx = 1; 332 gc.weightx = 1.0; 333 add(new JMultilineLabel(tr("Query both open and closed changesets")), gc); 334 335 ButtonGroup bgRestrictions = new ButtonGroup(); 336 bgRestrictions.add(rbBoth); 337 bgRestrictions.add(rbClosedOnly); 338 bgRestrictions.add(rbOpenOnly); 339 } 340 341 public void startUserInput() { 342 restoreFromSettings(); 343 } 344 345 public void fillInQuery(ChangesetQuery query) { 346 if (rbBoth.isSelected()) { 347 query.beingClosed(true); 348 query.beingOpen(true); 349 } else if (rbOpenOnly.isSelected()) { 350 query.beingOpen(true); 351 } else if (rbClosedOnly.isSelected()) { 352 query.beingClosed(true); 353 } 354 } 355 356 public void rememberSettings() { 357 String prefRoot = "changeset-query.advanced.open-restrictions"; 358 if (rbBoth.isSelected()) { 359 Main.pref.put(prefRoot + ".query-type", "both"); 360 } else if (rbOpenOnly.isSelected()) { 361 Main.pref.put(prefRoot + ".query-type", "open"); 362 } else if (rbClosedOnly.isSelected()) { 363 Main.pref.put(prefRoot + ".query-type", "closed"); 364 } 365 } 366 367 public void restoreFromSettings() { 368 String prefRoot = "changeset-query.advanced.open-restrictions"; 369 String v = Main.pref.get(prefRoot + ".query-type", "open"); 370 rbBoth.setSelected("both".equals(v)); 371 rbOpenOnly.setSelected("open".equals(v)); 372 rbClosedOnly.setSelected("closed".equals(v)); 373 } 374 } 375 376 /** 377 * This is the panel for selecting whether the query should be restricted to a specific user 378 */ 379 private static class UserRestrictionPanel extends JPanel { 380 private final ButtonGroup bgUserRestrictions = new ButtonGroup(); 381 private final JRadioButton rbRestrictToMyself = new JRadioButton(); 382 private final JRadioButton rbRestrictToUid = new JRadioButton(); 383 private final JRadioButton rbRestrictToUserName = new JRadioButton(); 384 private final JosmTextField tfUid = new JosmTextField(10); 385 private transient UidInputFieldValidator valUid; 386 private final JosmTextField tfUserName = new JosmTextField(10); 387 private transient UserNameValidator valUserName; 388 private final JMultilineLabel lblRestrictedToMyself = new JMultilineLabel(tr("Only changesets owned by myself")); 389 390 UserRestrictionPanel() { 391 build(); 392 } 393 394 protected JPanel buildUidInputPanel() { 395 JPanel pnl = new JPanel(new GridBagLayout()); 396 GridBagConstraints gc = new GridBagConstraints(); 397 gc.fill = GridBagConstraints.HORIZONTAL; 398 gc.weightx = 0.0; 399 gc.insets = new Insets(0, 0, 0, 3); 400 pnl.add(new JLabel(tr("User ID:")), gc); 401 402 gc.gridx = 1; 403 pnl.add(tfUid, gc); 404 SelectAllOnFocusGainedDecorator.decorate(tfUid); 405 valUid = UidInputFieldValidator.decorate(tfUid); 406 407 // grab remaining space 408 gc.gridx = 2; 409 gc.weightx = 1.0; 410 pnl.add(new JPanel(), gc); 411 return pnl; 412 } 413 414 protected JPanel buildUserNameInputPanel() { 415 JPanel pnl = new JPanel(new GridBagLayout()); 416 GridBagConstraints gc = new GridBagConstraints(); 417 gc.fill = GridBagConstraints.HORIZONTAL; 418 gc.weightx = 0.0; 419 gc.insets = new Insets(0, 0, 0, 3); 420 pnl.add(new JLabel(tr("User name:")), gc); 421 422 gc.gridx = 1; 423 pnl.add(tfUserName, gc); 424 SelectAllOnFocusGainedDecorator.decorate(tfUserName); 425 valUserName = new UserNameValidator(tfUserName); 426 427 // grab remaining space 428 gc.gridx = 2; 429 gc.weightx = 1.0; 430 pnl.add(new JPanel(), gc); 431 return pnl; 432 } 433 434 protected void build() { 435 setLayout(new GridBagLayout()); 436 setBorder(BorderFactory.createCompoundBorder( 437 BorderFactory.createEmptyBorder(3, 3, 3, 3), 438 BorderFactory.createCompoundBorder( 439 BorderFactory.createLineBorder(Color.GRAY), 440 BorderFactory.createEmptyBorder(5, 5, 5, 5) 441 ) 442 )); 443 444 ItemListener userRestrictionChangeHandler = new UserRestrictionChangedHandler(); 445 GridBagConstraints gc = new GridBagConstraints(); 446 gc.anchor = GridBagConstraints.NORTHWEST; 447 gc.gridx = 0; 448 gc.fill = GridBagConstraints.HORIZONTAL; 449 gc.weightx = 0.0; 450 add(rbRestrictToMyself, gc); 451 rbRestrictToMyself.addItemListener(userRestrictionChangeHandler); 452 453 gc.gridx = 1; 454 gc.fill = GridBagConstraints.HORIZONTAL; 455 gc.weightx = 1.0; 456 add(lblRestrictedToMyself, gc); 457 458 gc.gridx = 0; 459 gc.gridy = 1; 460 gc.fill = GridBagConstraints.HORIZONTAL; 461 gc.weightx = 0.0; 462 add(rbRestrictToUid, gc); 463 rbRestrictToUid.addItemListener(userRestrictionChangeHandler); 464 465 gc.gridx = 1; 466 gc.fill = GridBagConstraints.HORIZONTAL; 467 gc.weightx = 1.0; 468 add(new JMultilineLabel(tr("Only changesets owned by the user with the following user ID")), gc); 469 470 gc.gridx = 1; 471 gc.gridy = 2; 472 gc.fill = GridBagConstraints.HORIZONTAL; 473 gc.weightx = 1.0; 474 add(buildUidInputPanel(), gc); 475 476 gc.gridx = 0; 477 gc.gridy = 3; 478 gc.fill = GridBagConstraints.HORIZONTAL; 479 gc.weightx = 0.0; 480 add(rbRestrictToUserName, gc); 481 rbRestrictToUserName.addItemListener(userRestrictionChangeHandler); 482 483 gc.gridx = 1; 484 gc.fill = GridBagConstraints.HORIZONTAL; 485 gc.weightx = 1.0; 486 add(new JMultilineLabel(tr("Only changesets owned by the user with the following user name")), gc); 487 488 gc.gridx = 1; 489 gc.gridy = 4; 490 gc.fill = GridBagConstraints.HORIZONTAL; 491 gc.weightx = 1.0; 492 add(buildUserNameInputPanel(), gc); 493 494 bgUserRestrictions.add(rbRestrictToMyself); 495 bgUserRestrictions.add(rbRestrictToUid); 496 bgUserRestrictions.add(rbRestrictToUserName); 497 } 498 499 public void startUserInput() { 500 if (JosmUserIdentityManager.getInstance().isAnonymous()) { 501 lblRestrictedToMyself.setText(tr("Only changesets owned by myself (disabled. JOSM is currently run by an anonymous user)")); 502 rbRestrictToMyself.setEnabled(false); 503 if (rbRestrictToMyself.isSelected()) { 504 rbRestrictToUid.setSelected(true); 505 } 506 } else { 507 lblRestrictedToMyself.setText(tr("Only changesets owned by myself")); 508 rbRestrictToMyself.setEnabled(true); 509 rbRestrictToMyself.setSelected(true); 510 } 511 restoreFromSettings(); 512 } 513 514 /** 515 * Sets the query restrictions on <code>query</code> for changeset owner based 516 * restrictions. 517 * 518 * @param query the query. Must not be null. 519 * @throws IllegalArgumentException if query is null 520 * @throws IllegalStateException if one of the available values for query parameters in 521 * this panel isn't valid 522 */ 523 public void fillInQuery(ChangesetQuery query) { 524 CheckParameterUtil.ensureParameterNotNull(query, "query"); 525 if (rbRestrictToMyself.isSelected()) { 526 JosmUserIdentityManager im = JosmUserIdentityManager.getInstance(); 527 if (im.isPartiallyIdentified()) { 528 query.forUser(im.getUserName()); 529 } else if (im.isFullyIdentified()) { 530 query.forUser(im.getUserId()); 531 } else 532 throw new IllegalStateException( 533 tr("Cannot restrict changeset query to the current user because the current user is anonymous")); 534 } else if (rbRestrictToUid.isSelected()) { 535 int uid = valUid.getUid(); 536 if (uid > 0) { 537 query.forUser(uid); 538 } else 539 throw new IllegalStateException(tr("Current value ''{0}'' for user ID is not valid", tfUid.getText())); 540 } else if (rbRestrictToUserName.isSelected()) { 541 if (!valUserName.isValid()) 542 throw new IllegalStateException( 543 tr("Cannot restrict the changeset query to the user name ''{0}''", tfUserName.getText())); 544 query.forUser(tfUserName.getText()); 545 } 546 } 547 548 public boolean isValidChangesetQuery() { 549 if (rbRestrictToUid.isSelected()) 550 return valUid.isValid(); 551 else if (rbRestrictToUserName.isSelected()) 552 return valUserName.isValid(); 553 return true; 554 } 555 556 protected void alertInvalidUid() { 557 HelpAwareOptionPane.showOptionDialog( 558 this, 559 tr("Please enter a valid user ID"), 560 tr("Invalid user ID"), 561 JOptionPane.ERROR_MESSAGE, 562 HelpUtil.ht("/Dialog/ChangesetQueryDialog#InvalidUserId") 563 ); 564 } 565 566 protected void alertInvalidUserName() { 567 HelpAwareOptionPane.showOptionDialog( 568 this, 569 tr("Please enter a non-empty user name"), 570 tr("Invalid user name"), 571 JOptionPane.ERROR_MESSAGE, 572 HelpUtil.ht("/Dialog/ChangesetQueryDialog#InvalidUserName") 573 ); 574 } 575 576 public void displayMessageIfInvalid() { 577 if (rbRestrictToUid.isSelected()) { 578 if (!valUid.isValid()) { 579 alertInvalidUid(); 580 } 581 } else if (rbRestrictToUserName.isSelected()) { 582 if (!valUserName.isValid()) { 583 alertInvalidUserName(); 584 } 585 } 586 } 587 588 public void rememberSettings() { 589 String prefRoot = "changeset-query.advanced.user-restrictions"; 590 if (rbRestrictToMyself.isSelected()) { 591 Main.pref.put(prefRoot + ".query-type", "mine"); 592 } else if (rbRestrictToUid.isSelected()) { 593 Main.pref.put(prefRoot + ".query-type", "uid"); 594 } else if (rbRestrictToUserName.isSelected()) { 595 Main.pref.put(prefRoot + ".query-type", "username"); 596 } 597 Main.pref.put(prefRoot + ".uid", tfUid.getText()); 598 Main.pref.put(prefRoot + ".username", tfUserName.getText()); 599 } 600 601 public void restoreFromSettings() { 602 String prefRoot = "changeset-query.advanced.user-restrictions"; 603 String v = Main.pref.get(prefRoot + ".query-type", "mine"); 604 if ("mine".equals(v)) { 605 JosmUserIdentityManager im = JosmUserIdentityManager.getInstance(); 606 if (im.isAnonymous()) { 607 rbRestrictToUid.setSelected(true); 608 } else { 609 rbRestrictToMyself.setSelected(true); 610 } 611 } else if ("uid".equals(v)) { 612 rbRestrictToUid.setSelected(true); 613 } else if ("username".equals(v)) { 614 rbRestrictToUserName.setSelected(true); 615 } 616 tfUid.setText(Main.pref.get(prefRoot + ".uid", "")); 617 if (!valUid.isValid()) { 618 tfUid.setText(""); 619 } 620 tfUserName.setText(Main.pref.get(prefRoot + ".username", "")); 621 } 622 623 class UserRestrictionChangedHandler implements ItemListener { 624 @Override 625 public void itemStateChanged(ItemEvent e) { 626 tfUid.setEnabled(rbRestrictToUid.isSelected()); 627 tfUserName.setEnabled(rbRestrictToUserName.isSelected()); 628 if (rbRestrictToUid.isSelected()) { 629 tfUid.requestFocusInWindow(); 630 } else if (rbRestrictToUserName.isSelected()) { 631 tfUserName.requestFocusInWindow(); 632 } 633 } 634 } 635 } 636 637 /** 638 * This is the panel to apply a time restriction to the changeset query 639 */ 640 private static class TimeRestrictionPanel extends JPanel { 641 642 private final JRadioButton rbClosedAfter = new JRadioButton(); 643 private final JRadioButton rbClosedAfterAndCreatedBefore = new JRadioButton(); 644 private final JosmTextField tfClosedAfterDate1 = new JosmTextField(); 645 private transient DateValidator valClosedAfterDate1; 646 private final JosmTextField tfClosedAfterTime1 = new JosmTextField(); 647 private transient TimeValidator valClosedAfterTime1; 648 private final JosmTextField tfClosedAfterDate2 = new JosmTextField(); 649 private transient DateValidator valClosedAfterDate2; 650 private final JosmTextField tfClosedAfterTime2 = new JosmTextField(); 651 private transient TimeValidator valClosedAfterTime2; 652 private final JosmTextField tfCreatedBeforeDate = new JosmTextField(); 653 private transient DateValidator valCreatedBeforeDate; 654 private final JosmTextField tfCreatedBeforeTime = new JosmTextField(); 655 private transient TimeValidator valCreatedBeforeTime; 656 657 TimeRestrictionPanel() { 658 build(); 659 } 660 661 protected JPanel buildClosedAfterInputPanel() { 662 JPanel pnl = new JPanel(new GridBagLayout()); 663 GridBagConstraints gc = new GridBagConstraints(); 664 gc.fill = GridBagConstraints.HORIZONTAL; 665 gc.weightx = 0.0; 666 gc.insets = new Insets(0, 0, 0, 3); 667 pnl.add(new JLabel(tr("Date: ")), gc); 668 669 gc.gridx = 1; 670 gc.weightx = 0.7; 671 pnl.add(tfClosedAfterDate1, gc); 672 SelectAllOnFocusGainedDecorator.decorate(tfClosedAfterDate1); 673 valClosedAfterDate1 = DateValidator.decorate(tfClosedAfterDate1); 674 tfClosedAfterDate1.setToolTipText(valClosedAfterDate1.getStandardTooltipTextAsHtml()); 675 676 gc.gridx = 2; 677 gc.weightx = 0.0; 678 pnl.add(new JLabel(tr("Time:")), gc); 679 680 gc.gridx = 3; 681 gc.weightx = 0.3; 682 pnl.add(tfClosedAfterTime1, gc); 683 SelectAllOnFocusGainedDecorator.decorate(tfClosedAfterTime1); 684 valClosedAfterTime1 = TimeValidator.decorate(tfClosedAfterTime1); 685 tfClosedAfterTime1.setToolTipText(valClosedAfterTime1.getStandardTooltipTextAsHtml()); 686 return pnl; 687 } 688 689 protected JPanel buildClosedAfterAndCreatedBeforeInputPanel() { 690 JPanel pnl = new JPanel(new GridBagLayout()); 691 GridBagConstraints gc = new GridBagConstraints(); 692 gc.fill = GridBagConstraints.HORIZONTAL; 693 gc.weightx = 0.0; 694 gc.insets = new Insets(0, 0, 0, 3); 695 pnl.add(new JLabel(tr("Closed after - ")), gc); 696 697 gc.gridx = 1; 698 gc.fill = GridBagConstraints.HORIZONTAL; 699 gc.weightx = 0.0; 700 gc.insets = new Insets(0, 0, 0, 3); 701 pnl.add(new JLabel(tr("Date:")), gc); 702 703 gc.gridx = 2; 704 gc.weightx = 0.7; 705 pnl.add(tfClosedAfterDate2, gc); 706 SelectAllOnFocusGainedDecorator.decorate(tfClosedAfterDate2); 707 valClosedAfterDate2 = DateValidator.decorate(tfClosedAfterDate2); 708 tfClosedAfterDate2.setToolTipText(valClosedAfterDate2.getStandardTooltipTextAsHtml()); 709 gc.gridx = 3; 710 gc.weightx = 0.0; 711 pnl.add(new JLabel(tr("Time:")), gc); 712 713 gc.gridx = 4; 714 gc.weightx = 0.3; 715 pnl.add(tfClosedAfterTime2, gc); 716 SelectAllOnFocusGainedDecorator.decorate(tfClosedAfterTime2); 717 valClosedAfterTime2 = TimeValidator.decorate(tfClosedAfterTime2); 718 tfClosedAfterTime2.setToolTipText(valClosedAfterTime2.getStandardTooltipTextAsHtml()); 719 720 gc.gridy = 1; 721 gc.gridx = 0; 722 gc.fill = GridBagConstraints.HORIZONTAL; 723 gc.weightx = 0.0; 724 gc.insets = new Insets(0, 0, 0, 3); 725 pnl.add(new JLabel(tr("Created before - ")), gc); 726 727 gc.gridx = 1; 728 gc.fill = GridBagConstraints.HORIZONTAL; 729 gc.weightx = 0.0; 730 gc.insets = new Insets(0, 0, 0, 3); 731 pnl.add(new JLabel(tr("Date:")), gc); 732 733 gc.gridx = 2; 734 gc.weightx = 0.7; 735 pnl.add(tfCreatedBeforeDate, gc); 736 SelectAllOnFocusGainedDecorator.decorate(tfCreatedBeforeDate); 737 valCreatedBeforeDate = DateValidator.decorate(tfCreatedBeforeDate); 738 tfCreatedBeforeDate.setToolTipText(valCreatedBeforeDate.getStandardTooltipTextAsHtml()); 739 740 gc.gridx = 3; 741 gc.weightx = 0.0; 742 pnl.add(new JLabel(tr("Time:")), gc); 743 744 gc.gridx = 4; 745 gc.weightx = 0.3; 746 pnl.add(tfCreatedBeforeTime, gc); 747 SelectAllOnFocusGainedDecorator.decorate(tfCreatedBeforeTime); 748 valCreatedBeforeTime = TimeValidator.decorate(tfCreatedBeforeTime); 749 tfCreatedBeforeTime.setToolTipText(valCreatedBeforeDate.getStandardTooltipTextAsHtml()); 750 751 return pnl; 752 } 753 754 protected void build() { 755 setLayout(new GridBagLayout()); 756 setBorder(BorderFactory.createCompoundBorder( 757 BorderFactory.createEmptyBorder(3, 3, 3, 3), 758 BorderFactory.createCompoundBorder( 759 BorderFactory.createLineBorder(Color.GRAY), 760 BorderFactory.createEmptyBorder(5, 5, 5, 5) 761 ) 762 )); 763 764 // -- changesets closed after a specific date/time 765 // 766 GridBagConstraints gc = new GridBagConstraints(); 767 gc.anchor = GridBagConstraints.NORTHWEST; 768 gc.gridx = 0; 769 gc.fill = GridBagConstraints.HORIZONTAL; 770 gc.weightx = 0.0; 771 add(rbClosedAfter, gc); 772 773 gc.gridx = 1; 774 gc.fill = GridBagConstraints.HORIZONTAL; 775 gc.weightx = 1.0; 776 add(new JMultilineLabel(tr("Only changesets closed after the following date/time")), gc); 777 778 gc.gridx = 1; 779 gc.gridy = 1; 780 gc.fill = GridBagConstraints.HORIZONTAL; 781 gc.weightx = 1.0; 782 add(buildClosedAfterInputPanel(), gc); 783 784 // -- changesets closed after a specific date/time and created before a specific date time 785 // 786 gc = new GridBagConstraints(); 787 gc.anchor = GridBagConstraints.NORTHWEST; 788 gc.gridy = 2; 789 gc.gridx = 0; 790 gc.fill = GridBagConstraints.HORIZONTAL; 791 gc.weightx = 0.0; 792 add(rbClosedAfterAndCreatedBefore, gc); 793 794 gc.gridx = 1; 795 gc.fill = GridBagConstraints.HORIZONTAL; 796 gc.weightx = 1.0; 797 add(new JMultilineLabel(tr("Only changesets closed after and created before a specific date/time")), gc); 798 799 gc.gridx = 1; 800 gc.gridy = 3; 801 gc.fill = GridBagConstraints.HORIZONTAL; 802 gc.weightx = 1.0; 803 add(buildClosedAfterAndCreatedBeforeInputPanel(), gc); 804 805 ButtonGroup bg = new ButtonGroup(); 806 bg.add(rbClosedAfter); 807 bg.add(rbClosedAfterAndCreatedBefore); 808 809 ItemListener restrictionChangeHandler = new TimeRestrictionChangedHandler(); 810 rbClosedAfter.addItemListener(restrictionChangeHandler); 811 rbClosedAfterAndCreatedBefore.addItemListener(restrictionChangeHandler); 812 813 rbClosedAfter.setSelected(true); 814 } 815 816 public boolean isValidChangesetQuery() { 817 if (rbClosedAfter.isSelected()) 818 return valClosedAfterDate1.isValid() && valClosedAfterTime1.isValid(); 819 else if (rbClosedAfterAndCreatedBefore.isSelected()) 820 return valClosedAfterDate2.isValid() && valClosedAfterTime2.isValid() 821 && valCreatedBeforeDate.isValid() && valCreatedBeforeTime.isValid(); 822 // should not happen 823 return true; 824 } 825 826 class TimeRestrictionChangedHandler implements ItemListener { 827 @Override 828 public void itemStateChanged(ItemEvent e) { 829 tfClosedAfterDate1.setEnabled(rbClosedAfter.isSelected()); 830 tfClosedAfterTime1.setEnabled(rbClosedAfter.isSelected()); 831 832 tfClosedAfterDate2.setEnabled(rbClosedAfterAndCreatedBefore.isSelected()); 833 tfClosedAfterTime2.setEnabled(rbClosedAfterAndCreatedBefore.isSelected()); 834 tfCreatedBeforeDate.setEnabled(rbClosedAfterAndCreatedBefore.isSelected()); 835 tfCreatedBeforeTime.setEnabled(rbClosedAfterAndCreatedBefore.isSelected()); 836 } 837 } 838 839 public void startUserInput() { 840 restoreFromSettings(); 841 } 842 843 public void fillInQuery(ChangesetQuery query) { 844 if (!isValidChangesetQuery()) 845 throw new IllegalStateException(tr("Cannot build changeset query with time based restrictions. Input is not valid.")); 846 if (rbClosedAfter.isSelected()) { 847 LocalDate d1 = valClosedAfterDate1.getDate(); 848 LocalTime d2 = valClosedAfterTime1.getDate(); 849 final Date d3 = new Date(d1.atTime(d2).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()); 850 query.closedAfter(d3); 851 } else if (rbClosedAfterAndCreatedBefore.isSelected()) { 852 LocalDate d1 = valClosedAfterDate2.getDate(); 853 LocalTime d2 = valClosedAfterTime2.getDate(); 854 Date d3 = new Date(d1.atTime(d2).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()); 855 856 d1 = valCreatedBeforeDate.getDate(); 857 d2 = valCreatedBeforeTime.getDate(); 858 Date d4 = new Date(d1.atTime(d2).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()); 859 860 query.closedAfterAndCreatedBefore(d3, d4); 861 } 862 } 863 864 public void displayMessageIfInvalid() { 865 if (isValidChangesetQuery()) 866 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 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("closed-after".equals(v)); 898 rbClosedAfterAndCreatedBefore.setSelected("closed-after-created-before".equals(v)); 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 private static class BBoxRestrictionPanel extends BoundingBoxSelectionPanel { 930 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()) 952 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 UidInputFieldValidator(JTextComponent tc) { 972 super(tc); 973 } 974 975 public static UidInputFieldValidator decorate(JTextComponent tc) { 976 return new UidInputFieldValidator(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().isEmpty()) { 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().isEmpty()) return 0; 1007 try { 1008 int uid = Integer.parseInt(value.trim()); 1009 if (uid > 0) 1010 return uid; 1011 return 0; 1012 } catch (NumberFormatException e) { 1013 return 0; 1014 } 1015 } 1016 } 1017 1018 /** 1019 * Validates dates entered as text in a {@link JTextComponent}. Validates the input 1020 * on the fly and gives feedback about whether the date is valid or not. 1021 * 1022 * Dates can be entered in one of four standard formats defined for the current locale. 1023 */ 1024 private static class DateValidator extends AbstractTextComponentValidator { 1025 DateValidator(JTextComponent tc) { 1026 super(tc); 1027 } 1028 1029 public static DateValidator decorate(JTextComponent tc) { 1030 return new DateValidator(tc); 1031 } 1032 1033 @Override 1034 public boolean isValid() { 1035 return getDate() != null; 1036 } 1037 1038 public String getStandardTooltipTextAsHtml() { 1039 return "<html>" + getStandardTooltipText() + "</html>"; 1040 } 1041 1042 public String getStandardTooltipText() { 1043 final ZonedDateTime now = ZonedDateTime.now(); 1044 return tr( 1045 "Please enter a date in the usual format for your locale.<br>" 1046 + "Example: {0}<br>" 1047 + "Example: {1}<br>" 1048 + "Example: {2}<br>" 1049 + "Example: {3}<br>", 1050 DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).format(now), 1051 DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).format(now), 1052 DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG).format(now), 1053 DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).format(now) 1054 ); 1055 } 1056 1057 @Override 1058 public void validate() { 1059 if (!isValid()) { 1060 String msg = "<html>The current value isn't a valid date.<br>" + getStandardTooltipText()+ "</html>"; 1061 feedbackInvalid(msg); 1062 return; 1063 } else { 1064 String msg = "<html>" + getStandardTooltipText() + "</html>"; 1065 feedbackValid(msg); 1066 } 1067 } 1068 1069 public LocalDate getDate() { 1070 for (final FormatStyle format: FormatStyle.values()) { 1071 DateTimeFormatter df = DateTimeFormatter.ofLocalizedDate(format); 1072 try { 1073 return LocalDate.parse(getComponent().getText(), df); 1074 } catch (DateTimeParseException e) { 1075 // Try next format 1076 Main.trace(e); 1077 } 1078 } 1079 return null; 1080 } 1081 } 1082 1083 /** 1084 * Validates time values entered as text in a {@link JTextComponent}. Validates the input 1085 * on the fly and gives feedback about whether the time value is valid or not. 1086 * 1087 * Time values can be entered in one of four standard formats defined for the current locale. 1088 */ 1089 private static class TimeValidator extends AbstractTextComponentValidator { 1090 TimeValidator(JTextComponent tc) { 1091 super(tc); 1092 } 1093 1094 public static TimeValidator decorate(JTextComponent tc) { 1095 return new TimeValidator(tc); 1096 } 1097 1098 @Override 1099 public boolean isValid() { 1100 if (getComponent().getText().trim().isEmpty()) 1101 return true; 1102 return getDate() != null; 1103 } 1104 1105 public String getStandardTooltipTextAsHtml() { 1106 return "<html>" + getStandardTooltipText() + "</html>"; 1107 } 1108 1109 public String getStandardTooltipText() { 1110 final ZonedDateTime now = ZonedDateTime.now(); 1111 return tr( 1112 "Please enter a valid time in the usual format for your locale.<br>" 1113 + "Example: {0}<br>" 1114 + "Example: {1}<br>" 1115 + "Example: {2}<br>" 1116 + "Example: {3}<br>", 1117 DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT).format(now), 1118 DateTimeFormatter.ofLocalizedTime(FormatStyle.MEDIUM).format(now), 1119 DateTimeFormatter.ofLocalizedTime(FormatStyle.LONG).format(now), 1120 DateTimeFormatter.ofLocalizedTime(FormatStyle.FULL).format(now) 1121 ); 1122 } 1123 1124 @Override 1125 public void validate() { 1126 if (!isValid()) { 1127 String msg = "<html>The current value isn't a valid time.<br>" + getStandardTooltipText() + "</html>"; 1128 feedbackInvalid(msg); 1129 return; 1130 } else { 1131 String msg = "<html>" + getStandardTooltipText() + "</html>"; 1132 feedbackValid(msg); 1133 } 1134 } 1135 1136 public LocalTime getDate() { 1137 if (getComponent().getText().trim().isEmpty()) 1138 return LocalTime.MIDNIGHT; 1139 1140 for (final FormatStyle format: FormatStyle.values()) { 1141 DateTimeFormatter df = DateTimeFormatter.ofLocalizedTime(format); 1142 try { 1143 return LocalTime.parse(getComponent().getText(), df); 1144 } catch (DateTimeParseException e) { 1145 // Try next format 1146 Main.trace(e); 1147 } 1148 } 1149 return LocalTime.MIDNIGHT; 1150 } 1151 } 1152}