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