001: /*******************************************************************************
002: * Copyright (c) 2004, 2006 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.presentations.util;
011:
012: import org.eclipse.jface.util.Geometry;
013: import org.eclipse.swt.SWT;
014: import org.eclipse.swt.events.ControlEvent;
015: import org.eclipse.swt.events.ControlListener;
016: import org.eclipse.swt.events.DisposeEvent;
017: import org.eclipse.swt.events.DisposeListener;
018: import org.eclipse.swt.graphics.Point;
019: import org.eclipse.swt.graphics.Rectangle;
020: import org.eclipse.swt.widgets.Composite;
021: import org.eclipse.swt.widgets.Control;
022: import org.eclipse.swt.widgets.Event;
023: import org.eclipse.swt.widgets.Layout;
024: import org.eclipse.swt.widgets.Listener;
025: import org.eclipse.ui.internal.dnd.DragUtil;
026: import org.eclipse.ui.internal.dnd.SwtUtil;
027: import org.eclipse.ui.internal.layout.SizeCache;
028:
029: /**
030: * A ProxyControl is an invisible control whose size and position are linked
031: * with some target control. That is, when the dummy control is asked for its
032: * preferred size it returns the preferred size of the target. Changing the
033: * bounds of the dummy control also changes the bounds of the target. This allows
034: * any Composite to lay out a control that isn't one of its children.
035: *
036: * <p>
037: * For example, imagine you have a ViewForm and a ToolBar that share the same parent
038: * and you want the ToolBar to be located in the upper-right corner of the ViewForm.
039: * If the ToolBar were a child of the ViewForm, this could be done easily by calling
040: * viewForm.setTopRight(toolBar). However, this is impossible since ViewForm.setTopRight
041: * will only accept a child control. Instead, we create a ProxyControl as a child
042: * of the viewForm, and set the toolbar as its target. The ViewForm will treat
043: * the ProxyControl just like any other child, but it will actually be arranging the
044: * ToolBar.
045: * </p>
046: * <p>For example:
047: * </p>
048: * <code>
049: * // Create a ViewForm and a ToolBar that are siblings
050: * ViewForm viewForm = new ViewForm(parent, SWT.NONE);
051: * ToolBar toolBar = new ToolBar(parent, SWT.NONE);
052: *
053: * // Allow the ViewForm to control the position of the ToolBar by creating
054: * // a ProxyControl in the ViewForm that targets the ToolBar.
055: * ProxyControl toolBarProxy = new ProxyControl(viewForm);
056: * toolBarProxy.setTarget(toolBar);
057: * viewForm.setTopRight(toolBarProxy.getControl());
058: * </code>
059: *
060: * <p>
061: * This is intended to simplify management of view toolbars in the presentation API.
062: * Presentation objects have no control over where the view toolbars are created in
063: * the widget hierarchy, but they may wish to control the position of the view toolbars
064: * using traditional SWT layouts and composites.
065: * </p>
066: */
067: public class ProxyControl {
068:
069: /**
070: * Invisible dummy control
071: */
072: private Composite control;
073:
074: /**
075: * Target control (possibly null)
076: */
077: private Control target = null;
078:
079: /**
080: * Target cache (possibly null)
081: */
082: private SizeCache targetCache = null;
083:
084: /**
085: * Most specific common ancestor between the target and the proxy controls
086: */
087: private Control commonAncestor;
088:
089: /**
090: * Visibility state of the proxy control the last time it had a non-null target.
091: * Note: when the target is set to null, we force the proxy to become invisible
092: * and use this variable to remember the initial state when we get a new non-null
093: * target.
094: */
095: private boolean visible = true;
096:
097: /**
098: * Dispose listener. Breaks the link between the target and the proxy if either
099: * control is disposed.
100: */
101: private DisposeListener disposeListener = new DisposeListener() {
102: public void widgetDisposed(DisposeEvent e) {
103: if (e.widget == target || e.widget == control) {
104: setTargetControl(null);
105: }
106: }
107: };
108:
109: private Listener visibilityListener = new Listener() {
110:
111: public void handleEvent(Event event) {
112: if (target != null) {
113: visible = control.getVisible();
114: target.setVisible(visible);
115: }
116: }
117:
118: };
119:
120: /**
121: * Allow the visibility of the proxy control to be updated. When the target
122: * is not <code>null</code> it's visibility is tied to the listener. But
123: * in the case where some action causes the target to be populated while
124: * its visibility is <code>false</code>, it won't re-appear until its
125: * visibility is set to <code>true</code>.
126: *
127: * @param visible
128: * <code>true</code> - set it to visible
129: * @since 3.2
130: */
131: public void setVisible(boolean visible) {
132: // this.visible = visible;
133: }
134:
135: /**
136: * Movement listener. Updates the bounds of the target to match the
137: * bounds of the dummy control.
138: */
139: private ControlListener controlListener = new ControlListener() {
140:
141: public void controlMoved(ControlEvent e) {
142: ProxyControl.this .layout();
143: }
144:
145: public void controlResized(ControlEvent e) {
146: //if (e.widget == control) {
147: // ProxyControl.this.layout();
148: //}
149: }
150:
151: };
152:
153: /**
154: * Creates a new ProxyControl as a child of the given parent. This is an invisible dummy
155: * control. If given a target, the ProxyControl will update the bounds of the target to
156: * match the bounds of the dummy control.
157: *
158: * @param parent parent composite
159: */
160: public ProxyControl(Composite parent) {
161: // Create the invisible dummy composite
162: control = new Composite(parent, SWT.NO_BACKGROUND);
163: control.setVisible(false);
164:
165: // Attach a layout to the dummy composite. This is used to make the preferred
166: // size of the dummy match the preferred size of the target.
167: control.setLayout(new Layout() {
168: protected void layout(Composite composite,
169: boolean flushCache) {
170: ProxyControl.this .layout();
171: // does nothing. The bounds of the target are updated by the controlListener
172: }
173:
174: protected Point computeSize(Composite composite, int wHint,
175: int hHint, boolean flushCache) {
176: if (targetCache == null) {
177: if (target != null) {
178: return target.computeSize(wHint, hHint,
179: flushCache);
180: }
181: // Note: If we returned (0,0), SWT would ignore the result and use a default value.
182: return new Point(1, 1);
183: }
184:
185: return targetCache.computeSize(wHint, hHint);
186: }
187: });
188:
189: // Attach listeners to the dummy
190: control.addDisposeListener(disposeListener);
191: control.addListener(SWT.Show, visibilityListener);
192: control.addListener(SWT.Hide, visibilityListener);
193: }
194:
195: /**
196: * Sets the control whose position will be managed by this proxy
197: *
198: * @param target the control, or null if none
199: */
200: public void setTargetControl(Control target) {
201: targetCache = null;
202: internalSetTargetControl(target);
203: }
204:
205: private void internalSetTargetControl(Control target) {
206: if (this .target != target) {
207:
208: if (this .target != null) {
209: for (Control next = control; next != commonAncestor
210: && next != null; next = next.getParent()) {
211: next.removeControlListener(controlListener);
212: }
213: commonAncestor = null;
214:
215: // If we already had a target, detach the dispose listener
216: // (prevents memory leaks due to listeners)
217: if (!this .target.isDisposed()) {
218: this .target.removeDisposeListener(disposeListener);
219: }
220: }
221:
222: if (this .target == null && target != null) {
223: // If we had previously forced the dummy control invisible, restore its visibility
224: control.setVisible(visible);
225: }
226:
227: this .target = target;
228:
229: if (target != null) {
230: commonAncestor = SwtUtil.findCommonAncestor(
231: this .target, control);
232: for (Control next = control; next != null
233: && next != commonAncestor; next = next
234: .getParent()) {
235: next.addControlListener(controlListener);
236: }
237:
238: // Make the new target's visiblity match the visibility of the dummy control
239: target.setVisible(control.getVisible());
240: // Add a dispose listener. Ensures that the target is cleared
241: // if it is ever disposed.
242: target.addDisposeListener(disposeListener);
243: } else {
244: control.setVisible(false);
245: }
246: }
247: }
248:
249: public void setTarget(SizeCache cache) {
250: targetCache = cache;
251:
252: if (targetCache != null) {
253: setTargetControl(cache.getControl());
254: } else {
255: setTargetControl(null);
256: }
257: }
258:
259: /**
260: * Returns the proxy control
261: *
262: * @return the proxy control (not null)
263: */
264: public Control getControl() {
265: return control;
266: }
267:
268: public Control getTarget() {
269: return target;
270: }
271:
272: /**
273: * Moves the target control on top of the dummy control.
274: */
275: public void layout() {
276: if (getTarget() == null) {
277: return;
278: }
279:
280: // Compute the unclipped bounds of the target in display coordinates
281: Rectangle displayBounds = Geometry.toDisplay(control
282: .getParent(), control.getBounds());
283:
284: // Clip the bounds of the target so that it doesn't go outside the dummy control's parent
285: Rectangle clippingRegion = DragUtil.getDisplayBounds(control
286: .getParent());
287: displayBounds = displayBounds.intersection(clippingRegion);
288:
289: // Compute the bounds of the target, in the local coordinate system of its parent
290: Rectangle targetBounds = Geometry.toControl(getTarget()
291: .getParent(), displayBounds);
292:
293: // Move the target
294: getTarget().setBounds(targetBounds);
295: }
296: }
|