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 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 transient UidInputFieldValidator valUid; 382 private JosmTextField tfUserName; 383 private transient 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 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 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 JRadioButton rbClosedAfter; 640 private JRadioButton rbClosedAfterAndCreatedBefore; 641 private JosmTextField tfClosedAfterDate1; 642 private transient DateValidator valClosedAfterDate1; 643 private JosmTextField tfClosedAfterTime1; 644 private transient TimeValidator valClosedAfterTime1; 645 private JosmTextField tfClosedAfterDate2; 646 private transient DateValidator valClosedAfterDate2; 647 private JosmTextField tfClosedAfterTime2; 648 private transient TimeValidator valClosedAfterTime2; 649 private JosmTextField tfCreatedBeforeDate; 650 private transient DateValidator valCreatedBeforeDate; 651 private JosmTextField tfCreatedBeforeTime; 652 private transient 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 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) { 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 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()) return; 952 HelpAwareOptionPane.showOptionDialog( 953 this, 954 tr( 955 "<html>Please enter valid longitude/latitude values to restrict<br>" + 956 "the changeset query to a specific bounding box.</html>" 957 ), 958 tr("Invalid bounding box"), 959 JOptionPane.ERROR_MESSAGE, 960 HelpUtil.ht("/Dialog/ChangesetQueryDialog#InvalidBoundingBox") 961 ); 962 } 963 } 964 965 /** 966 * Validator for user ids entered in a {@link JTextComponent}. 967 * 968 */ 969 private static class UidInputFieldValidator extends AbstractTextComponentValidator { 970 public static UidInputFieldValidator decorate(JTextComponent tc) { 971 return new UidInputFieldValidator(tc); 972 } 973 974 UidInputFieldValidator(JTextComponent tc) { 975 super(tc); 976 } 977 978 @Override 979 public boolean isValid() { 980 return getUid() > 0; 981 } 982 983 @Override 984 public void validate() { 985 String value = getComponent().getText(); 986 if (value == null || value.trim().isEmpty()) { 987 feedbackInvalid(""); 988 return; 989 } 990 try { 991 int uid = Integer.parseInt(value); 992 if (uid <= 0) { 993 feedbackInvalid(tr("The current value is not a valid user ID. Please enter an integer value > 0")); 994 return; 995 } 996 } catch (NumberFormatException e) { 997 feedbackInvalid(tr("The current value is not a valid user ID. Please enter an integer value > 0")); 998 return; 999 } 1000 feedbackValid(tr("Please enter an integer value > 0")); 1001 } 1002 1003 public int getUid() { 1004 String value = getComponent().getText(); 1005 if (value == null || value.trim().isEmpty()) return 0; 1006 try { 1007 int uid = Integer.parseInt(value.trim()); 1008 if (uid > 0) return uid; 1009 return 0; 1010 } catch (NumberFormatException e) { 1011 return 0; 1012 } 1013 } 1014 } 1015 1016 private static class UserNameInputValidator extends AbstractTextComponentValidator { 1017 public static UserNameInputValidator decorate(JTextComponent tc) { 1018 return new UserNameInputValidator(tc); 1019 } 1020 1021 UserNameInputValidator(JTextComponent tc) { 1022 super(tc); 1023 } 1024 1025 @Override 1026 public boolean isValid() { 1027 return !getComponent().getText().trim().isEmpty(); 1028 } 1029 1030 @Override 1031 public void validate() { 1032 String value = getComponent().getText(); 1033 if (value.trim().isEmpty()) { 1034 feedbackInvalid(tr("<html>The current value is not a valid user name.<br>Please enter an non-empty user name.</html>")); 1035 return; 1036 } 1037 feedbackValid(tr("Please enter an non-empty user name")); 1038 } 1039 } 1040 1041 /** 1042 * Validates dates entered as text in a {@link JTextComponent}. Validates the input 1043 * on the fly and gives feedback about whether the date is valid or not. 1044 * 1045 * Dates can be entered in one of four standard formats defined for the current locale. 1046 */ 1047 private static class DateValidator extends AbstractTextComponentValidator { 1048 public static DateValidator decorate(JTextComponent tc) { 1049 return new DateValidator(tc); 1050 } 1051 1052 DateValidator(JTextComponent tc) { 1053 super(tc); 1054 } 1055 1056 @Override 1057 public boolean isValid() { 1058 return getDate() != null; 1059 } 1060 1061 public String getStandardTooltipTextAsHtml() { 1062 return "<html>" + getStandardTooltipText() + "</html>"; 1063 } 1064 1065 public String getStandardTooltipText() { 1066 Date date = new Date(); 1067 return tr( 1068 "Please enter a date in the usual format for your locale.<br>" 1069 + "Example: {0}<br>" 1070 + "Example: {1}<br>" 1071 + "Example: {2}<br>" 1072 + "Example: {3}<br>", 1073 DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault()).format(date), 1074 DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault()).format(date), 1075 DateFormat.getDateInstance(DateFormat.LONG, Locale.getDefault()).format(date), 1076 DateFormat.getDateInstance(DateFormat.FULL, Locale.getDefault()).format(date) 1077 ); 1078 } 1079 1080 @Override 1081 public void validate() { 1082 if (!isValid()) { 1083 String msg = "<html>The current value isn't a valid date.<br>" + getStandardTooltipText()+ "</html>"; 1084 feedbackInvalid(msg); 1085 return; 1086 } else { 1087 String msg = "<html>" + getStandardTooltipText() + "</html>"; 1088 feedbackValid(msg); 1089 } 1090 } 1091 1092 public Date getDate() { 1093 for (int format: new int[] {DateFormat.SHORT, DateFormat.MEDIUM, DateFormat.LONG, DateFormat.FULL}) { 1094 DateFormat df = DateFormat.getDateInstance(format); 1095 try { 1096 return df.parse(getComponent().getText()); 1097 } catch (ParseException e) { 1098 // Try next format 1099 if (Main.isTraceEnabled()) { 1100 Main.trace(e.getMessage()); 1101 } 1102 } 1103 } 1104 return null; 1105 } 1106 } 1107 1108 /** 1109 * Validates time values entered as text in a {@link JTextComponent}. Validates the input 1110 * on the fly and gives feedback about whether the time value is valid or not. 1111 * 1112 * Time values can be entered in one of four standard formats defined for the current locale. 1113 */ 1114 private static class TimeValidator extends AbstractTextComponentValidator { 1115 public static TimeValidator decorate(JTextComponent tc) { 1116 return new TimeValidator(tc); 1117 } 1118 1119 TimeValidator(JTextComponent tc) { 1120 super(tc); 1121 } 1122 1123 @Override 1124 public boolean isValid() { 1125 if (getComponent().getText().trim().isEmpty()) return true; 1126 return getDate() != null; 1127 } 1128 1129 public String getStandardTooltipTextAsHtml() { 1130 return "<html>" + getStandardTooltipText() + "</html>"; 1131 } 1132 1133 public String getStandardTooltipText() { 1134 Date date = new Date(); 1135 return tr( 1136 "Please enter a valid time in the usual format for your locale.<br>" 1137 + "Example: {0}<br>" 1138 + "Example: {1}<br>" 1139 + "Example: {2}<br>" 1140 + "Example: {3}<br>", 1141 DateFormat.getTimeInstance(DateFormat.SHORT, Locale.getDefault()).format(date), 1142 DateFormat.getTimeInstance(DateFormat.MEDIUM, Locale.getDefault()).format(date), 1143 DateFormat.getTimeInstance(DateFormat.LONG, Locale.getDefault()).format(date), 1144 DateFormat.getTimeInstance(DateFormat.FULL, Locale.getDefault()).format(date) 1145 ); 1146 } 1147 1148 @Override 1149 public void validate() { 1150 1151 if (!isValid()) { 1152 String msg = "<html>The current value isn't a valid time.<br>" + getStandardTooltipText() + "</html>"; 1153 feedbackInvalid(msg); 1154 return; 1155 } else { 1156 String msg = "<html>" + getStandardTooltipText() + "</html>"; 1157 feedbackValid(msg); 1158 } 1159 } 1160 1161 public Date getDate() { 1162 if (getComponent().getText().trim().isEmpty()) 1163 return null; 1164 1165 for (int style : new int[]{DateFormat.SHORT, DateFormat.MEDIUM, DateFormat.LONG, DateFormat.FULL}) { 1166 try { 1167 return DateFormat.getTimeInstance(style, Locale.getDefault()).parse(getComponent().getText()); 1168 } catch (ParseException e) { 1169 continue; 1170 } 1171 } 1172 return null; 1173 } 1174 } 1175}