1 /*******************************************************************************
2  * Copyright (c) 2000, 2018 IBM Corporation and others.
3  *
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/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *******************************************************************************/
14 package org.eclipse.swt.widgets;
15 
16 import org.eclipse.swt.*;
17 import org.eclipse.swt.events.*;
18 import org.eclipse.swt.graphics.*;
19 import org.eclipse.swt.internal.*;
20 import org.eclipse.swt.internal.gtk.*;
21 
22 /**
23  * Instances of this class support the layout of selectable
24  * expand bar items.
25  * <p>
26  * The item children that may be added to instances of this class
27  * must be of type <code>ExpandItem</code>.
28  * </p>
29  * <dl>
30  * <dt><b>Styles:</b></dt>
31  * <dd>V_SCROLL</dd>
32  * <dt><b>Events:</b></dt>
33  * <dd>Expand, Collapse</dd>
34  * </dl>
35  * <p>
36  * IMPORTANT: This class is <em>not</em> intended to be subclassed.
37  * </p>
38  *
39  * @see ExpandItem
40  * @see ExpandEvent
41  * @see ExpandListener
42  * @see ExpandAdapter
43  * @see <a href="http://www.eclipse.org/swt/snippets/#expandbar">ExpandBar snippets</a>
44  * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: ControlExample</a>
45  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
46  *
47  * @since 3.2
48  * @noextend This class is not intended to be subclassed by clients.
49  */
50 public class ExpandBar extends Composite {
51 	ExpandItem [] items;
52 	ExpandItem lastFocus;
53 	int itemCount;
54 	int spacing;
55 	int yCurrentScroll;
56 
57 /**
58  * Constructs a new instance of this class given its parent
59  * and a style value describing its behavior and appearance.
60  * <p>
61  * The style value is either one of the style constants defined in
62  * class <code>SWT</code> which is applicable to instances of this
63  * class, or must be built by <em>bitwise OR</em>'ing together
64  * (that is, using the <code>int</code> "|" operator) two or more
65  * of those <code>SWT</code> style constants. The class description
66  * lists the style constants that are applicable to the class.
67  * Style bits are also inherited from superclasses.
68  * </p>
69  *
70  * @param parent a composite control which will be the parent of the new instance (cannot be null)
71  * @param style the style of control to construct
72  *
73  * @exception IllegalArgumentException <ul>
74  *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
75  * </ul>
76  * @exception SWTException <ul>
77  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
78  *    <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
79  * </ul>
80  *
81  * @see SWT#V_SCROLL
82  * @see Widget#checkSubclass
83  * @see Widget#getStyle
84  */
ExpandBar(Composite parent, int style)85 public ExpandBar (Composite parent, int style) {
86 	super (parent, style);
87 }
88 
89 /**
90  * Adds the listener to the collection of listeners who will
91  * be notified when an item in the receiver is expanded or collapsed
92  * by sending it one of the messages defined in the <code>ExpandListener</code>
93  * interface.
94  *
95  * @param listener the listener which should be notified
96  *
97  * @exception IllegalArgumentException <ul>
98  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
99  * </ul>
100  * @exception SWTException <ul>
101  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
102  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
103  * </ul>
104  *
105  * @see ExpandListener
106  * @see #removeExpandListener
107  */
addExpandListener(ExpandListener listener)108 public void addExpandListener (ExpandListener listener) {
109 	checkWidget ();
110 	if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
111 	TypedListener typedListener = new TypedListener (listener);
112 	addListener (SWT.Expand, typedListener);
113 	addListener (SWT.Collapse, typedListener);
114 }
115 
116 @Override
checkSubclass()117 protected void checkSubclass () {
118 	if (!isValidSubclass ()) error (SWT.ERROR_INVALID_SUBCLASS);
119 }
120 
121 @Override
computeSizeInPixels(int wHint, int hHint, boolean changed)122 Point computeSizeInPixels (int wHint, int hHint, boolean changed) {
123 	if (wHint != SWT.DEFAULT && wHint < 0) wHint = 0;
124 	if (hHint != SWT.DEFAULT && hHint < 0) hHint = 0;
125 	Point size = computeNativeSize (handle, wHint, hHint, changed);
126 	if (size.x == 0 && wHint == SWT.DEFAULT) size.x = DEFAULT_WIDTH;
127 	if (size.y == 0 && hHint == SWT.DEFAULT) size.y = DEFAULT_HEIGHT;
128 	int border = gtk_container_get_border_width_or_margin (handle);
129 	size.x += 2 * border;
130 	size.y += 2 * border;
131 	return size;
132 }
133 
134 @Override
createHandle(int index)135 void createHandle (int index) {
136 	state |= HANDLE;
137 	fixedHandle = OS.g_object_new (display.gtk_fixed_get_type (), 0);
138 	if (fixedHandle == 0) error (SWT.ERROR_NO_HANDLES);
139 	gtk_widget_set_has_surface_or_window (fixedHandle, true);
140 	handle = gtk_box_new (GTK.GTK_ORIENTATION_VERTICAL, false, 0);
141 	if (handle == 0) error (SWT.ERROR_NO_HANDLES);
142 	if ((style & SWT.V_SCROLL) != 0) {
143 		scrolledHandle = GTK.gtk_scrolled_window_new (0, 0);
144 		if (scrolledHandle == 0) error (SWT.ERROR_NO_HANDLES);
145 		GTK.gtk_scrolled_window_set_policy (scrolledHandle, GTK.GTK_POLICY_NEVER, GTK.GTK_POLICY_AUTOMATIC);
146 		GTK.gtk_container_add (fixedHandle, scrolledHandle);
147 		GTK.gtk_container_add(scrolledHandle, handle);
148 		long viewport = GTK.gtk_bin_get_child (scrolledHandle);
149 		GTK.gtk_viewport_set_shadow_type (viewport, GTK.GTK_SHADOW_NONE);
150 	} else {
151 		GTK.gtk_container_add (fixedHandle, handle);
152 	}
153 	gtk_container_set_border_width (handle, 0);
154 	// In GTK 3 font description is inherited from parent widget which is not how SWT has always worked,
155 	// reset to default font to get the usual behavior
156 	setFontDescription(defaultFont().handle);
157 }
158 
createItem(ExpandItem item, int style, int index)159 void createItem (ExpandItem item, int style, int index) {
160 	if (!(0 <= index && index <= itemCount)) error (SWT.ERROR_INVALID_RANGE);
161 	if (itemCount == items.length) {
162 		ExpandItem [] newItems = new ExpandItem [itemCount + 4];
163 		System.arraycopy (items, 0, newItems, 0, items.length);
164 		items = newItems;
165 	}
166 	System.arraycopy (items, index, items, index + 1, itemCount - index);
167 	items [index] = item;
168 	itemCount++;
169 	item.width = Math.max (0, getClientAreaInPixels ().width - spacing * 2);
170 	layoutItems (index, true);
171 }
172 
173 @Override
createWidget(int index)174 void createWidget (int index) {
175 	super.createWidget (index);
176 	items = new ExpandItem [4];
177 }
178 
destroyItem(ExpandItem item)179 void destroyItem (ExpandItem item) {
180 	int index = 0;
181 	while (index < itemCount) {
182 		if (items [index] == item) break;
183 		index++;
184 	}
185 	if (index == itemCount) return;
186 	System.arraycopy (items, index + 1, items, index, --itemCount - index);
187 	items [itemCount] = null;
188 	item.redraw ();
189 	layoutItems (index, true);
190 }
191 
192 @Override
eventHandle()193 long eventHandle () {
194 	return fixedHandle;
195 }
196 
197 @Override
forceFocus(long focusHandle)198 boolean forceFocus (long focusHandle) {
199 	if (lastFocus != null && lastFocus.setFocus ()) return true;
200 	for (int i = 0; i < itemCount; i++) {
201 		ExpandItem item = items [i];
202 		if (item.setFocus ()) return true;
203 	}
204 	return super.forceFocus (focusHandle);
205 }
206 
207 @Override
hasFocus()208 boolean hasFocus () {
209 	for (int i=0; i<itemCount; i++) {
210 		ExpandItem item = items [i];
211 		if (item.hasFocus ()) return true;
212 	}
213 	return super.hasFocus();
214 }
215 
216 @Override
hookEvents()217 void hookEvents () {
218 	super.hookEvents ();
219 	if (scrolledHandle != 0) {
220 		OS.g_signal_connect_closure (scrolledHandle, OS.size_allocate, display.getClosure (SIZE_ALLOCATE), true);
221 	}
222 }
223 
getBandHeight()224 int getBandHeight () {
225 	if (font == null) return ExpandItem.CHEVRON_SIZE;
226 	GC gc = new GC (this);
227 	FontMetrics metrics = gc.getFontMetrics ();
228 	gc.dispose ();
229 	return Math.max (ExpandItem.CHEVRON_SIZE, metrics.getHeight ());
230 }
231 
232 /**
233  * Returns the item at the given, zero-relative index in the
234  * receiver. Throws an exception if the index is out of range.
235  *
236  * @param index the index of the item to return
237  * @return the item at the given index
238  *
239  * @exception IllegalArgumentException <ul>
240  *    <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li>
241  * </ul>
242  * @exception SWTException <ul>
243  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
244  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
245  * </ul>
246  */
getItem(int index)247 public ExpandItem getItem (int index) {
248 	checkWidget();
249 	if (!(0 <= index && index < itemCount)) error (SWT.ERROR_INVALID_RANGE);
250 	return items [index];
251 }
252 
253 /**
254  * Returns the number of items contained in the receiver.
255  *
256  * @return the number of items
257  *
258  * @exception SWTException <ul>
259  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
260  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
261  * </ul>
262  */
getItemCount()263 public int getItemCount () {
264 	checkWidget();
265 	return itemCount;
266 }
267 
268 /**
269  * Returns an array of <code>ExpandItem</code>s which are the items
270  * in the receiver.
271  * <p>
272  * Note: This is not the actual structure used by the receiver
273  * to maintain its list of items, so modifying the array will
274  * not affect the receiver.
275  * </p>
276  *
277  * @return the items in the receiver
278  *
279  * @exception SWTException <ul>
280  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
281  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
282  * </ul>
283  */
getItems()284 public ExpandItem [] getItems () {
285 	checkWidget ();
286 	ExpandItem [] result = new ExpandItem [itemCount];
287 	System.arraycopy (items, 0, result, 0, itemCount);
288 	return result;
289 }
290 
291 /**
292  * Returns the receiver's spacing.
293  *
294  * @return the spacing
295  *
296  * @exception SWTException <ul>
297  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
298  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
299  * </ul>
300  */
getSpacing()301 public int getSpacing () {
302 	checkWidget ();
303 	return DPIUtil.autoScaleDown(spacing);
304 }
305 
getSpacingInPixels()306 int getSpacingInPixels () {
307 	checkWidget ();
308 	return spacing;
309 }
310 
311 @Override
gtk_key_press_event(long widget, long event)312 long gtk_key_press_event (long widget, long event) {
313 	if (!hasFocus ()) return 0;
314 	long result = super.gtk_key_press_event (widget, event);
315 	if (result != 0) return result;
316 	int index = 0;
317 	while (index < itemCount) {
318 		if (items [index].hasFocus ()) break;
319 		index++;
320 	}
321 	int [] key = new int[1];
322 	GDK.gdk_event_get_keyval(event, key);
323 	boolean next = false;
324 	switch (key[0]) {
325 		case GDK.GDK_Up:
326 		case GDK.GDK_Left: next = false; break;
327 		case GDK.GDK_Down:
328 		case GDK.GDK_Right: next = true; break;
329 		default: return result;
330 	}
331 	int start = index, offset = next ? 1 : -1;
332 	while ((index = (index + offset + itemCount) % itemCount) != start) {
333 		ExpandItem item = items [index];
334 		if (item.setFocus ()) return result;
335 	}
336 	return result;
337 }
338 
339 /**
340  * Searches the receiver's list starting at the first item
341  * (index 0) until an item is found that is equal to the
342  * argument, and returns the index of that item. If no item
343  * is found, returns -1.
344  *
345  * @param item the search item
346  * @return the index of the item
347  *
348  * @exception IllegalArgumentException <ul>
349  *    <li>ERROR_NULL_ARGUMENT - if the item is null</li>
350  *    <li>ERROR_INVALID_ARGUMENT - if the item has been disposed</li>
351  * </ul>
352  * @exception SWTException <ul>
353  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
354  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
355  * </ul>
356  */
indexOf(ExpandItem item)357 public int indexOf (ExpandItem item) {
358 	checkWidget();
359 	if (item == null) error (SWT.ERROR_NULL_ARGUMENT);
360 	for (int i = 0; i < itemCount; i++) {
361 		if (items [i] == item) return i;
362 	}
363 	return -1;
364 }
365 
layoutItems(int index, boolean setScrollbar)366 void layoutItems (int index, boolean setScrollbar) {
367 	for (int i = 0; i < itemCount; i++) {
368 		ExpandItem item = items [i];
369 		if (item != null) item.resizeControl (yCurrentScroll);
370 	}
371 }
372 
373 @Override
gtk_size_allocate(long widget, long allocation)374 long gtk_size_allocate (long widget, long allocation) {
375 	long result = super.gtk_size_allocate (widget, allocation);
376 	layoutItems (0, false);
377 	return result;
378 }
379 
380 @Override
parentingHandle()381 long parentingHandle () {
382 	return fixedHandle;
383 }
384 
385 @Override
releaseChildren(boolean destroy)386 void releaseChildren (boolean destroy) {
387 	for (int i = 0; i < itemCount; i++) {
388 		ExpandItem item = items [i];
389 		if (item != null && !item.isDisposed ()) {
390 			item.release (false);
391 		}
392 	}
393 	super.releaseChildren (destroy);
394 }
395 
396 /**
397  * Removes the listener from the collection of listeners who will
398  * be notified when items in the receiver are expanded or collapsed.
399  *
400  * @param listener the listener which should no longer be notified
401  *
402  * @exception IllegalArgumentException <ul>
403  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
404  * </ul>
405  * @exception SWTException <ul>
406  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
407  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
408  * </ul>
409  *
410  * @see ExpandListener
411  * @see #addExpandListener
412  */
removeExpandListener(ExpandListener listener)413 public void removeExpandListener (ExpandListener listener) {
414 	checkWidget ();
415 	if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
416 	if (eventTable == null) return;
417 	eventTable.unhook (SWT.Expand, listener);
418 	eventTable.unhook (SWT.Collapse, listener);
419 }
420 
421 @Override
reskinChildren(int flags)422 void reskinChildren (int flags) {
423 	if (items != null) {
424 		for (int i=0; i<items.length; i++) {
425 			ExpandItem item = items [i];
426 			if (item != null ) item.reskin (flags);
427 		}
428 	}
429 	super.reskinChildren (flags);
430 }
431 
432 @Override
setWidgetBackground()433 void setWidgetBackground  () {
434 	GdkRGBA rgba = (state & BACKGROUND) != 0 ? getBackgroundGdkRGBA () : null;
435 	super.setBackgroundGdkRGBA (handle, rgba);
436 }
437 
438 @Override
setFontDescription(long font)439 void setFontDescription (long font) {
440 	super.setFontDescription (font);
441 	for (int i = 0; i < itemCount; i++) {
442 		items[i].setFontDescription (font);
443 	}
444 	layoutItems (0, true);
445 }
446 
447 @Override
setForegroundGdkRGBA(GdkRGBA rgba)448 void setForegroundGdkRGBA (GdkRGBA rgba) {
449 	super.setForegroundGdkRGBA(rgba);
450 	for (int i = 0; i < itemCount; i++) {
451 		items[i].setForegroundRGBA (rgba);
452 	}
453 }
454 
455 @Override
setOrientation(boolean create)456 void setOrientation (boolean create) {
457 	super.setOrientation (create);
458 	if (items != null) {
459 		for (int i=0; i<items.length; i++) {
460 			if (items[i] != null) items[i].setOrientation (create);
461 		}
462 	}
463 }
464 
setScrollbar()465 void setScrollbar () {
466 	if (itemCount == 0) return;
467 	if ((style & SWT.V_SCROLL) == 0) return;
468 	int height = getClientAreaInPixels ().height;
469 	ExpandItem item = items [itemCount - 1];
470 	int maxHeight = item.y + getBandHeight () + spacing;
471 	if (item.expanded) maxHeight += item.height;
472 	long adjustmentHandle = GTK.gtk_scrolled_window_get_vadjustment (scrolledHandle);
473 	GtkAdjustment adjustment = new GtkAdjustment ();
474 	gtk_adjustment_get (adjustmentHandle, adjustment);
475 	yCurrentScroll = (int)adjustment.value;
476 
477 	//claim bottom free space
478 	if (yCurrentScroll > 0 && height > maxHeight) {
479 		yCurrentScroll = Math.max (0, yCurrentScroll + maxHeight - height);
480 		layoutItems (0, false);
481 	}
482 	maxHeight += yCurrentScroll;
483 	adjustment.value = Math.min (yCurrentScroll, maxHeight);
484 	adjustment.upper = maxHeight;
485 	adjustment.page_size = height;
486 	GTK.gtk_adjustment_configure(adjustmentHandle, adjustment.value, adjustment.lower, adjustment.upper,
487 		adjustment.step_increment, adjustment.page_increment, adjustment.page_size);
488 	int policy = maxHeight > height ? GTK.GTK_POLICY_ALWAYS : GTK.GTK_POLICY_NEVER;
489 	GTK.gtk_scrolled_window_set_policy (scrolledHandle, GTK.GTK_POLICY_NEVER, policy);
490 	GtkAllocation allocation = new GtkAllocation ();
491 	GTK.gtk_widget_get_allocation (fixedHandle, allocation);
492 	int width = allocation.width - spacing * 2;
493 	if (policy == GTK.GTK_POLICY_ALWAYS) {
494 		long vHandle = 0;
495 		vHandle = GTK.gtk_scrolled_window_get_vscrollbar (scrolledHandle);
496 		GtkRequisition requisition = new GtkRequisition ();
497 		gtk_widget_get_preferred_size (vHandle, requisition);
498 		width -= requisition.width;
499 	}
500 	width = Math.max (0,  width);
501 	for (int i = 0; i < itemCount; i++) {
502 		ExpandItem item2 = items[i];
503 		item2.setBounds (0, 0, width, item2.height, false, true);
504 	}
505 }
506 
507 /**
508  * Sets the receiver's spacing. Spacing specifies the number of points allocated around
509  * each item.
510  *
511  * @param spacing the spacing around each item
512  *
513  * @exception SWTException <ul>
514  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
515  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
516  * </ul>
517  */
setSpacing(int spacing)518 public void setSpacing (int spacing) {
519 	checkWidget ();
520 	setSpacingInPixels(DPIUtil.autoScaleUp(spacing));
521 }
522 
setSpacingInPixels(int spacing)523 void setSpacingInPixels (int spacing) {
524 	checkWidget ();
525 	if (spacing < 0) return;
526 	if (spacing == this.spacing) return;
527 	this.spacing = spacing;
528 	GTK.gtk_box_set_spacing (handle, spacing);
529 	gtk_container_set_border_width (handle, spacing);
530 }
531 
showItem(ExpandItem item)532 void showItem (ExpandItem item) {
533 	Control control = item.control;
534 	if (control != null && !control.isDisposed ()) {
535 		control.setVisible (item.expanded);
536 	}
537 	item.redraw ();
538 	int index = indexOf (item);
539 	layoutItems (index + 1, true);
540 }
541 
542 @Override
updateScrollBarValue(ScrollBar bar)543 void updateScrollBarValue (ScrollBar bar) {
544 	yCurrentScroll = bar.getSelection();
545 	layoutItems (0, false);
546 }
547 }
548