001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2008-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2014-2016 ForgeRock AS. 016 */ 017package org.opends.guitools.controlpanel.ui; 018 019import static org.opends.messages.AdminToolMessages.*; 020 021import java.awt.CardLayout; 022import java.awt.Component; 023import java.awt.GridBagConstraints; 024import java.awt.Insets; 025import java.awt.event.ActionEvent; 026import java.awt.event.ActionListener; 027import java.util.ArrayList; 028import java.util.Arrays; 029import java.util.List; 030 031import javax.swing.JButton; 032import javax.swing.JPanel; 033import javax.swing.SwingUtilities; 034import javax.swing.border.Border; 035import javax.swing.border.EmptyBorder; 036import javax.swing.tree.TreePath; 037 038import org.forgerock.i18n.LocalizableMessage; 039import org.opends.guitools.controlpanel.browser.BasicNodeError; 040import org.opends.guitools.controlpanel.browser.BrowserController; 041import org.opends.guitools.controlpanel.datamodel.ControlPanelInfo; 042import org.opends.guitools.controlpanel.datamodel.CustomSearchResult; 043import org.opends.guitools.controlpanel.datamodel.ServerDescriptor; 044import org.opends.guitools.controlpanel.event.ConfigurationChangeEvent; 045import org.opends.guitools.controlpanel.event.EntryReadErrorEvent; 046import org.opends.guitools.controlpanel.event.EntryReadEvent; 047import org.opends.guitools.controlpanel.event.EntryReadListener; 048import org.opends.guitools.controlpanel.event.LDAPEntryChangedEvent; 049import org.opends.guitools.controlpanel.event.LDAPEntryChangedListener; 050import org.opends.guitools.controlpanel.task.DeleteEntryTask; 051import org.opends.guitools.controlpanel.task.ModifyEntryTask; 052import org.opends.guitools.controlpanel.task.Task; 053import org.opends.guitools.controlpanel.util.Utilities; 054import org.opends.server.config.ConfigConstants; 055import org.forgerock.opendj.ldap.DN; 056import org.opends.server.types.Entry; 057import org.opends.server.types.OpenDsException; 058import org.opends.server.util.ServerConstants; 059 060/** This is the panel that contains all the different views to display an entry. */ 061public class LDAPEntryPanel extends StatusGenericPanel 062implements EntryReadListener 063{ 064 private static final long serialVersionUID = -6608246173472437830L; 065 private JButton saveChanges; 066 private JButton delete; 067 private JPanel mainPanel; 068 private CardLayout cardLayout; 069 070 private ErrorSearchingEntryPanel errorSearchingPanel; 071 private LDIFViewEntryPanel ldifEntryPanel; 072 private TableViewEntryPanel tableEntryPanel; 073 private SimplifiedViewEntryPanel simplifiedEntryPanel; 074 075 private ViewEntryPanel displayedEntryPanel; 076 077 private CustomSearchResult searchResult; 078 private BrowserController controller; 079 private TreePath treePath; 080 081 private ModifyEntryTask newTask; 082 083 private final String NOTHING_SELECTED = "Nothing Selected"; 084 private final String MULTIPLE_SELECTED = "Multiple Selected"; 085 private final String LDIF_VIEW = "LDIF View"; 086 private final String ATTRIBUTE_VIEW = "Attribute View"; 087 private final String SIMPLIFIED_VIEW = "Simplified View"; 088 private final String ERROR_SEARCHING = "Error Searching"; 089 090 private View view = View.SIMPLIFIED_VIEW; 091 092 /** The different views that we have to display an LDAP entry. */ 093 public enum View 094 { 095 /** Simplified view. */ 096 SIMPLIFIED_VIEW, 097 /** Attribute view (contained in a table). */ 098 ATTRIBUTE_VIEW, 099 /** LDIF view (text based). */ 100 LDIF_VIEW 101 } 102 103 /** Default constructor. */ 104 public LDAPEntryPanel() 105 { 106 super(); 107 createLayout(); 108 } 109 110 /** Creates the layout of the panel (but the contents are not populated here). */ 111 private void createLayout() 112 { 113 GridBagConstraints gbc = new GridBagConstraints(); 114 cardLayout = new CardLayout(); 115 mainPanel = new JPanel(cardLayout); 116 mainPanel.setOpaque(false); 117 gbc.gridx = 0; 118 gbc.gridy = 0; 119 gbc.gridwidth = 2; 120 gbc.weightx = 1.0; 121 gbc.weighty = 1.0; 122 gbc.fill = GridBagConstraints.BOTH; 123 add(mainPanel, gbc); 124 gbc.gridwidth = 1; 125 gbc.anchor = GridBagConstraints.WEST; 126 gbc.insets = new Insets(5, 5, 5, 5); 127 gbc.weighty = 0.0; 128 gbc.gridy ++; 129 gbc.fill = GridBagConstraints.NONE; 130 delete = Utilities.createButton(INFO_CTRL_PANEL_DELETE_ENTRY_BUTTON.get()); 131 delete.setOpaque(false); 132 add(delete, gbc); 133 delete.addActionListener(new ActionListener() 134 { 135 @Override 136 public void actionPerformed(ActionEvent ev) 137 { 138 deleteEntry(); 139 } 140 }); 141 142 gbc.anchor = GridBagConstraints.EAST; 143 gbc.gridx ++; 144 saveChanges = 145 Utilities.createButton(INFO_CTRL_PANEL_SAVE_CHANGES_LABEL.get()); 146 saveChanges.setOpaque(false); 147 add(saveChanges, gbc); 148 saveChanges.addActionListener(new ActionListener() 149 { 150 @Override 151 public void actionPerformed(ActionEvent ev) 152 { 153 saveChanges(true); 154 } 155 }); 156 157 Border border = new EmptyBorder(10, 10, 10, 10); 158 159 NoItemSelectedPanel noEntryPanel = new NoItemSelectedPanel(); 160 noEntryPanel.setMessage(INFO_CTRL_PANEL_NO_ENTRY_SELECTED_LABEL.get()); 161 Utilities.setBorder(noEntryPanel, border); 162 mainPanel.add(noEntryPanel, NOTHING_SELECTED); 163 164 NoItemSelectedPanel multipleEntryPanel = new NoItemSelectedPanel(); 165 multipleEntryPanel.setMessage( 166 INFO_CTRL_PANEL_MULTIPLE_ENTRIES_SELECTED_LABEL.get()); 167 Utilities.setBorder(multipleEntryPanel, border); 168 mainPanel.add(multipleEntryPanel, MULTIPLE_SELECTED); 169 170 errorSearchingPanel = new ErrorSearchingEntryPanel(); 171 if (errorSearchingPanel.requiresBorder()) 172 { 173 Utilities.setBorder(multipleEntryPanel, border); 174 } 175 mainPanel.add(errorSearchingPanel, ERROR_SEARCHING); 176 177 LDAPEntryChangedListener listener = new LDAPEntryChangedListener() 178 { 179 @Override 180 public void entryChanged(LDAPEntryChangedEvent ev) 181 { 182 boolean enable = saveChanges.isVisible() && 183 !authenticationRequired(getInfo().getServerDescriptor()); 184 if (enable) 185 { 186 if (ev.getEntry() == null) 187 { 188 // Something changed that is wrong: assume the entry has been 189 // modified, when the user tries to save we will inform of the 190 // problem 191 enable = true; 192 } 193 else 194 { 195 boolean modified = 196 !Utilities.areDnsEqual(ev.getEntry().getName().toString(), 197 searchResult.getDN()) || 198 !ModifyEntryTask.getModifications(ev.getEntry(), searchResult, 199 getInfo()).isEmpty(); 200 enable = modified; 201 } 202 } 203 saveChanges.setEnabled(enable); 204 } 205 }; 206 207 ldifEntryPanel = new LDIFViewEntryPanel(); 208 ldifEntryPanel.addLDAPEntryChangedListener(listener); 209 if (ldifEntryPanel.requiresBorder()) 210 { 211 Utilities.setBorder(ldifEntryPanel, border); 212 } 213 mainPanel.add(ldifEntryPanel, LDIF_VIEW); 214 215 tableEntryPanel = new TableViewEntryPanel(); 216 tableEntryPanel.addLDAPEntryChangedListener(listener); 217 if (tableEntryPanel.requiresBorder()) 218 { 219 Utilities.setBorder(tableEntryPanel, border); 220 } 221 mainPanel.add(tableEntryPanel, ATTRIBUTE_VIEW); 222 223 simplifiedEntryPanel = new SimplifiedViewEntryPanel(); 224 simplifiedEntryPanel.addLDAPEntryChangedListener(listener); 225 if (simplifiedEntryPanel.requiresBorder()) 226 { 227 Utilities.setBorder(simplifiedEntryPanel, border); 228 } 229 mainPanel.add(simplifiedEntryPanel, SIMPLIFIED_VIEW); 230 231 cardLayout.show(mainPanel, NOTHING_SELECTED); 232 } 233 234 @Override 235 public void okClicked() 236 { 237 // No ok button 238 } 239 240 @Override 241 public void entryRead(EntryReadEvent ev) 242 { 243 searchResult = ev.getSearchResult(); 244 245 updateEntryView(searchResult, treePath); 246 } 247 248 /** 249 * Updates the panel with the provided search result. 250 * @param searchResult the search result corresponding to the selected node. 251 * @param treePath the tree path of the selected node. 252 */ 253 private void updateEntryView(CustomSearchResult searchResult, 254 TreePath treePath) 255 { 256 boolean isReadOnly = isReadOnly(searchResult.getDN()); 257 boolean canDelete = canDelete(searchResult.getDN()); 258 259 delete.setVisible(canDelete); 260 saveChanges.setVisible(!isReadOnly); 261 String cardKey; 262 switch (view) 263 { 264 case LDIF_VIEW: 265 displayedEntryPanel = ldifEntryPanel; 266 cardKey = LDIF_VIEW; 267 break; 268 case ATTRIBUTE_VIEW: 269 displayedEntryPanel = tableEntryPanel; 270 cardKey = ATTRIBUTE_VIEW; 271 break; 272 default: 273 displayedEntryPanel = simplifiedEntryPanel; 274 cardKey = SIMPLIFIED_VIEW; 275 } 276 displayedEntryPanel.update(searchResult, isReadOnly, treePath); 277 saveChanges.setEnabled(false); 278 cardLayout.show(mainPanel, cardKey); 279 } 280 281 /** 282 * Sets the view to be displayed by this panel. 283 * @param view the view. 284 */ 285 public void setView(View view) 286 { 287 if (view != this.view) 288 { 289 this.view = view; 290 if (searchResult != null) 291 { 292 updateEntryView(searchResult, treePath); 293 } 294 } 295 } 296 297 /** 298 * Displays a message informing that an error occurred reading the entry. 299 * @param ev the entry read error event. 300 */ 301 @Override 302 public void entryReadError(EntryReadErrorEvent ev) 303 { 304 searchResult = null; 305 306 errorSearchingPanel.setError(ev.getDN(), ev.getError()); 307 308 delete.setVisible(false); 309 saveChanges.setVisible(false); 310 311 cardLayout.show(mainPanel, ERROR_SEARCHING); 312 313 displayedEntryPanel = null; 314 } 315 316 /** 317 * Displays a message informing that an error occurred resolving a referral. 318 * @param dn the DN of the local entry. 319 * @param referrals the list of referrals defined in the entry. 320 * @param error the error that occurred resolving the referral. 321 */ 322 public void referralSolveError(String dn, String[] referrals, 323 BasicNodeError error) 324 { 325 searchResult = null; 326 327 errorSearchingPanel.setReferralError(dn, referrals, error); 328 329 delete.setVisible(false); 330 saveChanges.setVisible(false); 331 332 cardLayout.show(mainPanel, ERROR_SEARCHING); 333 334 displayedEntryPanel = null; 335 } 336 337 /** Displays a panel informing that nothing is selected. */ 338 public void noEntrySelected() 339 { 340 searchResult = null; 341 342 delete.setVisible(false); 343 saveChanges.setVisible(false); 344 345 cardLayout.show(mainPanel, NOTHING_SELECTED); 346 347 displayedEntryPanel = null; 348 } 349 350 /** Displays a panel informing that multiple entries are selected. */ 351 public void multipleEntriesSelected() 352 { 353 searchResult = null; 354 355 delete.setVisible(false); 356 saveChanges.setVisible(false); 357 358 cardLayout.show(mainPanel, MULTIPLE_SELECTED); 359 360 displayedEntryPanel = null; 361 } 362 363 @Override 364 public GenericDialog.ButtonType getButtonType() 365 { 366 return GenericDialog.ButtonType.NO_BUTTON; 367 } 368 369 @Override 370 public LocalizableMessage getTitle() 371 { 372 return INFO_CTRL_PANEL_EDIT_LDAP_ENTRY_TITLE.get(); 373 } 374 375 @Override 376 public Component getPreferredFocusComponent() 377 { 378 return saveChanges; 379 } 380 381 @Override 382 public void configurationChanged(ConfigurationChangeEvent ev) 383 { 384 final ServerDescriptor desc = ev.getNewDescriptor(); 385 SwingUtilities.invokeLater(new Runnable() 386 { 387 @Override 388 public void run() 389 { 390 boolean isReadOnly = true; 391 boolean canDelete = false; 392 if (searchResult != null && desc.isAuthenticated()) 393 { 394 isReadOnly = isReadOnly(searchResult.getDN()); 395 canDelete = canDelete(searchResult.getDN()); 396 } 397 398 delete.setVisible(canDelete); 399 saveChanges.setVisible(!isReadOnly); 400 } 401 }); 402 } 403 404 @Override 405 public void setInfo(ControlPanelInfo info) 406 { 407 super.setInfo(info); 408 simplifiedEntryPanel.setInfo(info); 409 ldifEntryPanel.setInfo(info); 410 tableEntryPanel.setInfo(info); 411 errorSearchingPanel.setInfo(info); 412 } 413 414 private List<DN> parentReadOnly; 415 private List<DN> nonDeletable; 416 { 417 try 418 { 419 parentReadOnly = Arrays.asList( 420 DN.valueOf(ConfigConstants.DN_TASK_ROOT), 421 DN.valueOf(ConfigConstants.DN_MONITOR_ROOT), 422 DN.valueOf(ConfigConstants.DN_BACKUP_ROOT), 423 DN.valueOf(ServerConstants.DN_EXTERNAL_CHANGELOG_ROOT) 424 ); 425 nonDeletable = Arrays.asList( 426 DN.valueOf(ConfigConstants.DN_CONFIG_ROOT), 427 DN.valueOf(ConfigConstants.DN_DEFAULT_SCHEMA_ROOT), 428 DN.valueOf(ConfigConstants.DN_TRUST_STORE_ROOT) 429 ); 430 } 431 catch (Throwable t) 432 { 433 throw new RuntimeException("Error decoding DNs: "+t, t); 434 } 435 } 436 437 /** 438 * Returns <CODE>true</CODE> if the provided DN corresponds to a read-only 439 * entry and <CODE>false</CODE> otherwise. 440 * @param sDn the DN of the entry. 441 * @return <CODE>true</CODE> if the provided DN corresponds to a read-only 442 * entry and <CODE>false</CODE> otherwise. 443 */ 444 private boolean isReadOnly(String sDn) 445 { 446 boolean isReadOnly = false; 447 try 448 { 449 DN dn = DN.valueOf(sDn); 450 for (DN parentDN : parentReadOnly) 451 { 452 if (dn.isSubordinateOrEqualTo(parentDN)) 453 { 454 isReadOnly = true; 455 break; 456 } 457 } 458 if (!isReadOnly) 459 { 460 isReadOnly = dn.equals(DN.rootDN()); 461 } 462 } 463 catch (Throwable t) 464 { 465 throw new RuntimeException("Error decoding DNs: "+t, t); 466 } 467 return isReadOnly; 468 } 469 470 /** 471 * Returns <CODE>true</CODE> if the provided DN corresponds to an entry that 472 * can be deleted and <CODE>false</CODE> otherwise. 473 * @param sDn the DN of the entry. 474 * @return <CODE>true</CODE> if the provided DN corresponds to an entry that 475 * can be deleted and <CODE>false</CODE> otherwise. 476 */ 477 public boolean canDelete(String sDn) 478 { 479 try 480 { 481 DN dn = DN.valueOf(sDn); 482 return !dn.equals(DN.rootDN()) 483 && !nonDeletable.contains(dn) 484 && isDescendantOfAny(dn, parentReadOnly); 485 } 486 catch (Throwable t) 487 { 488 throw new RuntimeException("Error decoding DNs: "+t, t); 489 } 490 } 491 492 private boolean isDescendantOfAny(DN dn, List<DN> parentDNs) 493 { 494 for (DN parentDN : parentDNs) 495 { 496 if (dn.isSubordinateOrEqualTo(parentDN)) 497 { 498 return false; 499 } 500 } 501 return true; 502 } 503 504 /** 505 * Saves the changes done to the entry. 506 * @param modal whether the progress dialog for the task must be modal or 507 * not. 508 */ 509 private void saveChanges(boolean modal) 510 { 511 newTask = null; 512 final ArrayList<LocalizableMessage> errors = new ArrayList<>(); 513 // Check that the entry is correct. 514 try 515 { 516 ProgressDialog dlg = new ProgressDialog( 517 Utilities.getFrame(this), 518 Utilities.getFrame(this), 519 INFO_CTRL_PANEL_MODIFYING_ENTRY_CHANGES_TITLE.get(), getInfo()); 520 dlg.setModal(modal); 521 Entry entry = displayedEntryPanel.getEntry(); 522 newTask = new ModifyEntryTask(getInfo(), dlg, entry, 523 searchResult, controller, treePath); 524 for (Task task : getInfo().getTasks()) 525 { 526 task.canLaunch(newTask, errors); 527 } 528 529 if (errors.isEmpty()) 530 { 531 if (newTask.hasModifications()) { 532 String dn = entry.getName().toString(); 533 launchOperation(newTask, 534 INFO_CTRL_PANEL_MODIFYING_ENTRY_SUMMARY.get(dn), 535 INFO_CTRL_PANEL_MODIFYING_ENTRY_COMPLETE.get(), 536 INFO_CTRL_PANEL_MODIFYING_ENTRY_SUCCESSFUL.get(dn), 537 ERR_CTRL_PANEL_MODIFYING_ENTRY_ERROR_SUMMARY.get(), 538 ERR_CTRL_PANEL_MODIFYING_ENTRY_ERROR_DETAILS.get(dn), 539 null, 540 dlg); 541 saveChanges.setEnabled(false); 542 dlg.setVisible(true); 543 } 544 else 545 { 546 // Mark the panel as it has no changes. This can happen because every 547 // time the user types something the saveChanges button is enabled 548 // (for performance reasons with huge entries). 549 saveChanges.setEnabled(false); 550 } 551 } 552 } 553 catch (OpenDsException ode) 554 { 555 errors.add(ERR_CTRL_PANEL_INVALID_ENTRY.get(ode.getMessageObject())); 556 } 557 if (!errors.isEmpty()) 558 { 559 displayErrorDialog(errors); 560 } 561 } 562 563 private void deleteEntry() 564 { 565 final ArrayList<LocalizableMessage> errors = new ArrayList<>(); 566 // Check that the entry is correct. 567 // Rely in numsubordinates and hassubordinates 568 boolean isLeaf = !BrowserController.getHasSubOrdinates(searchResult); 569 570 if (treePath != null) 571 { 572 LocalizableMessage title = isLeaf ? INFO_CTRL_PANEL_DELETING_ENTRY_TITLE.get() : 573 INFO_CTRL_PANEL_DELETING_SUBTREE_TITLE.get(); 574 ProgressDialog dlg = new ProgressDialog( 575 Utilities.createFrame(), 576 Utilities.getParentDialog(this), title, getInfo()); 577 DeleteEntryTask newTask = new DeleteEntryTask(getInfo(), dlg, 578 new TreePath[]{treePath}, controller); 579 for (Task task : getInfo().getTasks()) 580 { 581 task.canLaunch(newTask, errors); 582 } 583 if (errors.isEmpty()) 584 { 585 LocalizableMessage confirmationMessage = 586 isLeaf ? INFO_CTRL_PANEL_DELETE_ENTRY_CONFIRMATION_DETAILS.get( 587 searchResult.getDN()) : 588 INFO_CTRL_PANEL_DELETE_SUBTREE_CONFIRMATION_DETAILS.get( 589 searchResult.getDN()); 590 if (displayConfirmationDialog( 591 INFO_CTRL_PANEL_CONFIRMATION_REQUIRED_SUMMARY.get(), 592 confirmationMessage)) 593 { 594 String dn = searchResult.getDN(); 595 if (isLeaf) 596 { 597 launchOperation(newTask, 598 INFO_CTRL_PANEL_DELETING_ENTRY_SUMMARY.get(dn), 599 INFO_CTRL_PANEL_DELETING_ENTRY_COMPLETE.get(), 600 INFO_CTRL_PANEL_DELETING_ENTRY_SUCCESSFUL.get(dn), 601 ERR_CTRL_PANEL_DELETING_ENTRY_ERROR_SUMMARY.get(), 602 ERR_CTRL_PANEL_DELETING_ENTRY_ERROR_DETAILS.get(dn), 603 null, 604 dlg); 605 } 606 else 607 { 608 launchOperation(newTask, 609 INFO_CTRL_PANEL_DELETING_SUBTREE_SUMMARY.get(dn), 610 INFO_CTRL_PANEL_DELETING_SUBTREE_COMPLETE.get(), 611 INFO_CTRL_PANEL_DELETING_SUBTREE_SUCCESSFUL.get(dn), 612 ERR_CTRL_PANEL_DELETING_SUBTREE_ERROR_SUMMARY.get(), 613 ERR_CTRL_PANEL_DELETING_SUBTREE_ERROR_DETAILS.get(dn), 614 null, 615 dlg); 616 } 617 dlg.setVisible(true); 618 } 619 } 620 } 621 if (!errors.isEmpty()) 622 { 623 displayErrorDialog(errors); 624 } 625 } 626 627 /** 628 * Returns the browser controller in charge of the tree. 629 * @return the browser controller in charge of the tree. 630 */ 631 public BrowserController getController() 632 { 633 return controller; 634 } 635 636 /** 637 * Sets the browser controller in charge of the tree. 638 * @param controller the browser controller in charge of the tree. 639 */ 640 public void setController(BrowserController controller) 641 { 642 this.controller = controller; 643 } 644 645 /** 646 * Returns the tree path associated with the node that is being displayed. 647 * @return the tree path associated with the node that is being displayed. 648 */ 649 public TreePath getTreePath() 650 { 651 return treePath; 652 } 653 654 /** 655 * Sets the tree path associated with the node that is being displayed. 656 * @param treePath the tree path associated with the node that is being 657 * displayed. 658 */ 659 public void setTreePath(TreePath treePath) 660 { 661 this.treePath = treePath; 662 } 663 664 /** 665 * Method used to know if there are unsaved changes or not. It is used by 666 * the entry selection listener when the user changes the selection. 667 * @return <CODE>true</CODE> if there are unsaved changes (and so the 668 * selection of the entry should be cancelled) and <CODE>false</CODE> 669 * otherwise. 670 */ 671 public boolean mustCheckUnsavedChanges() 672 { 673 return displayedEntryPanel != null && 674 saveChanges.isVisible() && saveChanges.isEnabled(); 675 } 676 677 /** 678 * Tells whether the user chose to save the changes in the panel, to not save 679 * them or simply canceled the selection change in the tree. 680 * @return the value telling whether the user chose to save the changes in the 681 * panel, to not save them or simply canceled the selection in the tree. 682 */ 683 public UnsavedChangesDialog.Result checkUnsavedChanges() 684 { 685 UnsavedChangesDialog.Result result; 686 UnsavedChangesDialog unsavedChangesDlg = new UnsavedChangesDialog( 687 Utilities.getParentDialog(this), getInfo()); 688 unsavedChangesDlg.setMessage(INFO_CTRL_PANEL_UNSAVED_CHANGES_SUMMARY.get(), 689 INFO_CTRL_PANEL_UNSAVED_ENTRY_CHANGES_DETAILS.get(searchResult.getDN())); 690 Utilities.centerGoldenMean(unsavedChangesDlg, 691 Utilities.getParentDialog(this)); 692 unsavedChangesDlg.setVisible(true); 693 result = unsavedChangesDlg.getResult(); 694 if (result == UnsavedChangesDialog.Result.SAVE) 695 { 696 saveChanges(false); 697 if (newTask == null || // The user data is not valid 698 newTask.getState() != Task.State.FINISHED_SUCCESSFULLY) 699 { 700 result = UnsavedChangesDialog.Result.CANCEL; 701 } 702 } 703 704 return result; 705 } 706}