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 
17 import org.eclipse.swt.*;
18 import org.eclipse.swt.events.*;
19 import org.eclipse.swt.graphics.*;
20 import org.eclipse.swt.internal.*;
21 import org.eclipse.swt.internal.gtk.*;
22 import org.eclipse.swt.internal.gtk3.*;
23 import org.eclipse.swt.internal.gtk4.*;
24 
25 /**
26  * Instances of this class implement the notebook user interface
27  * metaphor.  It allows the user to select a notebook page from
28  * set of pages.
29  * <p>
30  * The item children that may be added to instances of this class
31  * must be of type <code>TabItem</code>.
32  * <code>Control</code> children are created and then set into a
33  * tab item using <code>TabItem#setControl</code>.
34  * </p><p>
35  * Note that although this class is a subclass of <code>Composite</code>,
36  * it does not make sense to set a layout on it.
37  * </p>
38  * <dl>
39  * <dt><b>Styles:</b></dt>
40  * <dd>TOP, BOTTOM</dd>
41  * <dt><b>Events:</b></dt>
42  * <dd>Selection</dd>
43  * </dl>
44  * <p>
45  * Note: Only one of the styles TOP and BOTTOM may be specified.
46  * </p><p>
47  * IMPORTANT: This class is <em>not</em> intended to be subclassed.
48  * </p>
49  *
50  * @see <a href="http://www.eclipse.org/swt/snippets/#tabfolder">TabFolder, TabItem snippets</a>
51  * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: ControlExample</a>
52  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
53  * @noextend This class is not intended to be subclassed by clients.
54  */
55 public class TabFolder extends Composite {
56 	/*
57 	 * Implementation note (see bug 454936, bug 480794):
58 	 *
59 	 * Architecture Change on GTK3:
60 	 * In TabItem#setControl(Control), we reparent the child to be a child of the 'tab'
61 	 * rather than tabfolder's parent swtFixed container.
62 	 * Note, this reparenting is only on the GTK side, not on the SWT side.
63 	 *
64 	 * GTK2 and GTK3 child nesting behaviour differs now.
65 	 * GTK2:
66 	 *   swtFixed
67 	 *   |-- GtkNoteBook
68 	 *   |   |-- tabLabel1
69 	 *   |   |-- tabLabel2
70 	 *   |-- swtFixed (child1)  //child is sibling of Notebook
71 	 *   |-- swtFixed (child2)
72 	 *
73 	 * GTK3+:
74 	 * 	swtFixed
75 	 * 	|--	GtkNoteBook
76 	 * 		|-- tabLabel1
77 	 * 		|-- tabLabel2
78 	 * 		|-- pageHandle (tabItem1)
79 	 * 			|-- child1 //child now child of Notebook.pageHandle.
80 	 * 		|-- pageHandle (tabItem2)
81 	 * 			|-- child1
82 	 *
83 	 * This changes the hierarchy so that children are beneath gtkNotebook (as oppose to
84 	 * being siblings) and thus fixes DND and background color issues.
85 	 * In gtk2, reparenting doesn't function properly (tab content appear blank),
86 	 * so this is a gtk3-specific behavior.
87 	 *
88 	 * Note about the reason for reparenting:
89 	 * Reparenting (as opposed to adding widget to a tab in the first place) is necessary
90 	 * because the SWT API allows situation where you create a child control before you create a TabItem.
91 	 */
92 
93 	TabItem [] items;
94 	ImageList imageList;
95 
96 /**
97  * Constructs a new instance of this class given its parent
98  * and a style value describing its behavior and appearance.
99  * <p>
100  * The style value is either one of the style constants defined in
101  * class <code>SWT</code> which is applicable to instances of this
102  * class, or must be built by <em>bitwise OR</em>'ing together
103  * (that is, using the <code>int</code> "|" operator) two or more
104  * of those <code>SWT</code> style constants. The class description
105  * lists the style constants that are applicable to the class.
106  * Style bits are also inherited from superclasses.
107  * </p>
108  *
109  * @param parent a composite control which will be the parent of the new instance (cannot be null)
110  * @param style the style of control to construct
111  *
112  * @exception IllegalArgumentException <ul>
113  *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
114  * </ul>
115  * @exception SWTException <ul>
116  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
117  *    <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
118  * </ul>
119  *
120  * @see SWT
121  * @see SWT#TOP
122  * @see SWT#BOTTOM
123  * @see Widget#checkSubclass
124  * @see Widget#getStyle
125  */
TabFolder(Composite parent, int style)126 public TabFolder (Composite parent, int style) {
127 	super (parent, checkStyle (style));
128 }
129 
checkStyle(int style)130 static int checkStyle (int style) {
131 	style = checkBits (style, SWT.TOP, SWT.BOTTOM, 0, 0, 0, 0);
132 	/*
133 	* Even though it is legal to create this widget
134 	* with scroll bars, they serve no useful purpose
135 	* because they do not automatically scroll the
136 	* widget's client area.  The fix is to clear
137 	* the SWT style.
138 	*/
139 	return style & ~(SWT.H_SCROLL | SWT.V_SCROLL);
140 }
141 
142 @Override
checkSubclass()143 protected void checkSubclass () {
144 	if (!isValidSubclass ()) error (SWT.ERROR_INVALID_SUBCLASS);
145 }
146 
147 /**
148  * Adds the listener to the collection of listeners who will
149  * be notified when the user changes the receiver's selection, by sending
150  * it one of the messages defined in the <code>SelectionListener</code>
151  * interface.
152  * <p>
153  * When <code>widgetSelected</code> is called, the item field of the event object is valid.
154  * <code>widgetDefaultSelected</code> is not called.
155  * </p>
156  *
157  * @param listener the listener which should be notified when the user changes the receiver's selection
158  *
159  * @exception IllegalArgumentException <ul>
160  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
161  * </ul>
162  * @exception SWTException <ul>
163  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
164  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
165  * </ul>
166  *
167  * @see SelectionListener
168  * @see #removeSelectionListener
169  * @see SelectionEvent
170  */
addSelectionListener(SelectionListener listener)171 public void addSelectionListener(SelectionListener listener) {
172 	checkWidget ();
173 	if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
174 	TypedListener typedListener = new TypedListener(listener);
175 	addListener(SWT.Selection,typedListener);
176 	addListener(SWT.DefaultSelection,typedListener);
177 }
178 
179 @Override
clientHandle()180 long clientHandle () {
181 	int index = GTK.gtk_notebook_get_current_page (handle);
182 	if (index != -1 && items [index] != null) {
183 		return items [index].pageHandle;
184 	}
185 	return handle;
186 }
187 
188 @Override
computeSizeInPixels(int wHint, int hHint, boolean changed)189 Point computeSizeInPixels (int wHint, int hHint, boolean changed) {
190 	checkWidget ();
191 	Point size = super.computeSizeInPixels (wHint, hHint, changed);
192 	if (wHint != SWT.DEFAULT && wHint < 0) wHint = 0;
193 	if (hHint != SWT.DEFAULT && hHint < 0) hHint = 0;
194 	boolean scrollable = GTK.gtk_notebook_get_scrollable (handle);
195 	GTK.gtk_notebook_set_scrollable (handle, false);
196 	Point notebookSize = computeNativeSize (handle, wHint, hHint, changed);
197 	GTK.gtk_notebook_set_scrollable (handle, scrollable);
198 	int[] initialGap = new int[1];
199 	notebookSize.x += initialGap[0]*2;
200 	size.x = Math.max (notebookSize.x, size.x);
201 	size.y = Math.max (notebookSize.y, size.y);
202 	return size;
203 }
204 
205 @Override
computeTrimInPixels(int x, int y, int width, int height)206 Rectangle computeTrimInPixels (int x, int y, int width, int height) {
207 	checkWidget();
208 	forceResize ();
209 	long clientHandle = clientHandle ();
210 	GtkAllocation allocation = new GtkAllocation ();
211 	GTK.gtk_widget_get_allocation (clientHandle, allocation);
212 	int clientX = allocation.x;
213 	int clientY = allocation.y;
214 	x -= clientX;
215 	y -= clientY;
216 	width +=  clientX + clientX;
217 	if ((style & SWT.BOTTOM) != 0) {
218 		int clientHeight = allocation.height;
219 		GTK.gtk_widget_get_allocation (handle, allocation);
220 		int parentHeight = allocation.height;
221 		height += parentHeight - clientHeight;
222 	} else {
223 		height +=  clientX + clientY;
224 	}
225 	return new Rectangle (x, y, width, height);
226 }
227 
228 @Override
getClientAreaInPixels()229 Rectangle getClientAreaInPixels () {
230 	Rectangle clientRectangle = super.getClientAreaInPixels ();
231 
232 	/*
233 	* Bug 454936 (see also other 454936 references)
234 	* SWT's calls to gtk_widget_size_allocate and gtk_widget_set_allocation
235 	* causes GTK+ to move the clientHandle's SwtFixed down by the size of the labels.
236 	* These calls can come up from 'shell' and TabFolder has no control over these calls.
237 	*
238 	* This is an undesired side-effect. Client handle's x & y positions should never
239 	* be incremented as this is an internal sub-container.
240 	*
241 	* Note: 0 by 0 was chosen as 1 by 1 shifts controls beyond their original pos.
242 	* The long term fix would be to not use widget_*_allocation from higher containers,
243 	* but this would require removal of swtFixed.
244 	*
245 	* This is Gtk3-specific for Tabfolder as the architecture is changed in gtk3 only.
246 	*/
247 	clientRectangle.x = 0;
248 	clientRectangle.y = 0;
249 	return clientRectangle;
250 }
251 
252 
253 @Override
createHandle(int index)254 void createHandle (int index) {
255 	state |= HANDLE;
256 	fixedHandle = OS.g_object_new (display.gtk_fixed_get_type (), 0);
257 	if (fixedHandle == 0) error (SWT.ERROR_NO_HANDLES);
258 	handle = GTK.gtk_notebook_new ();
259 	if (handle == 0) error (SWT.ERROR_NO_HANDLES);
260 
261 	if (GTK.GTK4) {
262 		OS.swt_fixed_add(fixedHandle, handle);
263 	} else {
264 		GTK3.gtk_widget_set_has_window(fixedHandle, true);
265 		GTK3.gtk_container_add (fixedHandle, handle);
266 	}
267 
268 	GTK.gtk_notebook_set_show_tabs (handle, true);
269 	GTK.gtk_notebook_set_scrollable (handle, true);
270 	if ((style & SWT.BOTTOM) != 0) {
271 		GTK.gtk_notebook_set_tab_pos (handle, GTK.GTK_POS_BOTTOM);
272 	}
273 }
274 
275 @Override
createWidget(int index)276 void createWidget (int index) {
277 	super.createWidget(index);
278 	items = new TabItem [4];
279 }
280 
createItem(TabItem item, int index)281 void createItem (TabItem item, int index) {
282 	int itemCount = 0;
283 	if (GTK.GTK4) {
284 		itemCount = GTK.gtk_notebook_get_n_pages(handle);
285 	} else {
286 		long list = GTK3.gtk_container_get_children (handle);
287 		if (list != 0) {
288 			itemCount = OS.g_list_length (list);
289 			OS.g_list_free (list);
290 		}
291 	}
292 
293 	if (!(0 <= index && index <= itemCount)) error (SWT.ERROR_INVALID_RANGE);
294 	if (itemCount == items.length) {
295 		TabItem [] newItems = new TabItem [items.length + 4];
296 		System.arraycopy (items, 0, newItems, 0, items.length);
297 		items = newItems;
298 	}
299 	long boxHandle = gtk_box_new (GTK.GTK_ORIENTATION_HORIZONTAL, false, 0);
300 	if (boxHandle == 0) error (SWT.ERROR_NO_HANDLES);
301 	long labelHandle = GTK.gtk_label_new_with_mnemonic (null);
302 	if (labelHandle == 0) error (SWT.ERROR_NO_HANDLES);
303 	long imageHandle = GTK.gtk_image_new ();
304 	if (imageHandle == 0) error (SWT.ERROR_NO_HANDLES);
305 
306 	if (GTK.GTK4) {
307 		GTK4.gtk_box_append(boxHandle, imageHandle);
308 		GTK4.gtk_box_append(boxHandle, labelHandle);
309 	} else {
310 		GTK3.gtk_container_add(boxHandle, imageHandle);
311 		GTK3.gtk_container_add(boxHandle, labelHandle);
312 	}
313 
314 	long pageHandle = OS.g_object_new (display.gtk_fixed_get_type (), 0);
315 	if (pageHandle == 0) error (SWT.ERROR_NO_HANDLES);
316 	OS.g_signal_handlers_block_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, SWITCH_PAGE);
317 	GTK.gtk_notebook_insert_page (handle, pageHandle, boxHandle, index);
318 	OS.g_signal_handlers_unblock_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, SWITCH_PAGE);
319 
320 	if (GTK.GTK4) {
321 		GTK.gtk_widget_hide(imageHandle);
322 	} else {
323 		GTK.gtk_widget_show(boxHandle);
324 		GTK.gtk_widget_show(labelHandle);
325 		GTK.gtk_widget_show(pageHandle);
326 	}
327 
328 	item.state |= HANDLE;
329 	item.handle = boxHandle;
330 	item.labelHandle = labelHandle;
331 	item.imageHandle = imageHandle;
332 	item.pageHandle = pageHandle;
333 	System.arraycopy (items, index, items, index + 1, itemCount++ - index);
334 	items [index] = item;
335 	if ((state & FOREGROUND) != 0) {
336 		item.setForegroundGdkRGBA (item.handle, getForegroundGdkRGBA());
337 	}
338 	if ((state & FONT) != 0) {
339 		long fontDesc = getFontDescription ();
340 		item.setFontDescription (fontDesc);
341 		OS.pango_font_description_free (fontDesc);
342 	}
343 	if (itemCount == 1) {
344 		OS.g_signal_handlers_block_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, SWITCH_PAGE);
345 		GTK.gtk_notebook_set_current_page (handle, 0);
346 		OS.g_signal_handlers_unblock_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, SWITCH_PAGE);
347 		Event event = new Event();
348 		event.item = items[0];
349 		sendSelectionEvent (SWT.Selection, event, false);
350 		// the widget could be destroyed at this point
351 	}
352 }
353 
destroyItem(TabItem item)354 void destroyItem (TabItem item) {
355 	int index = 0;
356 	int itemCount = getItemCount();
357 	while (index < itemCount) {
358 		if (items [index] == item) break;
359 		index++;
360 	}
361 	if (index == itemCount) error (SWT.ERROR_ITEM_NOT_REMOVED);
362 	int oldIndex = GTK.gtk_notebook_get_current_page (handle);
363 	OS.g_signal_handlers_block_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, SWITCH_PAGE);
364 	GTK.gtk_notebook_remove_page (handle, index);
365 	OS.g_signal_handlers_unblock_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, SWITCH_PAGE);
366 	System.arraycopy (items, index + 1, items, index, --itemCount - index);
367 	items [itemCount] = null;
368 	if (index == oldIndex) {
369 		int newIndex = GTK.gtk_notebook_get_current_page (handle);
370 		if (newIndex != -1) {
371 			Control control = items [newIndex].getControl ();
372 			if (control != null && !control.isDisposed ()) {
373 				control.setBoundsInPixels (getClientAreaInPixels());
374 				control.setVisible (true);
375 			}
376 			Event event = new Event ();
377 			event.item = items [newIndex];
378 			sendSelectionEvent (SWT.Selection, event, true);
379 			// the widget could be destroyed at this point
380 		}
381 	}
382 }
383 
384 @Override
eventHandle()385 long eventHandle () {
386 	return handle;
387 }
388 
389 @Override
_getChildren()390 Control[] _getChildren() {
391 	Control[] directChildren = super._getChildren();
392 	int directCount = directChildren.length;
393 	int itemCount = items == null ? 0 : items.length;
394 	Control[] children = new Control[itemCount + directCount];
395 
396 	int childrenCount = 0;
397 	for (int itemIndex = 0; itemIndex < itemCount; itemIndex++) {
398 		TabItem tabItem = items[itemIndex];
399 		if (tabItem != null && !tabItem.isDisposed()) {
400 			long parentHandle = tabItem.pageHandle;
401 
402 			if (GTK.GTK4) {
403 				for (long child = GTK4.gtk_widget_get_first_child(parentHandle); child != 0; child = GTK4.gtk_widget_get_next_sibling(child)) {
404 					Widget childWidget = display.getWidget(child);
405 					if (childWidget != null && childWidget instanceof Control && childWidget != this) {
406 						children[childrenCount] = (Control)childWidget;
407 						childrenCount++;
408 					}
409 				}
410 			} else {
411 				long list = GTK3.gtk_container_get_children (parentHandle);
412 				if (list != 0) {
413 					long handle = OS.g_list_data (list);
414 					if (handle != 0) {
415 						Widget widget = display.getWidget (handle);
416 						if (widget != null && widget != this) {
417 							if (widget instanceof Control) {
418 								children [childrenCount++] = (Control) widget;
419 							}
420 						}
421 					}
422 					OS.g_list_free (list);
423 				}
424 			}
425 		}
426 	}
427 
428 	if (childrenCount == itemCount + directCount) {
429 		return children;
430 	} else {
431 		Control[] newChildren;
432 		if (childrenCount == itemCount) {
433 			newChildren = children;
434 		} else {
435 			newChildren = new Control [childrenCount + directCount];
436 			System.arraycopy (children, 0, newChildren, 0, childrenCount);
437 		}
438 		System.arraycopy (directChildren, 0, newChildren, childrenCount, directCount);
439 		return newChildren;
440 	}
441 }
442 
443 /**
444  * Returns the item at the given, zero-relative index in the
445  * receiver. Throws an exception if the index is out of range.
446  *
447  * @param index the index of the item to return
448  * @return the item at the given index
449  *
450  * @exception IllegalArgumentException <ul>
451  *    <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li>
452  * </ul>
453  * @exception SWTException <ul>
454  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
455  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
456  * </ul>
457  */
getItem(int index)458 public TabItem getItem (int index) {
459 	checkWidget();
460 	if (!(0 <= index && index < getItemCount())) error (SWT.ERROR_INVALID_RANGE);
461 
462 	if (GTK.GTK4) {
463 		long child = GTK4.gtk_widget_get_first_child(handle);
464 		if (child == 0) error(SWT.ERROR_CANNOT_GET_ITEM);
465 	} else {
466 		long list = GTK3.gtk_container_get_children (handle);
467 		if (list == 0) error (SWT.ERROR_CANNOT_GET_ITEM);
468 		int itemCount = OS.g_list_length (list);
469 		OS.g_list_free (list);
470 		if (!(0 <= index && index < itemCount)) error (SWT.ERROR_CANNOT_GET_ITEM);
471 	}
472 
473 	return items [index];
474 }
475 
476 /**
477  * Returns the tab item at the given point in the receiver
478  * or null if no such item exists. The point is in the
479  * coordinate system of the receiver.
480  *
481  * @param point the point used to locate the item
482  * @return the tab item at the given point, or null if the point is not in a tab item
483  *
484  * @exception IllegalArgumentException <ul>
485  *    <li>ERROR_NULL_ARGUMENT - if the point is null</li>
486  * </ul>
487  * @exception SWTException <ul>
488  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
489  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
490  * </ul>
491  *
492  * @since 3.4
493  */
getItem(Point point)494 public TabItem getItem(Point point) {
495 	checkWidget();
496 	if (point == null) error (SWT.ERROR_NULL_ARGUMENT);
497 	int itemCount = getItemCount();
498 	for (int i = 0; i < itemCount; i++) {
499 		TabItem item = items[i];
500 		Rectangle rect = item.getBounds();
501 		if (rect.contains(point)) return item;
502 	}
503 	return null;
504 }
505 
506 /**
507  * Returns the number of items contained in the receiver.
508  *
509  * @return the number of items
510  *
511  * @exception SWTException <ul>
512  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
513  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
514  * </ul>
515  */
getItemCount()516 public int getItemCount () {
517 	checkWidget();
518 
519 	int itemCount = 0;
520 	if (GTK.GTK4) {
521 		itemCount = GTK.gtk_notebook_get_n_pages(handle);
522 	} else {
523 		long list = GTK3.gtk_container_get_children (handle);
524 		if (list == 0) return 0;
525 		itemCount = OS.g_list_length (list);
526 		OS.g_list_free (list);
527 	}
528 
529 	return itemCount;
530 }
531 
532 /**
533  * Returns an array of <code>TabItem</code>s which are the items
534  * in the receiver.
535  * <p>
536  * Note: This is not the actual structure used by the receiver
537  * to maintain its list of items, so modifying the array will
538  * not affect the receiver.
539  * </p>
540  *
541  * @return the items in the receiver
542  *
543  * @exception SWTException <ul>
544  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
545  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
546  * </ul>
547  */
getItems()548 public TabItem [] getItems () {
549 	checkWidget();
550 	int count = getItemCount ();
551 	TabItem [] result = new TabItem [count];
552 	System.arraycopy (items, 0, result, 0, count);
553 	return result;
554 }
555 
556 /**
557  * Returns an array of <code>TabItem</code>s that are currently
558  * selected in the receiver. An empty array indicates that no
559  * items are selected.
560  * <p>
561  * Note: This is not the actual structure used by the receiver
562  * to maintain its selection, so modifying the array will
563  * not affect the receiver.
564  * </p>
565  * @return an array representing the selection
566  *
567  * @exception SWTException <ul>
568  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
569  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
570  * </ul>
571  */
getSelection()572 public TabItem [] getSelection () {
573 	checkWidget();
574 	int index = GTK.gtk_notebook_get_current_page (handle);
575 	if (index == -1) return new TabItem [0];
576 	return new TabItem [] {items [index]};
577 }
578 
579 /**
580  * Returns the zero-relative index of the item which is currently
581  * selected in the receiver, or -1 if no item is selected.
582  *
583  * @return the index of the selected item
584  *
585  * @exception SWTException <ul>
586  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
587  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
588  * </ul>
589  */
getSelectionIndex()590 public int getSelectionIndex () {
591 	checkWidget();
592 	return GTK.gtk_notebook_get_current_page (handle);
593 }
594 
595 @Override
gtk_focus(long widget, long directionType)596 long gtk_focus (long widget, long directionType) {
597 	return 0;
598 }
599 
600 @Override
gtk_switch_page(long notebook, long page, int page_num)601 long gtk_switch_page(long notebook, long page, int page_num) {
602 	TabItem item = items[page_num];
603 
604 	if (GTK.GTK4) {
605 		Control control = item.getControl();
606 		control.setBoundsInPixels(getClientAreaInPixels());
607 	} else {
608 		int index = GTK.gtk_notebook_get_current_page(handle);
609 		if (index != -1) {
610 			Control control = items [index].getControl();
611 			if (control != null && !control.isDisposed()) {
612 				control.setVisible(false);
613 			}
614 		} else {
615 			return 0;
616 		}
617 
618 		Control control = item.getControl();
619 		if (control != null && !control.isDisposed()) {
620 			control.setBoundsInPixels(getClientAreaInPixels());
621 			control.setVisible(true);
622 		}
623 	}
624 
625 	Event event = new Event();
626 	event.item = item;
627 	sendSelectionEvent(SWT.Selection, event, false);
628 
629 	return 0;
630 }
631 
632 @Override
hookEvents()633 void hookEvents () {
634 	super.hookEvents ();
635 	OS.g_signal_connect_closure (handle, OS.switch_page, display.getClosure (SWITCH_PAGE), false);
636 }
637 
638 /**
639  * Searches the receiver's list starting at the first item
640  * (index 0) until an item is found that is equal to the
641  * argument, and returns the index of that item. If no item
642  * is found, returns -1.
643  *
644  * @param item the search item
645  * @return the index of the item
646  *
647  * @exception IllegalArgumentException <ul>
648  *    <li>ERROR_NULL_ARGUMENT - if the item is null</li>
649  * </ul>
650  * @exception SWTException <ul>
651  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
652  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
653  * </ul>
654  */
indexOf(TabItem item)655 public int indexOf (TabItem item) {
656 	checkWidget();
657 	if (item == null) error (SWT.ERROR_NULL_ARGUMENT);
658 
659 	int index = -1;
660 	int count = getItemCount();
661 	for (int i = 0; i < count; i++) {
662 		if (items [i] == item) {
663 			index = i;
664 			break;
665 		}
666 	}
667 	return index;
668 }
669 
670 @Override
minimumSize(int wHint, int hHint, boolean flushCache)671 Point minimumSize (int wHint, int hHint, boolean flushCache) {
672 	Control [] children = _getChildren ();
673 	int width = 0, height = 0;
674 	for (int i=0; i<children.length; i++) {
675 		Control child = children [i];
676 		int index = 0;
677 		int count = getItemCount();
678 		while (index < count) {
679 			if (items [index].control == child) break;
680 			index++;
681 		}
682 		if (index == count) {
683 			Rectangle rect = DPIUtil.autoScaleUp(child.getBounds ());
684 			width = Math.max (width, rect.x + rect.width);
685 			height = Math.max (height, rect.y + rect.height);
686 		} else {
687 			/*
688 			 * Since computeSize can be overridden by subclasses, we cannot
689 			 * call computeSizeInPixels directly.
690 			 */
691 			Point size = DPIUtil.autoScaleUp(child.computeSize (DPIUtil.autoScaleDown(wHint), DPIUtil.autoScaleDown(hHint), flushCache));
692 			width = Math.max (width, size.x);
693 			height = Math.max (height, size.y);
694 		}
695 	}
696 	return new Point (width, height);
697 }
698 
699 @Override
mnemonicHit(char key)700 boolean mnemonicHit (char key) {
701 	int itemCount = getItemCount ();
702 	for (int i=0; i<itemCount; i++) {
703 		long labelHandle = items [i].labelHandle;
704 		if (labelHandle != 0 && mnemonicHit (labelHandle, key)) return true;
705 	}
706 	return false;
707 }
708 
709 @Override
mnemonicMatch(char key)710 boolean mnemonicMatch (char key) {
711 	int itemCount = getItemCount ();
712 	for (int i=0; i<itemCount; i++) {
713 		long labelHandle = items [i].labelHandle;
714 		if (labelHandle != 0 && mnemonicHit (labelHandle, key)) return true;
715 	}
716 	return false;
717 }
718 
719 @Override
releaseChildren(boolean destroy)720 void releaseChildren (boolean destroy) {
721 	if (items != null) {
722 		for (int i=0; i<items.length; i++) {
723 			TabItem item = items [i];
724 			if (item != null && !item.isDisposed ()) {
725 				item.release (false);
726 			}
727 		}
728 		items = null;
729 	}
730 	super.releaseChildren (destroy);
731 }
732 
733 @Override
releaseWidget()734 void releaseWidget () {
735 	super.releaseWidget ();
736 	if (imageList != null) imageList.dispose ();
737 	imageList = null;
738 }
739 
740 @Override
removeControl(Control control)741 void removeControl (Control control) {
742 	super.removeControl (control);
743 	int count = getItemCount ();
744 	for (int i=0; i<count; i++) {
745 		TabItem item = items [i];
746 		if (item.control == control) item.setControl (null);
747 	}
748 }
749 
750 /**
751  * Removes the listener from the collection of listeners who will
752  * be notified when the user changes the receiver's selection.
753  *
754  * @param listener the listener which should no longer be notified
755  *
756  * @exception IllegalArgumentException <ul>
757  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
758  * </ul>
759  * @exception SWTException <ul>
760  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
761  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
762  * </ul>
763  *
764  * @see SelectionListener
765  * @see #addSelectionListener
766  */
removeSelectionListener(SelectionListener listener)767 public void removeSelectionListener (SelectionListener listener) {
768 	checkWidget ();
769 	if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
770 	if (eventTable == null) return;
771 	eventTable.unhook (SWT.Selection, listener);
772 	eventTable.unhook (SWT.DefaultSelection,listener);
773 }
774 
775 @Override
reskinChildren(int flags)776 void reskinChildren (int flags) {
777 	if (items != null) {
778 		int count = getItemCount();
779 
780 		for (int i = 0; i < count; i++) {
781 			TabItem item = items [i];
782 			if (item != null) item.reskin(flags);
783 		}
784 	}
785 	super.reskinChildren (flags);
786 }
787 
788 @Override
setBackgroundGdkRGBA(long context, long handle, GdkRGBA rgba)789 void setBackgroundGdkRGBA (long context, long handle, GdkRGBA rgba) {
790 	// Form background string
791 	String css = "notebook header {background-color: " + display.gtk_rgba_to_css_string (rgba) + ";}";
792 
793 		// Cache background
794 		cssBackground = css;
795 
796 		// Apply background color and any cached foreground color
797 	String finalCss = display.gtk_css_create_css_color_string (cssBackground, cssForeground, SWT.BACKGROUND);
798 	gtk_css_provider_load_from_css (context, finalCss);
799 }
800 
801 @Override
setBounds(int x, int y, int width, int height, boolean move, boolean resize)802 int setBounds (int x, int y, int width, int height, boolean move, boolean resize) {
803 	int result = super.setBounds (x, y, width, height, move, resize);
804 	if ((result & RESIZED) != 0) {
805 		int index = getSelectionIndex ();
806 		if (index != -1) {
807 			TabItem item = items [index];
808 			Control control = item.control;
809 			if (control != null && !control.isDisposed ()) {
810 				control.setBoundsInPixels (getClientAreaInPixels ());
811 			}
812 		}
813 	}
814 	return result;
815 }
816 
817 @Override
setFontDescription(long font)818 void setFontDescription (long font) {
819 	super.setFontDescription (font);
820 	TabItem [] items = getItems ();
821 	for (int i = 0; i < items.length; i++) {
822 		if (items[i] != null) {
823 			items[i].setFontDescription (font);
824 		}
825 	}
826 }
827 
828 @Override
setForegroundGdkRGBA(GdkRGBA rgba)829 void setForegroundGdkRGBA (GdkRGBA rgba) {
830 	super.setForegroundGdkRGBA(rgba);
831 	TabItem [] items = getItems ();
832 	for (int i = 0; i < items.length; i++) {
833 		if (items[i] != null) {
834 			items[i].setForegroundRGBA (rgba);
835 		}
836 	}
837 }
838 
839 @Override
setOrientation(boolean create)840 void setOrientation (boolean create) {
841 	super.setOrientation (create);
842 	if (items != null) {
843 		for (int i=0; i<items.length; i++) {
844 			if (items[i] != null) items[i].setOrientation (create);
845 		}
846 	}
847 }
848 
849 /**
850  * Selects the item at the given zero-relative index in the receiver.
851  * If the item at the index was already selected, it remains selected.
852  * The current selection is first cleared, then the new items are
853  * selected. Indices that are out of range are ignored.
854  *
855  * @param index the index of the item to select
856  *
857  * @exception SWTException <ul>
858  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
859  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
860  * </ul>
861  */
setSelection(int index)862 public void setSelection (int index) {
863 	checkWidget ();
864 	if (!(0 <= index && index < getItemCount ())) return;
865 	setSelection (index, false);
866 }
867 
setSelection(int index, boolean notify)868 void setSelection (int index, boolean notify) {
869 	if (index < 0) return;
870 	int oldIndex = GTK.gtk_notebook_get_current_page (handle);
871 	if (oldIndex == index) return;
872 	if (oldIndex != -1) {
873 		TabItem item = items [oldIndex];
874 		Control control = item.control;
875 		if (control != null && !control.isDisposed ()) {
876 			control.setVisible (false);
877 		}
878 	}
879 	OS.g_signal_handlers_block_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, SWITCH_PAGE);
880 	GTK.gtk_notebook_set_current_page (handle, index);
881 	OS.g_signal_handlers_unblock_matched (handle, OS.G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, SWITCH_PAGE);
882 	int newIndex = GTK.gtk_notebook_get_current_page (handle);
883 	if (newIndex != -1) {
884 		TabItem item = items [newIndex];
885 		Control control = item.control;
886 		if (control != null && !control.isDisposed ()) {
887 			control.setBoundsInPixels (getClientAreaInPixels ());
888 			control.setVisible (true);
889 		}
890 		if (notify) {
891 			Event event = new Event ();
892 			event.item = item;
893 			sendSelectionEvent (SWT.Selection, event, true);
894 		}
895 	}
896 }
897 
898 /**
899  * Sets the receiver's selection to the given item.
900  * The current selected is first cleared, then the new item is
901  * selected.
902  *
903  * @param item the item to select
904  *
905  * @exception IllegalArgumentException <ul>
906  *    <li>ERROR_NULL_ARGUMENT - if the item is null</li>
907  * </ul>
908  * @exception SWTException <ul>
909  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
910  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
911  * </ul>
912  *
913  * @since 3.2
914  */
setSelection(TabItem item)915 public void setSelection (TabItem item) {
916 	if (item == null) error (SWT.ERROR_NULL_ARGUMENT);
917 	setSelection (new TabItem [] {item});
918 }
919 
920 /**
921  * Sets the receiver's selection to be the given array of items.
922  * The current selected is first cleared, then the new items are
923  * selected.
924  *
925  * @param items the array of items
926  *
927  * @exception IllegalArgumentException <ul>
928  *    <li>ERROR_NULL_ARGUMENT - if the items array is null</li>
929  * </ul>
930  * @exception SWTException <ul>
931  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
932  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
933  * </ul>
934  */
setSelection(TabItem [] items)935 public void setSelection (TabItem [] items) {
936 	checkWidget();
937 	if (items == null) error (SWT.ERROR_NULL_ARGUMENT);
938 	if (items.length == 0) {
939 		setSelection (-1, false);
940 	} else {
941 		for (int i=items.length-1; i>=0; --i) {
942 			int index = indexOf (items [i]);
943 			if (index != -1) setSelection (index, false);
944 		}
945 	}
946 }
947 
948 @Override
traversePage(final boolean next)949 boolean traversePage (final boolean next) {
950 	if (next) {
951 		GTK.gtk_notebook_next_page (handle);
952 	} else {
953 		GTK.gtk_notebook_prev_page (handle);
954 	}
955 	return true;
956 }
957 
958 }
959