1 /*******************************************************************************
2 * Copyright (c) 2000, 2016 IBM Corporation and others.
4 * This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License 2.0
6 * which accompanies this distribution, and is available at
7 * https://www.eclipse.org/legal/epl-2.0/
9 * SPDX-License-Identifier: EPL-2.0
12 * IBM Corporation - initial API and implementation
13 *******************************************************************************/
14 package org.eclipse.swt.custom;
16 import org.eclipse.swt.*;
17 import org.eclipse.swt.graphics.*;
18 import org.eclipse.swt.widgets.*;
21 * A ScrolledComposite provides scrollbars and will scroll its content when the user
22 * uses the scrollbars.
25 * <p>There are two ways to use the ScrolledComposite:
29 * <li> Set the size of the control that is being scrolled and the ScrolledComposite
30 * will show scrollbars when the contained control can not be fully seen.</li>
32 * <li>The second way imitates the way a browser would work. Set the minimum size of
33 * the control and the ScrolledComposite will show scroll bars if the visible area is
34 * less than the minimum size of the control and it will expand the size of the control
35 * if the visible area is greater than the minimum size. This requires invoking
36 * both setMinWidth(), setMinHeight() and setExpandHorizontal(), setExpandVertical().</li>
40 * public static void main (String [] args) {
41 * Display display = new Display ();
42 * Color red = display.getSystemColor(SWT.COLOR_RED);
43 * Color blue = display.getSystemColor(SWT.COLOR_BLUE);
44 * Shell shell = new Shell (display);
45 * shell.setLayout(new FillLayout());
47 * // set the size of the scrolled content - method 1
48 * final ScrolledComposite sc1 = new ScrolledComposite(shell, SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER);
49 * final Composite c1 = new Composite(sc1, SWT.NONE);
51 * c1.setBackground(red);
52 * GridLayout layout = new GridLayout();
53 * layout.numColumns = 4;
54 * c1.setLayout(layout);
55 * Button b1 = new Button (c1, SWT.PUSH);
56 * b1.setText("first button");
57 * c1.setSize(c1.computeSize(SWT.DEFAULT, SWT.DEFAULT));
59 * // set the minimum width and height of the scrolled content - method 2
60 * final ScrolledComposite sc2 = new ScrolledComposite(shell, SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER);
61 * sc2.setExpandHorizontal(true);
62 * sc2.setExpandVertical(true);
63 * final Composite c2 = new Composite(sc2, SWT.NONE);
65 * c2.setBackground(blue);
66 * layout = new GridLayout();
67 * layout.numColumns = 4;
68 * c2.setLayout(layout);
69 * Button b2 = new Button (c2, SWT.PUSH);
70 * b2.setText("first button");
71 * sc2.setMinSize(c2.computeSize(SWT.DEFAULT, SWT.DEFAULT));
73 * Button add = new Button (shell, SWT.PUSH);
74 * add.setText("add children");
75 * final int[] index = new int[]{0};
76 * add.addListener(SWT.Selection, new Listener() {
77 * public void handleEvent(Event e) {
79 * Button button = new Button(c1, SWT.PUSH);
80 * button.setText("button "+index[0]);
81 * // reset size of content so children can be seen - method 1
82 * c1.setSize(c1.computeSize(SWT.DEFAULT, SWT.DEFAULT));
85 * button = new Button(c2, SWT.PUSH);
86 * button.setText("button "+index[0]);
87 * // reset the minimum width and height so children can be seen - method 2
88 * sc2.setMinSize(c2.computeSize(SWT.DEFAULT, SWT.DEFAULT));
94 * while (!shell.isDisposed ()) {
95 * if (!display.readAndDispatch ()) display.sleep ();
102 * <dt><b>Styles:</b><dd>H_SCROLL, V_SCROLL
105 * @see <a href="http://www.eclipse.org/swt/snippets/#scrolledcomposite">ScrolledComposite snippets</a>
106 * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
108 public class ScrolledComposite extends Composite {
111 Listener contentListener;
116 boolean expandHorizontal = false;
117 boolean expandVertical = false;
118 boolean alwaysShowScroll = false;
119 boolean showFocusedControl = false;
120 boolean showNextFocusedControl = true;
123 * Constructs a new instance of this class given its parent
124 * and a style value describing its behavior and appearance.
126 * The style value is either one of the style constants defined in
127 * class <code>SWT</code> which is applicable to instances of this
128 * class, or must be built by <em>bitwise OR</em>'ing together
129 * (that is, using the <code>int</code> "|" operator) two or more
130 * of those <code>SWT</code> style constants. The class description
131 * lists the style constants that are applicable to the class.
132 * Style bits are also inherited from superclasses.
135 * @param parent a widget which will be the parent of the new instance (cannot be null)
136 * @param style the style of widget to construct
138 * @exception IllegalArgumentException <ul>
139 * <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
141 * @exception SWTException <ul>
142 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
149 public ScrolledComposite(Composite parent, int style) {
150 super(parent, checkStyle(style));
151 super.setLayout(new ScrolledCompositeLayout());
152 ScrollBar hBar = getHorizontalBar ();
154 hBar.setVisible(false);
155 hBar.addListener (SWT.Selection, e -> hScroll());
158 ScrollBar vBar = getVerticalBar ();
160 vBar.setVisible(false);
161 vBar.addListener (SWT.Selection, e -> vScroll());
164 contentListener = e -> {
165 if (e.type != SWT.Resize) return;
170 if (event.type == SWT.FocusIn) {
171 if (!showNextFocusedControl) {
172 showNextFocusedControl = true;
173 } else if (event.widget instanceof Control) {
174 Control control = (Control) event.widget;
175 if (contains(control)) showControl(control);
178 Widget w = event.widget;
179 if (w instanceof Control) {
180 showNextFocusedControl = w.getDisplay().getActiveShell() == ((Control) w).getShell();
185 addDisposeListener(e -> {
186 getDisplay().removeFilter(SWT.FocusIn, filter);
187 getDisplay().removeFilter(SWT.FocusOut, filter);
191 static int checkStyle (int style) {
192 int mask = SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER | SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT;
196 boolean contains(Control control) {
197 if (control == null || control.isDisposed()) return false;
199 Composite parent = control.getParent();
200 while (parent != null && !(parent instanceof Shell)) {
201 if (this == parent) return true;
202 parent = parent.getParent();
208 * Returns the Always Show Scrollbars flag. True if the scrollbars are
209 * always shown even if they are not required. False if the scrollbars are only
210 * visible when some part of the composite needs to be scrolled to be seen.
211 * The H_SCROLL and V_SCROLL style bits are also required to enable scrollbars in the
212 * horizontal and vertical directions.
214 * @return the Always Show Scrollbars flag value
216 public boolean getAlwaysShowScrollBars() {
218 * This call is intentionally commented out, to allow this getter method to be
219 * called from a thread which is different from one that created the widget.
222 return alwaysShowScroll;
226 * Returns <code>true</code> if the content control
227 * will be expanded to fill available horizontal space.
229 * @return the receiver's horizontal expansion state
231 * @exception SWTException <ul>
232 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
233 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
238 public boolean getExpandHorizontal() {
240 return expandHorizontal;
244 * Returns <code>true</code> if the content control
245 * will be expanded to fill available vertical space.
247 * @return the receiver's vertical expansion state
249 * @exception SWTException <ul>
250 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
251 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
256 public boolean getExpandVertical() {
258 return expandVertical;
262 * Returns the minimum width of the content control.
264 * @return the minimum width
266 * @exception SWTException <ul>
267 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
268 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
273 public int getMinWidth() {
279 * Returns the minimum height of the content control.
281 * @return the minimum height
283 * @exception SWTException <ul>
284 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
285 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
290 public int getMinHeight() {
296 * Get the content that is being scrolled.
298 * @return the control displayed in the content area
300 public Control getContent() {
302 * This call is intentionally commented out, to allow this getter method to be
303 * called from a thread which is different from one that created the widget.
310 * Returns <code>true</code> if the receiver automatically scrolls to a focused child control
311 * to make it visible. Otherwise, returns <code>false</code>.
313 * @return a boolean indicating whether focused child controls are automatically scrolled into the viewport
315 * @exception SWTException <ul>
316 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
317 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
322 public boolean getShowFocusedControl() {
324 return showFocusedControl;
328 if (content == null) return;
329 Point location = content.getLocation ();
330 ScrollBar hBar = getHorizontalBar ();
331 int hSelection = hBar.getSelection ();
332 content.setLocation (-hSelection, location.y);
334 boolean needHScroll(Rectangle contentRect, boolean vVisible) {
335 ScrollBar hBar = getHorizontalBar();
336 if (hBar == null) return false;
338 Rectangle hostRect = getBounds();
339 int border = getBorderWidth();
340 hostRect.width -= 2*border;
341 ScrollBar vBar = getVerticalBar();
342 if (vVisible && vBar != null) hostRect.width -= vBar.getSize().x;
344 if (!expandHorizontal && contentRect.width > hostRect.width) return true;
345 if (expandHorizontal && minWidth > hostRect.width) return true;
349 boolean needVScroll(Rectangle contentRect, boolean hVisible) {
350 ScrollBar vBar = getVerticalBar();
351 if (vBar == null) return false;
353 Rectangle hostRect = getBounds();
354 int border = getBorderWidth();
355 hostRect.height -= 2*border;
356 ScrollBar hBar = getHorizontalBar();
357 if (hVisible && hBar != null) hostRect.height -= hBar.getSize().y;
359 if (!expandVertical && contentRect.height > hostRect.height) return true;
360 if (expandVertical && minHeight > hostRect.height) return true;
365 * Return the point in the content that currently appears in the top left
366 * corner of the scrolled composite.
368 * @return the point in the content that currently appears in the top left
369 * corner of the scrolled composite. If no content has been set, this returns
372 * @exception SWTException <ul>
373 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
374 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
379 public Point getOrigin() {
381 if (content == null) return new Point(0, 0);
382 Point location = content.getLocation();
383 return new Point(-location.x, -location.y);
386 * Scrolls the content so that the specified point in the content is in the top
387 * left corner. If no content has been set, nothing will occur.
389 * Negative values will be ignored. Values greater than the maximum scroll
390 * distance will result in scrolling to the end of the scrollbar.
392 * @param origin the point on the content to appear in the top left corner
394 * @exception SWTException <ul>
395 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
396 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
397 * <li>ERROR_INVALID_ARGUMENT - value of origin is outside of content
401 public void setOrigin(Point origin) {
402 setOrigin(origin.x, origin.y);
405 * Scrolls the content so that the specified point in the content is in the top
406 * left corner. If no content has been set, nothing will occur.
408 * Negative values will be ignored. Values greater than the maximum scroll
409 * distance will result in scrolling to the end of the scrollbar.
411 * @param x the x coordinate of the content to appear in the top left corner
413 * @param y the y coordinate of the content to appear in the top left corner
415 * @exception SWTException <ul>
416 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
417 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
422 public void setOrigin(int x, int y) {
424 if (content == null) return;
425 ScrollBar hBar = getHorizontalBar ();
427 hBar.setSelection(x);
428 x = -hBar.getSelection ();
432 ScrollBar vBar = getVerticalBar ();
434 vBar.setSelection(y);
435 y = -vBar.getSelection ();
439 content.setLocation(x, y);
442 * Set the Always Show Scrollbars flag. True if the scrollbars are
443 * always shown even if they are not required. False if the scrollbars are only
444 * visible when some part of the composite needs to be scrolled to be seen.
445 * The H_SCROLL and V_SCROLL style bits are also required to enable scrollbars in the
446 * horizontal and vertical directions.
448 * @param show true to show the scrollbars even when not required, false to show scrollbars only when required
450 * @exception SWTException <ul>
451 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
452 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
455 public void setAlwaysShowScrollBars(boolean show) {
457 if (show == alwaysShowScroll) return;
458 alwaysShowScroll = show;
459 ScrollBar hBar = getHorizontalBar ();
460 if (hBar != null && alwaysShowScroll) hBar.setVisible(true);
461 ScrollBar vBar = getVerticalBar ();
462 if (vBar != null && alwaysShowScroll) vBar.setVisible(true);
467 * Set the content that will be scrolled.
469 * @param content the control to be displayed in the content area
471 * @exception SWTException <ul>
472 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
473 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
476 public void setContent(Control content) {
478 if (this.content != null && !this.content.isDisposed()) {
479 this.content.removeListener(SWT.Resize, contentListener);
480 this.content.setBounds(new Rectangle(-200, -200, 0, 0));
483 this.content = content;
484 ScrollBar vBar = getVerticalBar ();
485 ScrollBar hBar = getHorizontalBar ();
486 if (this.content != null) {
490 vBar.setSelection(0);
495 hBar.setSelection(0);
497 content.setLocation(0, 0);
499 this.content.addListener(SWT.Resize, contentListener);
501 if (hBar != null) hBar.setVisible(alwaysShowScroll);
502 if (vBar != null) vBar.setVisible(alwaysShowScroll);
506 * Configure the ScrolledComposite to resize the content object to be as wide as the
507 * ScrolledComposite when the width of the ScrolledComposite is greater than the
508 * minimum width specified in setMinWidth. If the ScrolledComposite is less than the
509 * minimum width, the content will not be resized and instead the horizontal scroll bar will be
510 * used to view the entire width.
511 * If expand is false, this behaviour is turned off. By default, this behaviour is turned off.
513 * @param expand true to expand the content control to fill available horizontal space
515 * @exception SWTException <ul>
516 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
517 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
520 public void setExpandHorizontal(boolean expand) {
522 if (expand == expandHorizontal) return;
523 expandHorizontal = expand;
527 * Configure the ScrolledComposite to resize the content object to be as tall as the
528 * ScrolledComposite when the height of the ScrolledComposite is greater than the
529 * minimum height specified in setMinHeight. If the ScrolledComposite is less than the
530 * minimum height, the content will not be resized and instead the vertical scroll bar will be
531 * used to view the entire height.
532 * If expand is false, this behaviour is turned off. By default, this behaviour is turned off.
534 * @param expand true to expand the content control to fill available vertical space
536 * @exception SWTException <ul>
537 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
538 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
541 public void setExpandVertical(boolean expand) {
543 if (expand == expandVertical) return;
544 expandVertical = expand;
548 * Sets the layout which is associated with the receiver to be
549 * the argument which may be null.
551 * Note: No Layout can be set on this Control because it already
552 * manages the size and position of its children.
555 * @param layout the receiver's new layout or null
557 * @exception SWTException <ul>
558 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
559 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
563 public void setLayout (Layout layout) {
568 * Specify the minimum height at which the ScrolledComposite will begin scrolling the
569 * content with the vertical scroll bar. This value is only relevant if
570 * setExpandVertical(true) has been set.
572 * @param height the minimum height or 0 for default height
574 * @exception SWTException <ul>
575 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
576 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
579 public void setMinHeight(int height) {
580 setMinSize(minWidth, height);
583 * Specify the minimum width and height at which the ScrolledComposite will begin scrolling the
584 * content with the horizontal scroll bar. This value is only relevant if
585 * setExpandHorizontal(true) and setExpandVertical(true) have been set.
587 * @param size the minimum size or null for the default size
589 * @exception SWTException <ul>
590 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
591 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
594 public void setMinSize(Point size) {
598 setMinSize(size.x, size.y);
602 * Specify the minimum width and height at which the ScrolledComposite will begin scrolling the
603 * content with the horizontal scroll bar. This value is only relevant if
604 * setExpandHorizontal(true) and setExpandVertical(true) have been set.
606 * @param width the minimum width or 0 for default width
607 * @param height the minimum height or 0 for default height
609 * @exception SWTException <ul>
610 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
611 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
614 public void setMinSize(int width, int height) {
616 if (width == minWidth && height == minHeight) return;
617 minWidth = Math.max(0, width);
618 minHeight = Math.max(0, height);
622 * Specify the minimum width at which the ScrolledComposite will begin scrolling the
623 * content with the horizontal scroll bar. This value is only relevant if
624 * setExpandHorizontal(true) has been set.
626 * @param width the minimum width or 0 for default width
628 * @exception SWTException <ul>
629 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
630 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
633 public void setMinWidth(int width) {
634 setMinSize(width, minHeight);
638 * Configure the receiver to automatically scroll to a focused child control
639 * to make it visible.
641 * If show is <code>false</code>, show a focused control is off.
642 * By default, show a focused control is off.
644 * @param show <code>true</code> to show a focused control.
646 * @exception SWTException <ul>
647 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
648 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
653 public void setShowFocusedControl(boolean show) {
655 if (showFocusedControl == show) return;
656 Display display = getDisplay();
657 display.removeFilter(SWT.FocusIn, filter);
658 display.removeFilter(SWT.FocusOut, filter);
659 showFocusedControl = show;
660 if (!showFocusedControl) return;
661 display.addFilter(SWT.FocusIn, filter);
662 display.addFilter(SWT.FocusOut, filter);
663 Control control = display.getFocusControl();
664 if (contains(control)) showControl(control);
668 * Scrolls the content of the receiver so that the control is visible.
670 * @param control the control to be shown
672 * @exception IllegalArgumentException <ul>
673 * <li>ERROR_NULL_ARGUMENT - if the control is null</li>
674 * <li>ERROR_INVALID_ARGUMENT - if the control has been disposed</li>
676 * @exception SWTException <ul>
677 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
678 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
683 public void showControl(Control control) {
685 if (control == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
686 if (control.isDisposed ()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
687 if (!contains(control)) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
689 Rectangle itemRect = getDisplay().map(control.getParent(), this, control.getBounds());
690 Rectangle area = getClientArea();
691 Point origin = getOrigin();
692 if (itemRect.x < 0) {
693 origin.x = Math.max(0, origin.x + itemRect.x);
695 if (area.width < itemRect.x + itemRect.width) origin.x = Math.max(0, origin.x + itemRect.x + Math.min(itemRect.width, area.width) - area.width);
697 if (itemRect.y < 0) {
698 origin.y = Math.max(0, origin.y + itemRect.y);
700 if (area.height < itemRect.y + itemRect.height) origin.y = Math.max(0, origin.y + itemRect.y + Math.min(itemRect.height, area.height) - area.height);
706 if (content == null) return;
707 Point location = content.getLocation ();
708 ScrollBar vBar = getVerticalBar ();
709 int vSelection = vBar.getSelection ();
710 content.setLocation (location.x, -vSelection);