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