001: /*******************************************************************************
002: * Copyright (c) 2004, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.ui.internal.keys;
011:
012: import java.util.ArrayList;
013: import java.util.Collection;
014: import java.util.Comparator;
015: import java.util.Iterator;
016: import java.util.List;
017: import java.util.Map;
018: import java.util.ResourceBundle;
019: import java.util.SortedMap;
020: import java.util.TreeMap;
021:
022: import org.eclipse.core.commands.Command;
023: import org.eclipse.core.commands.ParameterizedCommand;
024: import org.eclipse.core.commands.common.CommandException;
025: import org.eclipse.core.commands.common.NotDefinedException;
026: import org.eclipse.jface.bindings.Binding;
027: import org.eclipse.jface.bindings.TriggerSequence;
028: import org.eclipse.jface.bindings.keys.KeySequence;
029: import org.eclipse.jface.bindings.keys.KeyStroke;
030: import org.eclipse.jface.dialogs.Dialog;
031: import org.eclipse.jface.dialogs.PopupDialog;
032: import org.eclipse.jface.preference.PreferenceDialog;
033: import org.eclipse.jface.window.Window;
034: import org.eclipse.swt.SWT;
035: import org.eclipse.swt.graphics.Point;
036: import org.eclipse.swt.graphics.Rectangle;
037: import org.eclipse.swt.layout.GridData;
038: import org.eclipse.swt.layout.GridLayout;
039: import org.eclipse.swt.widgets.Composite;
040: import org.eclipse.swt.widgets.Control;
041: import org.eclipse.swt.widgets.Event;
042: import org.eclipse.swt.widgets.Label;
043: import org.eclipse.swt.widgets.Listener;
044: import org.eclipse.swt.widgets.Shell;
045: import org.eclipse.swt.widgets.Table;
046: import org.eclipse.swt.widgets.TableColumn;
047: import org.eclipse.swt.widgets.TableItem;
048: import org.eclipse.ui.IWorkbench;
049: import org.eclipse.ui.activities.IActivityManager;
050: import org.eclipse.ui.commands.ICommandService;
051: import org.eclipse.ui.contexts.IContextService;
052: import org.eclipse.ui.dialogs.PreferencesUtil;
053: import org.eclipse.ui.internal.util.Util;
054: import org.eclipse.ui.keys.IBindingService;
055:
056: import com.ibm.icu.text.MessageFormat;
057:
058: /**
059: * <p>
060: * A dialog displaying a list of key bindings. The dialog will execute a command
061: * if it is selected.
062: * </p>
063: * <p>
064: * The methods on this class are not thread-safe and must be run from the UI
065: * thread.
066: * </p>
067: *
068: * @since 3.1
069: */
070: final class KeyAssistDialog extends PopupDialog {
071:
072: /**
073: * The data key for the binding stored on an SWT widget. The key is a
074: * fully-qualified name, but in reverse order. This is so that the equals
075: * method will detect misses faster.
076: */
077: private static final String BINDING_KEY = "Binding.bindings.jface.eclipse.org"; //$NON-NLS-1$
078:
079: /**
080: * The value of <code>previousWidth</code> to set if there is no
081: * remembered width.
082: */
083: private static final int NO_REMEMBERED_WIDTH = -1;
084:
085: /**
086: * The translation bundle in which to look up internationalized text.
087: */
088: private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle
089: .getBundle(KeyAssistDialog.class.getName());
090:
091: /**
092: * The activity manager for the associated workbench.
093: */
094: private final IActivityManager activityManager;
095:
096: /**
097: * The binding service for the associated workbench.
098: */
099: private final IBindingService bindingService;
100:
101: /**
102: * The binding that was selected when the key assist dialog last closed.
103: * This is only remembered until <code>clearRememberedState()</code> is
104: * called.
105: */
106: private Binding binding = null;
107:
108: /**
109: * The ordered list of command identifiers corresponding to the table.
110: */
111: private final List bindings = new ArrayList();
112:
113: /**
114: * The command service for the associated workbench.
115: */
116: private final ICommandService commandService;
117:
118: /**
119: * The table containing of the possible completions. This value is
120: * <code>null</code> until the dialog is created.
121: */
122: private Table completionsTable = null;
123:
124: /**
125: * Whether this dialog is currently holding some remembered state.
126: */
127: private boolean hasRememberedState = false;
128:
129: /**
130: * The key binding state for the associated workbench.
131: */
132: private final KeyBindingState keyBindingState;
133:
134: /**
135: * The width of the shell when it was previously open. This is only
136: * remembered until <code>clearRememberedState()</code> is called.
137: */
138: private int previousWidth = NO_REMEMBERED_WIDTH;
139:
140: /**
141: * The key binding listener for the associated workbench.
142: */
143: private final WorkbenchKeyboard workbenchKeyboard;
144:
145: /**
146: * A sorted map of conflicts to be used when the dialog pops up.
147: *
148: * @since 3.3
149: */
150: private SortedMap conflictMatches;
151:
152: /**
153: * Constructs a new instance of <code>KeyAssistDialog</code>. When the
154: * dialog is first constructed, it contains no widgets. The dialog is first
155: * created with no parent. If a parent is required, call
156: * <code>setParentShell()</code>. Also, between uses, it might be
157: * necessary to call <code>setParentShell()</code> as well.
158: *
159: * @param workbench
160: * The workbench in which this dialog is created; must not be
161: * <code>null</code>.
162: * @param associatedKeyboard
163: * The key binding listener for the workbench; must not be
164: * <code>null</code>.
165: * @param associatedState
166: * The key binding state associated with the workbench; must not
167: * be <code>null</code>.
168: */
169: KeyAssistDialog(final IWorkbench workbench,
170: final WorkbenchKeyboard associatedKeyboard,
171: final KeyBindingState associatedState) {
172: super ((Shell) null, PopupDialog.INFOPOPUP_SHELLSTYLE, true,
173: false, false, false, null, null);
174:
175: this .activityManager = workbench.getActivitySupport()
176: .getActivityManager();
177: this .bindingService = (IBindingService) workbench
178: .getService(IBindingService.class);
179: this .commandService = (ICommandService) workbench
180: .getService(ICommandService.class);
181: this .keyBindingState = associatedState;
182: this .workbenchKeyboard = associatedKeyboard;
183:
184: this .setInfoText(getKeySequenceString());
185: }
186:
187: /**
188: * Clears out the remembered state of the key assist dialog. This includes
189: * its width, as well as the selected binding.
190: */
191: final void clearRememberedState() {
192: previousWidth = NO_REMEMBERED_WIDTH;
193: binding = null;
194: hasRememberedState = false;
195: }
196:
197: /**
198: * Closes this shell, but first remembers some state of the dialog. This way
199: * it will have a response if asked to open the dialog again or if asked to
200: * open the keys preference page. This does not remember the internal state.
201: *
202: * @return Whether the shell was already closed.
203: */
204: public final boolean close() {
205: return close(false);
206: }
207:
208: /**
209: * Closes this shell, but first remembers some state of the dialog. This way
210: * it will have a response if asked to open the dialog again or if asked to
211: * open the keys preference page.
212: *
213: * @param rememberState
214: * Whether the internal state should be remembered.
215: * @return Whether the shell was already closed.
216: */
217: public final boolean close(final boolean rememberState) {
218: return close(rememberState, true);
219: }
220:
221: /**
222: * Closes this shell, but first remembers some state of the dialog. This way
223: * it will have a response if asked to open the dialog again or if asked to
224: * open the keys preference page.
225: *
226: * @param rememberState
227: * Whether the internal state should be remembered.
228: * @param resetState
229: * Whether the state should be reset.
230: * @return Whether the shell was already closed.
231: */
232: private final boolean close(final boolean rememberState,
233: final boolean resetState) {
234: final Shell shell = getShell();
235: if (rememberState) {
236: // Remember the previous width.
237: final int widthToRemember;
238: if ((shell != null) && (!shell.isDisposed())) {
239: widthToRemember = getShell().getSize().x;
240: } else {
241: widthToRemember = NO_REMEMBERED_WIDTH;
242: }
243:
244: // Remember the selected command name and key sequence.
245: final Binding bindingToRemember;
246: if ((completionsTable != null)
247: && (!completionsTable.isDisposed())) {
248: final int selectedIndex = completionsTable
249: .getSelectionIndex();
250: if (selectedIndex != -1) {
251: final TableItem selectedItem = completionsTable
252: .getItem(selectedIndex);
253: bindingToRemember = (Binding) selectedItem
254: .getData(BINDING_KEY);
255: } else {
256: bindingToRemember = null;
257: }
258: } else {
259: bindingToRemember = null;
260: }
261:
262: rememberState(widthToRemember, bindingToRemember);
263: completionsTable = null;
264: }
265:
266: if (resetState) {
267: keyBindingState.reset();
268: }
269: return super .close();
270: }
271:
272: /**
273: * Sets the position for the dialog based on the position of the workbench
274: * window. The dialog is flush with the bottom right corner of the workbench
275: * window. However, the dialog will not appear outside of the display's
276: * client area.
277: *
278: * @param size
279: * The final size of the dialog; must not be <code>null</code>.
280: */
281: private final void configureLocation(final Point size) {
282: final Shell shell = getShell();
283:
284: final Shell workbenchWindowShell = keyBindingState
285: .getAssociatedWindow().getShell();
286: final int xCoord;
287: final int yCoord;
288: if (workbenchWindowShell != null) {
289: /*
290: * Position the shell at the bottom right corner of the workbench
291: * window
292: */
293: final Rectangle workbenchWindowBounds = workbenchWindowShell
294: .getBounds();
295: xCoord = workbenchWindowBounds.x
296: + workbenchWindowBounds.width - size.x - 10;
297: yCoord = workbenchWindowBounds.y
298: + workbenchWindowBounds.height - size.y - 10;
299:
300: } else {
301: xCoord = 0;
302: yCoord = 0;
303:
304: }
305: final Rectangle bounds = new Rectangle(xCoord, yCoord, size.x,
306: size.y);
307: shell.setBounds(getConstrainedShellBounds(bounds));
308: }
309:
310: /**
311: * Sets the size for the dialog based on its previous size. The width of the
312: * dialog is its previous width, if it exists. Otherwise, it is simply the
313: * packed width of the dialog. The maximum width is 40% of the workbench
314: * window's width. The dialog's height is the packed height of the dialog to
315: * a maximum of half the height of the workbench window.
316: *
317: * @return The size of the dialog
318: */
319: private final Point configureSize() {
320: final Shell shell = getShell();
321:
322: // Get the packed size of the shell.
323: shell.pack();
324: final Point size = shell.getSize();
325:
326: // Use the previous width if appropriate.
327: if ((previousWidth != NO_REMEMBERED_WIDTH)
328: && (previousWidth > size.x)) {
329: size.x = previousWidth;
330: }
331:
332: // Enforce maximum sizing.
333: final Shell workbenchWindowShell = keyBindingState
334: .getAssociatedWindow().getShell();
335: if (workbenchWindowShell != null) {
336: final Point workbenchWindowSize = workbenchWindowShell
337: .getSize();
338: final int maxWidth = workbenchWindowSize.x * 2 / 5;
339: final int maxHeight = workbenchWindowSize.y / 2;
340: if (size.x > maxWidth) {
341: size.x = maxWidth;
342: }
343: if (size.y > maxHeight) {
344: size.y = maxHeight;
345: }
346: }
347:
348: // Set the size for the shell.
349: shell.setSize(size);
350: return size;
351: }
352:
353: /**
354: * Returns a string representing the key sequence used to open this dialog.
355: *
356: * @return the string describing the key sequence, or <code>null</code> if
357: * it cannot be determined.
358: */
359: private String getKeySequenceString() {
360: final Command command = commandService
361: .getCommand("org.eclipse.ui.window.showKeyAssist"); //$NON-NLS-1$
362: final TriggerSequence[] keyBindings = bindingService
363: .getActiveBindingsFor(new ParameterizedCommand(command,
364: null));
365: final int keyBindingsCount = keyBindings.length;
366: final KeySequence currentState = keyBindingState
367: .getCurrentSequence();
368: final int prefixSize = currentState.getKeyStrokes().length;
369:
370: // Try to find the first possible matching key binding.
371: KeySequence keySequence = null;
372: for (int i = 0; i < keyBindingsCount; i++) {
373: keySequence = (KeySequence) keyBindings[i];
374:
375: // Now just double-check to make sure the key is still possible.
376: if (prefixSize > 0) {
377: if (keySequence.startsWith(currentState, false)) {
378: /*
379: * Okay, so we have a partial match. Replace the key binding
380: * with the required suffix completion.
381: */
382: final KeyStroke[] oldKeyStrokes = keySequence
383: .getKeyStrokes();
384: final int newSize = oldKeyStrokes.length
385: - prefixSize;
386: final KeyStroke[] newKeyStrokes = new KeyStroke[newSize];
387: System.arraycopy(oldKeyStrokes, prefixSize,
388: newKeyStrokes, 0, newSize);
389: keySequence = KeySequence
390: .getInstance(newKeyStrokes);
391: break;
392: }
393:
394: /*
395: * The prefix doesn't match, so null out the key binding and try
396: * again.
397: */
398: keySequence = null;
399: continue;
400:
401: }
402:
403: // There is no prefix, so just grab the first.
404: break;
405: }
406: if (keySequence == null) {
407: return null; // couldn't find a suitable key binding
408: }
409:
410: return MessageFormat.format(Util.translateString(
411: RESOURCE_BUNDLE, "openPreferencePage"), //$NON-NLS-1$
412: new Object[] { keySequence.format() });
413: }
414:
415: /**
416: * Creates the content area for the key assistant. This creates a table and
417: * places it inside the composite. The composite will contain a list of all
418: * the key bindings.
419: *
420: * @param parent
421: * The parent composite to contain the dialog area; must not be
422: * <code>null</code>.
423: */
424: protected final Control createDialogArea(final Composite parent) {
425: // First, register the shell type with the context support
426: registerShellType();
427:
428: // Create a composite for the dialog area.
429: final Composite composite = new Composite(parent, SWT.NONE);
430: final GridLayout compositeLayout = new GridLayout();
431: compositeLayout.marginHeight = 0;
432: compositeLayout.marginWidth = 0;
433: composite.setLayout(compositeLayout);
434: composite.setLayoutData(new GridData(GridData.FILL_BOTH));
435: composite.setBackground(parent.getBackground());
436:
437: // Layout the partial matches.
438: final SortedMap partialMatches;
439: if (conflictMatches != null) {
440: partialMatches = conflictMatches;
441: conflictMatches = null;
442: } else {
443: partialMatches = getPartialMatches();
444: }
445:
446: if (partialMatches.isEmpty()) {
447: createEmptyDialogArea(composite);
448: } else {
449: createTableDialogArea(composite, partialMatches);
450: }
451: return composite;
452: }
453:
454: /**
455: * Creates an empty dialog area with a simple message saying there were no
456: * matches. This is used if no partial matches could be found. This should
457: * not really ever happen, but might be possible if the commands are
458: * changing while waiting for this dialog to open.
459: *
460: * @param parent
461: * The parent composite for the dialog area; must not be
462: * <code>null</code>.
463: */
464: private final void createEmptyDialogArea(final Composite parent) {
465: final Label noMatchesLabel = new Label(parent, SWT.NULL);
466: noMatchesLabel.setText(Util.translateString(RESOURCE_BUNDLE,
467: "NoMatches.Message")); //$NON-NLS-1$
468: noMatchesLabel.setLayoutData(new GridData(GridData.FILL_BOTH));
469: noMatchesLabel.setBackground(parent.getBackground());
470: }
471:
472: /**
473: * Creates a dialog area with a table of the partial matches for the current
474: * key binding state. The table will be either the minimum width, or
475: * <code>previousWidth</code> if it is not
476: * <code>NO_REMEMBERED_WIDTH</code>.
477: *
478: * @param parent
479: * The parent composite for the dialog area; must not be
480: * <code>null</code>.
481: * @param partialMatches
482: * The lexicographically sorted map of partial matches for the
483: * current state; must not be <code>null</code> or empty.
484: */
485: private final void createTableDialogArea(final Composite parent,
486: final SortedMap partialMatches) {
487: // Layout the table.
488: completionsTable = new Table(parent, SWT.FULL_SELECTION
489: | SWT.SINGLE);
490: final GridData gridData = new GridData(GridData.FILL_BOTH);
491: completionsTable.setLayoutData(gridData);
492: completionsTable.setBackground(parent.getBackground());
493: completionsTable.setLinesVisible(true);
494:
495: // Initialize the columns and rows.
496: bindings.clear();
497: final TableColumn columnCommandName = new TableColumn(
498: completionsTable, SWT.LEFT, 0);
499: final TableColumn columnKeySequence = new TableColumn(
500: completionsTable, SWT.LEFT, 1);
501: final Iterator itemsItr = partialMatches.entrySet().iterator();
502: while (itemsItr.hasNext()) {
503: final Map.Entry entry = (Map.Entry) itemsItr.next();
504: final TriggerSequence sequence = (TriggerSequence) entry
505: .getValue();
506: final Binding binding = (Binding) entry.getKey();
507: final ParameterizedCommand command = binding
508: .getParameterizedCommand();
509: try {
510: final String[] text = { command.getName(),
511: sequence.format() };
512: final TableItem item = new TableItem(completionsTable,
513: SWT.NULL);
514: item.setText(text);
515: item.setData(BINDING_KEY, binding);
516: bindings.add(binding);
517: } catch (NotDefinedException e) {
518: // Not much to do, but this shouldn't really happen.
519: }
520: }
521:
522: Dialog.applyDialogFont(parent);
523: columnKeySequence.pack();
524: if (previousWidth != NO_REMEMBERED_WIDTH) {
525: columnKeySequence.setWidth(previousWidth);
526: }
527: columnCommandName.pack();
528:
529: /*
530: * If you double-click on the table, it should execute the selected
531: * command.
532: */
533: completionsTable.addListener(SWT.DefaultSelection,
534: new Listener() {
535: public final void handleEvent(final Event event) {
536: executeKeyBinding(event);
537: }
538: });
539: }
540:
541: /**
542: * Edits the remembered selection in the preference dialog.
543: */
544: private final void editKeyBinding() {
545: // Create a preference dialog on the keys preference page.
546: final String keysPageId = "org.eclipse.ui.preferencePages.Keys"; //$NON-NLS-1$
547: final PreferenceDialog dialog = PreferencesUtil
548: .createPreferenceDialogOn(getShell(), keysPageId, null,
549: binding);
550:
551: /*
552: * Forget the remembered state (so we don't get stuck editing
553: * preferences).
554: */
555: clearRememberedState();
556:
557: // Open the dialog (blocking).
558: dialog.open();
559: }
560:
561: /**
562: * Handles the default selection event on the table of possible completions.
563: * This attempts to execute the given command.
564: */
565: private final void executeKeyBinding(final Event trigger) {
566: // Try to execute the corresponding command.
567: final int selectionIndex = completionsTable.getSelectionIndex();
568: if (selectionIndex >= 0) {
569: final Binding binding = (Binding) bindings
570: .get(selectionIndex);
571: try {
572: workbenchKeyboard.executeCommand(binding, trigger);
573: } catch (final CommandException e) {
574: workbenchKeyboard.logException(e, binding
575: .getParameterizedCommand());
576: }
577: }
578: }
579:
580: /**
581: * Gets the list of key bindings that are partial matches to the current key
582: * binding state.
583: *
584: * @return A sorted map of key sequences (KeySequence) to command identifier
585: * (String) representing the list of enabled commands that could
586: * possibly complete the current key sequence.
587: */
588: private final SortedMap getPartialMatches() {
589: // Put all partial matches into the matches into the map.
590: final Map partialMatches = bindingService
591: .getPartialMatches(keyBindingState.getCurrentSequence());
592:
593: // Create a sorted map that sorts based on lexicographical order.
594: final SortedMap sortedMatches = new TreeMap(new Comparator() {
595: public final int compare(final Object a, final Object b) {
596: final Binding bindingA = (Binding) a;
597: final Binding bindingB = (Binding) b;
598: final ParameterizedCommand commandA = bindingA
599: .getParameterizedCommand();
600: final ParameterizedCommand commandB = bindingB
601: .getParameterizedCommand();
602: try {
603: return commandA.getName().compareTo(
604: commandB.getName());
605: } catch (final NotDefinedException e) {
606: // should not happen
607: return 0;
608: }
609: }
610: });
611:
612: /*
613: * Remove those partial matches for which either the command is not
614: * identified or the activity manager believes the command is not
615: * enabled.
616: */
617: final Iterator partialMatchItr = partialMatches.entrySet()
618: .iterator();
619: while (partialMatchItr.hasNext()) {
620: final Map.Entry entry = (Map.Entry) partialMatchItr.next();
621: final Binding binding = (Binding) entry.getValue();
622: final Command command = binding.getParameterizedCommand()
623: .getCommand();
624: if (command.isDefined()
625: && activityManager.getIdentifier(command.getId())
626: .isEnabled()) {
627: sortedMatches.put(binding, entry.getKey());
628: }
629: }
630:
631: return sortedMatches;
632:
633: }
634:
635: /**
636: * Returns whether the dialog is currently holding some remembered state.
637: *
638: * @return <code>true</code> if the dialog has remembered state;
639: * <code>false</code> otherwise.
640: */
641: private final boolean hasRememberedState() {
642: return hasRememberedState;
643: }
644:
645: /**
646: * Opens this dialog. This method can be called multiple times on the same
647: * dialog. This only opens the dialog if there is no remembered state; if
648: * there is remembered state, then it tries to open the preference page
649: * instead.
650: *
651: * @return The return code from this dialog.
652: */
653: public final int open() {
654: // If there is remember state, open the preference page.
655: if (hasRememberedState()) {
656: editKeyBinding();
657: clearRememberedState();
658: return Window.OK;
659: }
660:
661: // If the dialog is already open, dispose the shell and recreate it.
662: final Shell shell = getShell();
663: if (shell != null) {
664: close(false, false);
665: }
666: create();
667:
668: // Configure the size and location.
669: final Point size = configureSize();
670: configureLocation(size);
671:
672: // Call the super method.
673: return super .open();
674: }
675:
676: /**
677: * Opens this dialog with the list of bindings for the user to select from.
678: *
679: * @return The return code from this dialog.
680: * @since 3.3
681: */
682: public final int open(Collection bindings) {
683: conflictMatches = new TreeMap(new Comparator() {
684: public final int compare(final Object a, final Object b) {
685: final Binding bindingA = (Binding) a;
686: final Binding bindingB = (Binding) b;
687: final ParameterizedCommand commandA = bindingA
688: .getParameterizedCommand();
689: final ParameterizedCommand commandB = bindingB
690: .getParameterizedCommand();
691: try {
692: return commandA.getName().compareTo(
693: commandB.getName());
694: } catch (final NotDefinedException e) {
695: // should not happen
696: return 0;
697: }
698: }
699: });
700: Iterator i = bindings.iterator();
701: while (i.hasNext()) {
702: Binding b = (Binding) i.next();
703: conflictMatches.put(b, b.getTriggerSequence());
704: }
705:
706: // If the dialog is already open, dispose the shell and recreate it.
707: final Shell shell = getShell();
708: if (shell != null) {
709: close(false, false);
710: }
711: create();
712:
713: // Configure the size and location.
714: final Point size = configureSize();
715: configureLocation(size);
716:
717: // Call the super method.
718: return super .open();
719: }
720:
721: /**
722: * Registers the shell as the same type as its parent with the context
723: * support. This ensures that it does not modify the current state of the
724: * application.
725: */
726: private final void registerShellType() {
727: final Shell shell = getShell();
728: final IContextService contextService = (IContextService) keyBindingState
729: .getAssociatedWindow().getWorkbench().getService(
730: IContextService.class);
731: contextService.registerShell(shell, contextService
732: .getShellType((Shell) shell.getParent()));
733: }
734:
735: /**
736: * Remembers the current state of this dialog.
737: *
738: * @param previousWidth
739: * The previous width of the dialog.
740: * @param binding
741: * The binding to remember, may be <code>null</code> if none.
742: */
743: private final void rememberState(final int previousWidth,
744: final Binding binding) {
745: this .previousWidth = previousWidth;
746: this .binding = binding;
747: hasRememberedState = true;
748: }
749:
750: /**
751: * Exposing this within the keys package.
752: *
753: * @param newParentShell
754: * The new parent shell; this value may be <code>null</code> if
755: * there is to be no parent.
756: */
757: protected final void setParentShell(final Shell newParentShell) {
758: super.setParentShell(newParentShell);
759: }
760: }
|