As I had quite recently initiated a
discussion about enriching the
Eclipse Graphical Editing Framework (GEF) with some "advanced" editing features, I thought this to be a good opportunity to give a concrete example of what I had in mind by posting about a feature that could improve the end-user experience when working with nested viewports.
So what is the problem when working with viewports? To put it bluntly I think their biggest advantage most certainly is also their biggest disadvantage. That is, the possibility to reduce a container figure's external bounds (making it thus easier to embed it within a larger graphical context) while preserving the ability to access its internal contents in-place (even if not completely at a time) on the other hand yields the problem that navigating and editing this internal contents is often quite painful, as a lot of scrolling is usually necessary to access hidden parts, and orientation is easily lost.
To emphasize this, consider the following two versions of the same (slightly modified) 4-bit adder example, provided with GEF's sample logic editor. Despite having resized the circuit figure, both diagrams are indeed identical. And having not seen the first version before, the accessibility of the second one would most certainly be quite limited.
BTW: You might notice that by using the original GEF logic example editor you may not produce these screenshots, because connections are not clipped correctly, and because the scroll bars within the circuit figure are not transparent but opaque. The first problem can be addressed by a patch I contributed to bug #195527 (fix for clipping problems of connections in combination with viewports). The second one is something I will document herein, so keep on reading...
While those troubles mentioned before with respect to navigating and editing the contents of a viewport seem to be somehow the price to pay, the fact that its contents may never be visualized completely at a time, is something that can be circumvented by adequate feedback support. What I am referring to is a feature we implemented as part of a
reasearch project during my time at the university.
The idea was born when
Philip Ritzkopf (who was a student assistant in our research group at that time and has very much contributed to the conception and implementation of the feature) pointed me to a
post in the GEF newsgroup about a "ghost image" figure (i.e. a semi-transparent copy of some original figure) that was discussed as an option to show feedback during a drag & drop operation. As we thought that the sketched mechanism could also be suited to show the hidden contents of a viewport, Philip and I directly started to implement a first prototype. Having made some further iterations, the following screenshot shows the final result.
Note that in case the circuit figure is not selected, everything will look like before. However, in case it is selected you get the 'Ghost Image Feedback'. What a difference!
So how is it realized? Indeed the approach is rather lightweight. It basically consists of a respective
SelectionEditPolicy (namely
ScrollableSelectionFeedbackEditPolicy), some interfaces (
IScrollableFigure,
IScrollableEditPart) to assure it can only registered to host edit parts, having a figure with a nested
Viewport (or a
ScrollPane containing a nested
Viewport to be more precise), and an implementation of a
GhostImageFigure, being used to render the feedback.
The GhostImageFigure being used is pretty much inspired by the one depicted in above mentioned use newsgroup entry. In difference to the version being posted, our's does not extend ImageFigure but constructs the original figure's image lazily within its paintFigure() method. The reason to do so is that this way, disposal of the used image can be performed directly after having painting it, so that no client has to take care (while this could be an option in case any performance problems might arise). It was furthermore enhanced by a possibility to specify a transparency color, which is needed in case the original figure is a connection (whose background should indeed be regarded as transparent). Here is the source code:
01 /**
02 *
03 *
04 * Copyright (C) 2005, 2008 Research Group Software Construction,
05 * RWTH Aachen University, Germany.
06 *
07 * All rights reserved. This program and the accompanying materials
08 * are made available under the terms of the Eclipse Public License
09 * version 1.0, which accompanies this distribution, and is available
10 * at http://www.eclipse.org/legal/epl-v10.html.
11 *
12 * Contributors:
13 * Research Group Software Construction - Initial API and implementation
14 *
15 *
16 */
17 package org.eclipse.draw2d;
18
19 import org.eclipse.swt.graphics.GC;
20 import org.eclipse.swt.graphics.Image;
21 import org.eclipse.swt.graphics.ImageData;
22 import org.eclipse.swt.graphics.RGB;
23 import org.eclipse.swt.widgets.Display;
24
25 import org.eclipse.draw2d.geometry.PrecisionRectangle;
26 import org.eclipse.draw2d.geometry.Rectangle;
27
28 /**
29 * A figure used to render a partly transparent copy of an original source
30 * figure.
31 *
32 * This class is pretty much based on a sample, posted within the GEF newsgroup
33 * (http://dev.eclipse.org/newslists/news.eclipse.tools.gef/msg15158.html),
34 * although we decided to not cache the ghost image itself (but only its image
35 * data), so the figure does not have to be disposed (and may thus directly
36 * extend {@link Figure} rather than {@link ImageFigure}).
37 *
38 * @author Philip Ritzkopf
39 * @author Alexander Nyssen
40 */
41 public class GhostImageFigure extends Figure {
42
43 private int alpha = -1;
44 private ImageData ghostImageData;
45
46 /**
47 * The single constructor.
48 *
49 * @param source
50 * The original figure that will be used to render the ghost
51 * image.
52 * @param alpha
53 * The desired transparency value, to be forwarded to
54 * {@link Graphics#setAlpha(int)}.
55 * @param transparency
56 * The RBG value of the color that is to be regarded as
57 * transparent. May be <code>null</code>.
58 */
59 public GhostImageFigure(final IFigure source, int alpha, RGB transparency) {
60 this.alpha = alpha;
61
62 Rectangle sourceFigureRelativePrecisionBounds = new PrecisionRectangle(
63 source.getBounds().getCopy());
64
65 Image offscreenImage = new Image(Display.getCurrent(),
66 sourceFigureRelativePrecisionBounds.width,
67 sourceFigureRelativePrecisionBounds.height);
68
69 GC gc = new GC(offscreenImage);
70 SWTGraphics swtGraphics = new SWTGraphics(gc);
71 swtGraphics.translate(-sourceFigureRelativePrecisionBounds.x,
72 -sourceFigureRelativePrecisionBounds.y);
73 source.paint(swtGraphics);
74
75 ghostImageData = offscreenImage.getImageData();
76 if (transparency != null) {
77 ghostImageData.transparentPixel = ghostImageData.palette
78 .getPixel(transparency);
79 }
80
81 offscreenImage.dispose();
82 swtGraphics.dispose();
83 gc.dispose();
84 }
85
86 /**
87 * @see Figure#paintFigure(Graphics)
88 */
89 protected void paintFigure(Graphics graphics) {
90 Image feedbackImage = new Image(Display.getCurrent(), ghostImageData);
91 graphics.setAlpha(alpha);
92 graphics.setClip(getBounds().getCopy());
93 graphics.drawImage(feedbackImage, 0, 0, ghostImageData.width,
94 ghostImageData.height, getBounds().x, getBounds().y,
95 getBounds().width, getBounds().height);
96 feedbackImage.dispose();
97 }
98 } |
The second central piece of the solution of course is the ScrollableSelectionFeedbackEditPolicy. Upon primary selection of its host IScrollableEditPart, it creates GhostImageFigures for all nodes nested within the host figure's Viewport and all connection figures related to them. To keep up with changes to the host figure and its nested viewport respectively, a couple of listeners are required, which are registered and unregistered in the activate() and deactivate() methods respectively. Note that the layout constraints for the feedback figures are determined by translating the original figure's bounds into absolute coordinates first, then into coordinates relative to the feedback layer (which is chosen to be the SCALABLE_FEEDBACK_LAYER), so feedback is correctly displayed during zooming as well.
001 /**
002 *
003 *
004 * Copyright (C) 2005, 2008 Research Group Software Construction,
005 * RWTH Aachen University, Germany.
006 *
007 * All rights reserved. This program and the accompanying materials
008 * are made available under the terms of the Eclipse Public License
009 * version 1.0, which accompanies this distribution, and is available
010 * at http://www.eclipse.org/legal/epl-v10.html.
011 *
012 * Contributors:
013 * Research Group Software Construction - Initial API and implementation
014 *
015 *
016 */
017 package sc.viper.gef.scrolling;
018
019 import java.beans.PropertyChangeEvent;
020 import java.beans.PropertyChangeListener;
021 import java.util.ArrayList;
022 import java.util.HashSet;
023 import java.util.Iterator;
024 import java.util.List;
025
026 import org.eclipse.core.runtime.Assert;
027 import org.eclipse.draw2d.FigureListener;
028 import org.eclipse.draw2d.Graphics;
029 import org.eclipse.draw2d.IFigure;
030 import org.eclipse.draw2d.LayoutListener;
031 import org.eclipse.draw2d.ScrollPane;
032 import org.eclipse.draw2d.Viewport;
033 import org.eclipse.draw2d.ViewportUtilities;
034 import org.eclipse.draw2d.geometry.Insets;
035 import org.eclipse.draw2d.geometry.Rectangle;
036 import org.eclipse.gef.ConnectionEditPart;
037 import org.eclipse.gef.EditPart;
038 import org.eclipse.gef.GraphicalEditPart;
039 import org.eclipse.gef.LayerConstants;
040 import org.eclipse.gef.editpolicies.SelectionEditPolicy;
041
042 import sc.viper.gef.EditPartUtilities;
043
044 /**
045 * A {@link SelectionEditPolicy}, which may be registered to an
046 * {@link IScrollableEditPart} to provide primary selection feedback by
047 * rendering the hidden contents of the host figure's {@link ScrollPane}'s
048 * nested {@link Viewport} by means of {@link GhostImageFigure}s.
049 *
050 * @author Philip Ritzkopf
051 * @author Alexander Nyssen
052 * @version $Revision: 1.37 $
053 */
054 public class ScrollableSelectionFeedbackEditPolicy extends SelectionEditPolicy {
055
056 private int feedbackAlpha = 100;
057
058 private final List feedbackFigures = new ArrayList();
059
060 private final FigureListener figureListener = new FigureListener() {
061
062 public void figureMoved(IFigure source) {
063 // react on host figure move
064 if (getHost().getSelected() == EditPart.SELECTED_PRIMARY) {
065 updateFeedback();
066 }
067 }
068 };
069
070 private final LayoutListener layoutListener = new LayoutListener.Stub() {
071
072 public void invalidate(IFigure container) {
073 // react on host figure resize
074 if (getHost().getSelected() == EditPart.SELECTED_PRIMARY) {
075 updateFeedback();
076 }
077 }
078 };
079
080 private final PropertyChangeListener viewportViewLocationChangeListener = new PropertyChangeListener() {
081
082 public void propertyChange(PropertyChangeEvent event) {
083 // Make sure the host edit part is always selected as primary
084 // selection, when it fires a property change event from its
085 // viewport
086 if (event.getSource() == ((IScrollableFigure) getHostFigure())
087 .getScrollPane().getViewport()
088 && getHost().getSelected() != EditPart.SELECTED_PRIMARY) {
089 getHost().getViewer().deselectAll();
090 getHost().getViewer().select(getHost());
091 }
092 // update feedback in case the viewport's view location changed
093 if (event.getPropertyName().equals(Viewport.PROPERTY_VIEW_LOCATION)) {
094 updateFeedback();
095 }
096 }
097 };
098
099 /**
100 * @see org.eclipse.gef.editpolicies.SelectionEditPolicy#activate()
101 */
102 public void activate() {
103 super.activate();
104 // register all necessary listeners
105 for (Iterator iterator = ViewportUtilities.getViewportsPath(
106 getHostFigureViewport(),
107 ViewportUtilities.getRootViewport(getHostFigure())).iterator(); iterator
108 .hasNext();) {
109 Viewport viewport = (Viewport) iterator.next();
110 viewport
111 .addPropertyChangeListener(viewportViewLocationChangeListener);
112
113 }
114 getHostFigure().addLayoutListener(layoutListener);
115 getHostFigure().addFigureListener(figureListener);
116 }
117
118 /**
119 * Adds a given feedback figure to the feedback layer (using the provided
120 * bounds to layout it) and registers it in the local
121 * {@link #feedbackFigures} list.
122 *
123 * @param feedbackFigure
124 * the feedback figure to add to the feedback layer
125 * @param feedbackFigureAbsoluteBounds
126 * the absolute bounds used to layout the feedback figure
127 */
128 protected void addFeedbackFigure(IFigure feedbackFigure,
129 Rectangle feedbackFigureAbsoluteBounds) {
130 getFeedbackLayer().translateToRelative(feedbackFigureAbsoluteBounds);
131 getFeedbackLayer().translateFromParent(feedbackFigureAbsoluteBounds);
132 feedbackFigure.setBounds(feedbackFigureAbsoluteBounds);
133 addFeedback(feedbackFigure);
134 feedbackFigures.add(feedbackFigure);
135 }
136
137 /**
138 * Creates a ghost image feedback figure for the given
139 * {@link ConnectionEditPart}'s figure and adds it to the feedback layer.
140 *
141 * @param connectionEditPart
142 */
143 protected void createConnectionFeedbackFigure(
144 ConnectionEditPart connectionEditPart) {
145 addFeedbackFigure(new GhostImageFigure(connectionEditPart.getFigure(),
146 getAlpha(), getLayer(LayerConstants.CONNECTION_LAYER)
147 .getBackgroundColor().getRGB()),
148 getAbsoluteBounds(connectionEditPart.getFigure()));
149 }
150
151 /**
152 * Creates the connection layer feedback figures.
153 */
154 protected void createConnectionFeedbackFigures() {
155 HashSet transitiveNestedConnections = EditPartUtilities
156 .getAllNestedConnectionEditParts((GraphicalEditPart) getHost());
157
158 for (Iterator iterator = transitiveNestedConnections.iterator(); iterator
159 .hasNext();) {
160 Object connection = iterator.next();
161 if (connection instanceof ConnectionEditPart) {
162 createConnectionFeedbackFigure((ConnectionEditPart) connection);
163 }
164
165 }
166 }
167
168 /**
169 * Creates a ghost image feedback figure for the given
170 * {@link GraphicalEditPart}'s figure and adds it to the feedback layer.
171 *
172 * @param childEditPart
173 */
174 protected void createNodeFeedbackFigure(GraphicalEditPart childEditPart) {
175 addFeedbackFigure(new GhostImageFigure(childEditPart.getFigure(),
176 getAlpha(), null), getAbsoluteBounds(childEditPart.getFigure()));
177 }
178
179 /**
180 * Creates the primary layer feedback figures.
181 */
182 protected void createNodeFeedbackFigures() {
183
184 // create ghost feedback for node children
185 for (Iterator iterator = getHost().getChildren().iterator(); iterator
186 .hasNext();) {
187 Object child = iterator.next();
188 if (child instanceof GraphicalEditPart) {
189 createNodeFeedbackFigure((GraphicalEditPart) child);
190 }
191 }
192 }
193
194 /**
195 * @see org.eclipse.gef.editpolicies.SelectionEditPolicy#deactivate()
196 */
197 public void deactivate() {
198 // remove all registered listeners
199 getHostFigure().removeFigureListener(figureListener);
200 getHostFigure().removeLayoutListener(layoutListener);
201 for (Iterator iterator = ViewportUtilities.getViewportsPath(
202 getHostFigureViewport(),
203 ViewportUtilities.getRootViewport(getHostFigure())).iterator(); iterator
204 .hasNext();) {
205 Viewport viewport = (Viewport) iterator.next();
206 viewport
207 .removePropertyChangeListener(viewportViewLocationChangeListener);
208
209 }
210 super.deactivate();
211 }
212
213 /**
214 * Used to obtain the alpha value used for all feedback figures. The valid
215 * range is the one documented for {@link Graphics#setAlpha(int)}.
216 *
217 * @return the alpha
218 */
219 protected int getAlpha() {
220 return feedbackAlpha;
221 }
222
223 /**
224 * @see org.eclipse.gef.editpolicies.SelectionEditPolicy#getFeedbackLayer()
225 */
226 protected IFigure getFeedbackLayer() {
227 return getLayer(LayerConstants.SCALED_FEEDBACK_LAYER);
228 }
229
230 /**
231 * Provides access to the host figure's {@link Viewport}.
232 *
233 * @return
234 */
235 protected Viewport getHostFigureViewport() {
236 return ((IScrollableFigure) getHostFigure()).getScrollPane()
237 .getViewport();
238 }
239
240 /**
241 * Removes all feedback figures from the feedback layer as well as from the
242 * {@link #feedbackFigures} list.
243 */
244 protected void hideFeedback() {
245 for (Iterator iterator = feedbackFigures.iterator(); iterator.hasNext();) {
246 removeFeedback((IFigure) iterator.next());
247 }
248 feedbackFigures.clear();
249 }
250
251 /**
252 * @see org.eclipse.gef.editpolicies.SelectionEditPolicy#hideSelection()
253 */
254 protected void hideSelection() {
255 hideFeedback();
256 }
257
258 /**
259 * Used to specify the alpha value used for all feedback figures. The valid
260 * range is the one documented for {@link Graphics#setAlpha(int)}.
261 *
262 * @param alpha
263 */
264 public void setAlpha(int alpha) {
265 this.feedbackAlpha = alpha;
266 }
267
268 /**
269 * @see org.eclipse.gef.editpolicies.AbstractEditPolicy#setHost(org.eclipse.gef
270 * .EditPart)
271 */
272 public void setHost(EditPart host) {
273 Assert.isLegal(host instanceof IScrollableEditPart);
274 super.setHost(host);
275 }
276
277 /**
278 * Creates feedback figures for all node figures nested within the host
279 * figure's viewport, as well as for all incoming and outgoing connections
280 * of these nodes. Feedback figures are only created in case there are
281 * children or connections, which are not fully visible.
282 */
283 protected void showFeedback() {
284 // ensure primary and connection layer are revalidated,
285 // so the bounds of all their child figures, which are
286 // used to calculate the feedback figure constraints,
287 // are valid
288 getLayer(LayerConstants.CONNECTION_LAYER).validate();
289 getLayer(LayerConstants.PRIMARY_LAYER).validate();
290
291 // check if there is a node child exceeding the client are
292 Rectangle clientArea = getAbsoluteClientArea(getHostFigure());
293 boolean primaryLayerChildExceedsViewport = !clientArea
294 .equals(getAbsoluteViewportArea(((IScrollableFigure) getHostFigure())
295 .getScrollPane().getViewport()));
296 // check if there is a connection exceeding the client area
297 boolean connectionLayerChildExceedsClientArea = false;
298 List connectionLayerChildren = getLayer(LayerConstants.CONNECTION_LAYER)
299 .getChildren();
300 for (Iterator iterator = connectionLayerChildren.iterator(); iterator
301 .hasNext()
302 && !connectionLayerChildExceedsClientArea;) {
303 IFigure connectionLayerChild = (IFigure) iterator.next();
304 connectionLayerChildExceedsClientArea = (ViewportUtilities
305 .getNearestEnclosingViewport(connectionLayerChild) == ((IScrollableFigure) getHostFigure())
306 .getScrollPane().getViewport() && !clientArea.getExpanded(
307 new Insets(1, 1, 1, 1)).contains(
308 getAbsoluteBounds(connectionLayerChild)));
309 }
310
311 // Only show feedback if there is a child or connection figure whose
312 // bounds exceed the client area
313 if (primaryLayerChildExceedsViewport
314 || connectionLayerChildExceedsClientArea) {
315 createNodeFeedbackFigures();
316 createConnectionFeedbackFigures();
317 }
318 }
319
320 /**
321 * @see org.eclipse.gef.editpolicies.SelectionEditPolicy#showSelection()
322 */
323 protected void showSelection() {
324 // force ViewportExposeHelper to perform auto scrolling before
325 // showing the feedback.
326 getHost().getViewer().reveal(getHost());
327 updateFeedback();
328 }
329
330 /**
331 * Removes any existing feedback figures by delegating to
332 * {@link #hideFeedback()}. In case the host edit part is the primary
333 * selection, recreates feedback figures via {@link #showFeedback()}.
334 */
335 protected void updateFeedback() {
336 hideFeedback();
337 if (getHost().getSelected() == EditPart.SELECTED_PRIMARY) {
338 showFeedback();
339 }
340 }
341
342 private static Rectangle getAbsoluteBounds(IFigure figure) {
343 Rectangle bounds = figure.getBounds().getCopy();
344 figure.translateToAbsolute(bounds);
345 return bounds;
346 }
347
348 private static Rectangle getAbsoluteClientArea(IFigure figure) {
349 Rectangle clientArea = figure.getClientArea().getCopy();
350 figure.translateToParent(clientArea);
351 figure.translateToAbsolute(clientArea);
352 return clientArea;
353 }
354
355 private static Rectangle getAbsoluteViewportArea(Viewport viewport) {
356 Rectangle viewportParentBounds = viewport.getParent().getBounds()
357 .getCopy();
358
359 int widthMax = viewport.getHorizontalRangeModel().getMaximum();
360 int widthMin = viewport.getHorizontalRangeModel().getMinimum();
361 int heightMax = viewport.getVerticalRangeModel().getMaximum();
362 int heightMin = viewport.getVerticalRangeModel().getMinimum();
363
364 viewportParentBounds
365 .setSize(widthMax - widthMin, heightMax - heightMin);
366 viewportParentBounds.translate(widthMin, heightMin);
367 viewportParentBounds.translate(viewport.getViewLocation().getNegated());
368 viewport.getParent().translateToAbsolute(viewportParentBounds);
369 return viewportParentBounds;
370 }
371 } |
Those interfaces created to allow type-safe access to the host edit part's figure, namely
IScrollableFigure and
IScrollableEditPart are rather unexciting, thus I will skip them here, as well as the utility classes (EditPartSupport and ViewportUtilities) we introduced to support some of the calculations within the edit policy.
What may be of interest however is how the circuit figure of the logic example can be beautified with respect to its scroll bars (as mentioned above, the screenshots were produced after having performed some beautifications on the GEF logic example):
It is indeed done by exchanging the ScrollPane being used within the CircuitFigure of the logic example with a custom implementation we have chosen to name PuristicScrollPane. It uses special PuristicScrollBars, which do not show a 'thumb' and use non-opaque navigation buttons. Here is the code:
001 /**
002 *
003 *
004 * Copyright (C) 2005, 2008 Research Group Software Construction,
005 * RWTH Aachen University, Germany.
006 *
007 * All rights reserved. This program and the accompanying materials
008 * are made available under the terms of the Eclipse Public License
009 * version 1.0, which accompanies this distribution, and is available
010 * at http://www.eclipse.org/legal/epl-v10.html.
011 *
012 * Contributors:
013 * Research Group Software Construction - Initial API and implementation
014 *
015 *
016 */
017 package org.eclipse.draw2d;
018
019 import java.beans.PropertyChangeEvent;
020 import java.beans.PropertyChangeListener;
021
022 import org.eclipse.draw2d.geometry.Rectangle;
023
024 public class PuristicScrollPane extends ScrollPane {
025
026 /**
027 * A {@link ScrollBar} with no thumb and non-opaque buttons.
028 *
029 * @author Alexander Nyssen
030 * @author Philip Ritzkopf
031 */
032 public class PuristicScrollBar extends ScrollBar {
033
034 /**
035 * Instantiates a new transparent scroll bar.
036 *
037 * @param isHorizontal
038 * whether this scroll bar is used as a horizontal one.
039 */
040 public PuristicScrollBar(boolean isHorizontal) {
041 super();
042 setHorizontal(isHorizontal);
043 }
044
045 /**
046 * @see org.eclipse.draw2d.ScrollBar#createDefaultDownButton()
047 */
048 protected Clickable createDefaultDownButton() {
049 Clickable buttonDown = super.createDefaultDownButton();
050 buttonDown.setBorder(null);
051 buttonDown.setOpaque(false);
052 return buttonDown;
053 }
054
055 /**
056 * @see org.eclipse.draw2d.ScrollBar#createDefaultThumb()
057 */
058 protected IFigure createDefaultThumb() {
059 return null;
060 }
061
062 /**
063 * @see org.eclipse.draw2d.ScrollBar#createDefaultUpButton()
064 */
065 protected Clickable createDefaultUpButton() {
066 Clickable buttonUp = super.createDefaultUpButton();
067 buttonUp.setBorder(null);
068 buttonUp.setOpaque(false);
069 return buttonUp;
070 }
071
072 /**
073 * @see org.eclipse.draw2d.ScrollBar#createPageDown()
074 */
075 protected Clickable createPageDown() {
076 return null;
077 }
078
079 /**
080 * @see org.eclipse.draw2d.ScrollBar#createPageUp()
081 */
082 protected Clickable createPageUp() {
083 return null;
084 }
085
086 /**
087 * @see PropertyChangeListener#propertyChange(java.beans.
088 * PropertyChangeEvent )
089 */
090 public void propertyChange(PropertyChangeEvent event) {
091 if (event.getSource() instanceof RangeModel) {
092 getButtonDown().setVisible(
093 getValue() != getMaximum() - getExtent());
094 getButtonUp().setVisible(getValue() != getMinimum());
095 }
096 super.propertyChange(event);
097 }
098 }
099
100 /**
101 * @see org.eclipse.draw2d.ScrollPane#createVerticalScrollBar()
102 */
103 protected void createVerticalScrollBar() {
104 PuristicScrollBar verticalScrollBar = new PuristicScrollBar(false);
105 setVerticalScrollBar(verticalScrollBar);
106 }
107
108 /**
109 * @see org.eclipse.draw2d.ScrollPane#createHorizontalScrollBar()
110 */
111 protected void createHorizontalScrollBar() {
112 PuristicScrollBar horizontalScrollBar = new PuristicScrollBar(true);
113 setHorizontalScrollBar(horizontalScrollBar);
114 }
115
116 /**
117 * @see org.eclipse.draw2d.Figure#paintChildren(org.eclipse.draw2d.Graphics)
118 */
119 protected void paintChildren(Graphics graphics) {
120 IFigure child;
121 // don't clip scroll bar area (as there is no thumb)
122 Rectangle clip = Rectangle.SINGLETON;
123 for (int i = 0; i < getChildren().size(); i++) {
124 child = (IFigure) getChildren().get(i);
125 if (child.isVisible() && child.intersects(graphics.getClip(clip))) {
126 graphics.clipRect(getBounds());
127 child.paint(graphics);
128 graphics.restoreState();
129 }
130 }
131 }
132
133 /**
134 * @see org.eclipse.draw2d.Figure#invalidate()
135 */
136 public void invalidate() {
137 // ensure scroll bar area is marked dirty as well.
138 getUpdateManager().addDirtyRegion(this, this.getBounds());
139 super.invalidate();
140 }
141 } |
Note that in order to achieve that children are also painted below the now transparent scroll bars, the paintChildren() and invalidate() methods within the PuristicScrollPane have to be refined accordingly. To achieve that the navigation buttons are only shown in case they are needed, the PuristicScrollBar implementation reacts to changes to its underlying range model by means of the PropertyChangeListener mechanism.
If with some of the aforementioned I could raise your interest, I propose you simply try the feature out yourself. All source code related to it (including those utility classes not explicitly shown here) is included in a
patch I added to
bug #303557 (which I have created to contribute the feature to GEF) as well as another
patch (which contains a ViewportUtilities helper class that is used by the contribution) I had created to address aforementioned clipping problems as part of
bug #195527. You may thus simply download both patches and apply them to the current HEAD version of GEF (and the GEF logic example).
The patch added to bug #303557 may also serve as a good starting point in case you not only want to trial the logic example but apply the feature to your own code, because you can infer all that is required from those few changes, we have made to CircuitFigure and CircuitEditPart respectively.