1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 package org.opensolaris.os.vp.panel.swing.control; 28 29 import java.awt.*; 30 import java.awt.event.*; 31 import java.beans.*; 32 import java.util.*; 33 import java.util.List; 34 import javax.swing.*; 35 import org.opensolaris.os.vp.panel.common.ClientContext; 36 import org.opensolaris.os.vp.panel.common.action.*; 37 import org.opensolaris.os.vp.panel.common.control.*; 38 import org.opensolaris.os.vp.panel.common.model.*; 39 import org.opensolaris.os.vp.panel.swing.action.SwingStructuredAction; 40 import org.opensolaris.os.vp.util.misc.*; 41 import org.opensolaris.os.vp.util.swing.*; 42 43 public class SwingControl<P extends PanelDescriptor, C extends Component> 44 extends DefaultControl<P> implements HasComponent<C>, 45 PropertyChangeListener { 46 47 // 48 // Instance data 49 // 50 51 private ManagedObject propSource; 52 private String[] propNames; 53 private boolean ignorePropChange; 54 55 private C comp; 56 private ComponentStack stack; 57 private JDialog childDialog; 58 private List<Control> dialogChildren = new ArrayList<Control>(); 59 60 private SwingStructuredAction<Object, Object, Object> resetAction = 61 new SwingStructuredAction<Object, Object, Object>(null, null, null, 62 this) { 63 64 @Override 65 public Object workBusy(Object pInput, Object rtInput) 66 throws ActionAbortedException, ActionFailedException, 67 ActionUnauthorizedException { 68 69 resetAll(); 70 return null; 71 } 72 }; 73 74 private SwingStructuredAction<Object, Object, Object> saveAction = 75 new SwingStructuredAction<Object, Object, Object>(null, null, null, 76 this) { 77 78 @Override 79 public Object workBusy(Object pInput, Object rtInput) 80 throws ActionAbortedException, ActionFailedException, 81 ActionUnauthorizedException { 82 83 saveAll(); 84 return null; 85 } 86 }; 87 88 // 89 // Constructors 90 // 91 92 public SwingControl(String id, String name, ClientContext context) { 93 super(id, name, context); 94 } 95 96 public SwingControl(String id, String name, P descriptor) { 97 super(id, name, descriptor); 98 } 99 100 // 101 // HasComponent methods 102 // 103 104 /** 105 * Gets the {@code Component} for this {@code SwingControl}. 106 * 107 * @return a {@code Component}, or {@code null} if this {@code 108 * SwingControl} has no {@code Component}. 109 * 110 * @see #setComponent 111 */ 112 @Override 113 public C getComponent() { 114 return comp; 115 } 116 117 // 118 // PropertyChangeListener methods 119 // 120 121 /** 122 * Convenience implementation that calls {@link #initComponentOnEventThread} 123 * if property changes are not temporarilty being {@link 124 * #getPropertyChangeIgnore ignored}. If this {@link SwingControl}'s 125 * component displayed the properties of a {@link ManagedObject}, for 126 * example, it may make sense to add this {@code SwingControl} as a {@code 127 * PropertyListener} to that object. 128 */ 129 @Override 130 public void propertyChange(PropertyChangeEvent event) { 131 if (!ignorePropChange && getComponent() != null) { 132 initComponentOnEventThread(); 133 } 134 } 135 136 // 137 // Control methods 138 // 139 140 @Override 141 public void childStarted(final Control child) { 142 super.childStarted(child); 143 144 assert childDialog == null; 145 146 if (dialogChildren.contains(child)) { 147 Component comp = getComponent(); 148 final Window owner = SwingUtilities.getWindowAncestor(comp); 149 150 childDialog = new JDialog(owner, 151 Dialog.ModalityType.DOCUMENT_MODAL); 152 Container cont = childDialog.getContentPane(); 153 cont.setLayout(new BorderLayout()); 154 155 Component cComp = ((SwingControl)child).getComponent(); 156 cont.add(cComp, BorderLayout.CENTER); 157 158 if (child instanceof HasImages) { 159 List<? extends Image> images = ((HasImages)child).getImages(); 160 childDialog.setIconImages(images); 161 } 162 163 childDialog.setTitle(child.getName()); 164 childDialog.setDefaultCloseOperation( 165 WindowConstants.DISPOSE_ON_CLOSE); 166 childDialog.pack(); 167 168 childDialog.addWindowListener( 169 new WindowAdapter() { 170 @Override 171 public void windowClosing(WindowEvent e) { 172 childDialog.removeWindowListener(this); 173 child.doCancel(); 174 } 175 }); 176 177 // Show the window after navigation has completed 178 getNavigator().asyncExec( 179 new Runnable() { 180 @Override 181 public void run() { 182 EventQueue.invokeLater( 183 new Runnable() { 184 @Override 185 public void run() { 186 childDialog.setLocationRelativeTo(owner); 187 childDialog.setVisible(true); 188 } 189 }); 190 } 191 }); 192 } 193 } 194 195 @Override 196 public void childStopped(Control child) { 197 super.childStopped(child); 198 199 if (childDialog != null) { 200 childDialog.dispose(); 201 childDialog = null; 202 } 203 } 204 205 /** 206 * If a) this {@code SwingControl} defines a {@link #getComponentStack 207 * ComponentStack}, b) the just-started {@link Control} implements {@link 208 * HasComponent}, and c) its {@code Component} does not yet have a parent, 209 * this method adds that {@code Component} to the stack. 210 */ 211 @Override 212 public void descendantStarted(Control[] path) { 213 super.descendantStarted(path); 214 215 final ComponentStack stack = getComponentStack(); 216 if (stack != null) { 217 Control control = path[path.length - 1]; 218 if (control instanceof HasComponent) { 219 final Component comp = ((HasComponent)control).getComponent(); 220 if (comp != null && comp.getParent() == null) { 221 GUIUtil.invokeAndWait( 222 new Runnable() { 223 @Override 224 public void run() { 225 stack.push(comp); 226 } 227 }); 228 } 229 } 230 } 231 } 232 233 /** 234 * If a) this {@code SwingControl} defines a {@link #getComponentStack 235 * ComponentStack}, b) the just-stopped {@link Control} implements {@link 236 * HasComponent}, and c) its {@code Component} is at the top of the stack, 237 * this method removes that {@code Component} from the stack. 238 */ 239 @Override 240 public void descendantStopped(Control[] path) { 241 super.descendantStopped(path); 242 243 final ComponentStack stack = getComponentStack(); 244 if (stack != null) { 245 Control control = path[path.length - 1]; 246 if (control instanceof HasComponent) { 247 final Component comp = ((HasComponent)control).getComponent(); 248 if (comp != null) { 249 GUIUtil.invokeAndWait( 250 new Runnable() { 251 @Override 252 public void run() { 253 StackUtil.popIfAtTop(stack, comp); 254 } 255 }); 256 } 257 } 258 } 259 } 260 261 /** 262 * Gets a {@link SwingStructuredAction} that invokes {@link #resetAll}, 263 * displaying errors. 264 */ 265 @Override 266 public SwingStructuredAction<?, ?, ?> getResetAction() { 267 return resetAction; 268 } 269 270 /** 271 * Gets a {@link SwingStructuredAction} that invokes {@link #saveAll}, 272 * displaying errors and prompting for login on {@code 273 * ActionUnauthorizedException}s. 274 */ 275 @Override 276 public SwingStructuredAction<?, ?, ?> getSaveAction() { 277 return saveAction; 278 } 279 280 /** 281 * Prompts the user to save changes, discard changes, or cancel navigation, 282 * when navigating away from a {@code Control} with unsaved changes. 283 * 284 * @see #getUnsavedChangesMessage 285 */ 286 @Override 287 protected UnsavedChangesAction getUnsavedChangesAction() { 288 String message = getUnsavedChangesMessage(); 289 if (message == null) { 290 message = Finder.getString("settings.unsavedchanges.message"); 291 } 292 293 String title = Finder.getString("settings.unsavedchanges.title"); 294 int messageType = JOptionPane.WARNING_MESSAGE; 295 int optionType = JOptionPane.DEFAULT_OPTION; 296 Icon icon = GUIUtil.getIcon(JOptionPane.WARNING_MESSAGE); 297 298 Object[] options = { 299 Finder.getString("settings.unsavedchanges.button.cancel"), 300 Finder.getString("settings.unsavedchanges.button.discard"), 301 Finder.getString("settings.unsavedchanges.button.save"), 302 }; 303 304 Object defaultOption = options[0]; 305 306 int result = JOptionPane.showOptionDialog(getComponent(), message, 307 title, optionType, messageType, icon, options, defaultOption); 308 309 switch (result) { 310 default: 311 case 0: return UnsavedChangesAction.CANCEL; 312 case 1: return UnsavedChangesAction.DISCARD; 313 case 2: return UnsavedChangesAction.SAVE; 314 } 315 } 316 317 /** 318 * If this {@code SwingControl} has a {@link #getChangeableAggregator 319 * ChangeableAggregator}, returns the value from the aggregator's {@link 320 * ChangeableAggregator#isChanged isChanged} method. Otherwise, returns 321 * {@code false}. 322 */ 323 @Override 324 protected boolean isChanged() { 325 ChangeableAggregator aggregator = getChangeableAggregator(); 326 if (aggregator != null) { 327 return aggregator.isChanged(); 328 } 329 return false; 330 } 331 332 /** 333 * If this {@code SwingControl} has a {@link #getChangeableAggregator 334 * ChangeableAggregator}, calls its {@link ChangeableAggregator#reset} 335 * method on the AWT event thread and waits for its completion. 336 */ 337 @Override 338 protected void reset() throws ActionAbortedException, ActionFailedException 339 { 340 final ChangeableAggregator aggregator = getChangeableAggregator(); 341 if (aggregator != null) { 342 GUIUtil.invokeAndWait( 343 new Runnable() { 344 @Override 345 public void run() { 346 aggregator.reset(); 347 } 348 }); 349 } 350 } 351 352 /** 353 * If this {@code SwingControl} has a {@link #getChangeableAggregator 354 * ChangeableAggregator}, calls its {@link ChangeableAggregator#save} 355 * method on the AWT event thread and waits for its completion. 356 */ 357 @Override 358 protected void save() throws ActionAbortedException, ActionFailedException, 359 ActionUnauthorizedException { 360 361 final ChangeableAggregator aggregator = getChangeableAggregator(); 362 if (aggregator != null) { 363 GUIUtil.invokeAndWait( 364 new Runnable() { 365 @Override 366 public void run() { 367 aggregator.save(); 368 } 369 }); 370 } 371 } 372 373 /** 374 * Calls the superclass implementation, {@link #getComponentCreate creates} 375 * this {@code SwingControl}'s {@code Component} if necessary, then 376 * {@link #initComponentOnEventThread initializes} it. 377 */ 378 @Override 379 public void start(Navigator navigator, Map<String, String> parameters) 380 throws NavigationAbortedException, InvalidParameterException { 381 382 super.start(navigator, parameters); 383 384 if (propSource != null) { 385 if (propNames == null) { 386 propSource.addPropertyChangeListener(this); 387 } else { 388 for (String propName : propNames) { 389 propSource.addPropertyChangeListener(propName, this); 390 } 391 } 392 } 393 394 Component comp = getComponentCreate(); 395 if (comp != null) { 396 initComponentOnEventThread(); 397 } 398 } 399 400 /** 401 * Calls the superclass implementation, then {@link 402 * #deinitComponentOnEventThread deinitializes} this {@code SwingControl}'s 403 * {@code Component}. 404 * 405 * @exception NavigationAbortedException 406 * if the navigation is cancelled, or a save fails 407 */ 408 @Override 409 public void stop(boolean isCancel) throws NavigationAbortedException { 410 super.stop(isCancel); 411 412 Component comp = getComponent(); 413 if (comp != null) { 414 deinitComponentOnEventThread(); 415 } 416 417 if (propSource != null) { 418 propSource.removePropertyChangeListener(this); 419 propSource = null; 420 propNames = null; 421 } 422 } 423 424 // 425 // DefaultControl methods 426 // 427 428 @Override 429 protected void removeChild(int index) { 430 synchronized (children) { 431 Control child = children.get(index); 432 children.remove(index); 433 dialogChildren.remove(child); 434 } 435 } 436 437 // 438 // SwingControl methods 439 // 440 441 /** 442 * Calls {@link DefaultControl#addChildren addChildren} with the given 443 * controls. When a child in the given list is started, it will 444 * automatically be placed in a modal dialog that corresponds to the child's 445 * lifecycle. 446 */ 447 public void addDialogChildren(Control... controls) { 448 super.addChildren(controls); 449 for (Control child : controls) { 450 dialogChildren.add(child); 451 } 452 } 453 454 /** 455 * Configures the just-{@code #createComponent created} {@code Component} 456 * for this {@code SwingControl}. This method is called automatically just 457 * after {@code createComponent}. 458 * <p/> 459 * This default implementation does nothing. Most subclasses can do all 460 * configuration in {@link #createComponent}. Only implementors that wish 461 * to provide a common configuration for all {@code Component}s created by 462 * their subclasses need to implement this method. 463 * 464 * @param comp 465 * the newly-created {@code Component} 466 */ 467 protected void configComponent(C comp) { 468 } 469 470 /** 471 * Creates the {@code Component} for this {@code SwingControl}. 472 * <p/> 473 * This default implementation does nothing and returns {@code null}. 474 * 475 * @return the newly-created {@code Component} 476 */ 477 protected C createComponent() { 478 return null; 479 } 480 481 /** 482 * De-initializes the {@code Component} for this {@code SwingControl}. This 483 * method is called automatically when this {@code Control} is stopped. 484 * <p/> 485 * This default implementation does nothing. 486 */ 487 protected void deinitComponent() { 488 } 489 490 /** 491 * Calls {@link #deinitComponent} on the event queue thread and waits until 492 * it has completed. This method is called automatically when this {@code 493 * Control} is stopped. 494 */ 495 protected void deinitComponentOnEventThread() { 496 GUIUtil.invokeAndWait( 497 new Runnable() { 498 @Override 499 public void run() { 500 deinitComponent(); 501 } 502 }); 503 } 504 505 /** 506 * Gets the {@link ChangeableAggregator}, if any, for this class. This is 507 * used in the default implementation of some {@link Control} methods to 508 * track changes to this {@code Control}. 509 * <p/> 510 * This default implementation returns {@code null}. 511 * 512 * @return a {@link ChangeableAggregator}, or {@code null} if this 513 * {@code SwingControl} has none 514 */ 515 public ChangeableAggregator getChangeableAggregator() { 516 return null; 517 } 518 519 /** 520 * Gets the {@code Component} for this {@code SwingControl}, {@link 521 * #createComponent creating} it, {@code #configComponent configuring} it, 522 * and {@code #setComponent setting} it first if necessary. 523 * <p/> 524 * This method is not thread-safe; external locking may be necessary to 525 * ensure that {@link #createComponent} is not called by multiple threads. 526 * 527 * @return a {@code Component}, or {@code null} if this {@code 528 * SwingControl} has no {@code Component}. 529 */ 530 public C getComponentCreate() { 531 C comp = getComponent(); 532 if (comp == null) { 533 comp = createComponent(); 534 if (comp != null) { 535 setComponent(comp); 536 configComponent(comp); 537 } 538 } 539 return comp; 540 } 541 542 /** 543 * Gets the {@link ComponentStack} to which {@code Component}s of 544 * descendant {@code SwingControl}s may be added. 545 * 546 * @return a {@link ComponentStack}, or {@code null} to defer to 547 * {@code SwingControl}s higher in the navigation stack 548 */ 549 public ComponentStack getComponentStack() { 550 return stack; 551 } 552 553 /** 554 * Gets whether property changes to the {@link #setPropertyChangeSource 555 * property change source} are temporarily ignored. 556 * 557 * @see #setPropertyChangeIgnore 558 */ 559 public boolean getPropertyChangeIgnore() { 560 return ignorePropChange; 561 } 562 563 /** 564 * Gets a custom message to display in the dialog that prompts the user to 565 * {@link #getUnsavedChangesAction handle unsaved changes}. 566 * <p/> 567 * This default implementation returns {@code null}. 568 * 569 * @return a {@code String}, or {@code null} to use the default message 570 */ 571 protected String getUnsavedChangesMessage() { 572 return null; 573 } 574 575 /** 576 * Initializes the {@code Component} for this {@code SwingControl}. This 577 * method is called automatically when this {@code Control} is started. 578 * <p/> 579 * This default implementation does nothing. 580 */ 581 protected void initComponent() { 582 } 583 584 /** 585 * Calls {@link #initComponent} on the event queue thread and waits until it 586 * has completed. This method is called automatically when this {@code 587 * Control} is started. 588 */ 589 protected void initComponentOnEventThread() { 590 GUIUtil.invokeAndWait( 591 new Runnable() { 592 @Override 593 public void run() { 594 initComponent(); 595 } 596 }); 597 } 598 599 /** 600 * Gets the {@code Component} for this {@code SwingControl}. 601 * 602 * @param comp 603 * a {@code Component}, or {@code null} if this {@code 604 * SwingControl} has no {@code Component}. 605 * 606 * @see #getComponent 607 */ 608 protected void setComponent(C comp) { 609 this.comp = comp; 610 } 611 612 /** 613 * Sets the {@link ComponentStack} to which {@code Component}s of 614 * descendant {@code SwingControl}s may be added. 615 * 616 * @param stack 617 * a {@link ComponentStack}, or {@code null} to defer to 618 * {@code SwingControl}s higher in the navigation stack 619 */ 620 protected void setComponentStack(ComponentStack stack) { 621 this.stack = stack; 622 } 623 624 /** 625 * Sets whether property changes to the {@link #setPropertyChangeSource 626 * property change source} are temporarily ignored. 627 * 628 * @see #getPropertyChangeIgnore 629 */ 630 public void setPropertyChangeIgnore(boolean ignorePropChange) { 631 this.ignorePropChange = ignorePropChange; 632 } 633 634 /** 635 * Sets a {@link ManagedObject} to which this control should listen 636 * for {@code PropertyChangeEvent}s once it is started. Should not 637 * be called while the control is started. 638 * 639 * @param propSource 640 * the {@link ManagedObject} to listen to 641 * 642 * @param propNames 643 * the names of the properties in which to listen for changes, 644 * or an empty array to listen for changes in all properties on 645 * {@code propSource} 646 */ 647 protected void setPropertyChangeSource(ManagedObject propSource, 648 String... propNames) { 649 650 assert !isStarted(); 651 this.propSource = propSource; 652 this.propNames = propNames == null || propNames.length == 0 ? null : 653 propNames; 654 } 655 } 656