1/*
2 * Copyright 2007-2010, Haiku. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 *	Authors:
6 *		Stefano Ceccherini (burton666@libero.it)
7 *		Ingo Weinhold (ingo_weinhold@gmx.de)
8 */
9
10
11/*!	The SmartTabView class is a BTabView descendant that hides the tab bar
12	as long as there is only a single tab.
13	Furthermore, it provides a tab context menu, as well as allowing you to
14	close buttons with the middle mouse button.
15*/
16
17
18#include "SmartTabView.h"
19
20#include <stdio.h>
21
22#include <Catalog.h>
23#include <Locale.h>
24#include <Message.h>
25#include <Messenger.h>
26#include <Screen.h>
27#include <ScrollView.h>
28#include <Window.h>
29
30
31// #pragma mark - SmartTabView
32
33
34SmartTabView::SmartTabView(BRect frame, const char* name, button_width width,
35		uint32 resizingMode, uint32 flags)
36	:
37	BTabView(frame, name, width, resizingMode, flags),
38	fInsets(0, 0, 0, 0),
39	fScrollView(NULL),
40	fListener(NULL)
41{
42	// Resize the container view to fill the complete tab view for single-tab
43	// mode. Later, when more than one tab is added, we shrink the container
44	// view again.
45	frame.OffsetTo(B_ORIGIN);
46	ContainerView()->MoveTo(frame.LeftTop());
47	ContainerView()->ResizeTo(frame.Width(), frame.Height());
48}
49
50
51SmartTabView::~SmartTabView()
52{
53}
54
55
56void
57SmartTabView::SetInsets(float left, float top, float right, float bottom)
58{
59	fInsets.left = left;
60	fInsets.top = top;
61	fInsets.right = right;
62	fInsets.bottom = bottom;
63}
64
65
66void
67SmartTabView::MouseDown(BPoint point)
68{
69	bool handled = false;
70
71	if (CountTabs() > 1) {
72		int32 tabIndex = _ClickedTabIndex(point);
73		int32 buttons = 0;
74		int32 clickCount = 0;
75		Window()->CurrentMessage()->FindInt32("buttons", &buttons);
76		Window()->CurrentMessage()->FindInt32("clicks", &clickCount);
77
78		if ((buttons & B_PRIMARY_MOUSE_BUTTON) != 0 && clickCount == 2) {
79			if (fListener != NULL)
80				fListener->TabDoubleClicked(this, point, tabIndex);
81		} else if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0) {
82			if (fListener != NULL)
83				fListener->TabRightClicked(this, point, tabIndex);
84			handled = true;
85		} else if ((buttons & B_TERTIARY_MOUSE_BUTTON) != 0) {
86			if (fListener != NULL)
87				fListener->TabMiddleClicked(this, point, tabIndex);
88			handled = true;
89		}
90	}
91
92	if (!handled)
93		BTabView::MouseDown(point);
94}
95
96
97void
98SmartTabView::AttachedToWindow()
99{
100	BTabView::AttachedToWindow();
101}
102
103
104void
105SmartTabView::AllAttached()
106{
107	BTabView::AllAttached();
108}
109
110
111void
112SmartTabView::Select(int32 index)
113{
114	BTabView::Select(index);
115	BView *view = ViewForTab(index);
116	if (view != NULL) {
117		view->MoveTo(fInsets.LeftTop());
118		view->ResizeTo(ContainerView()->Bounds().Width()
119				- fInsets.left - fInsets.right,
120			ContainerView()->Bounds().Height() - fInsets.top - fInsets.bottom);
121	}
122
123	if (fListener != NULL)
124		fListener->TabSelected(this, index);
125}
126
127
128void
129SmartTabView::AddTab(BView* target, BTab* tab)
130{
131	if (target == NULL)
132		return;
133
134	BTabView::AddTab(target, tab);
135
136	if (CountTabs() == 1) {
137		// Call select on the tab, since
138		// we're resizing the contained view
139		// inside that function
140		Select(0);
141	} else if (CountTabs() == 2) {
142		// Need to resize the view, since we're switching from "normal"
143		// to tabbed mode
144		ContainerView()->ResizeBy(0, -TabHeight());
145		ContainerView()->MoveBy(0, TabHeight());
146
147		// Make sure the content size stays the same, but take special care
148		// of full screen mode
149		BScreen screen(Window());
150		if (Window()->DecoratorFrame().Height() + 2 * TabHeight()
151				< screen.Frame().Height()) {
152			if (Window()->Frame().bottom + TabHeight()
153				> screen.Frame().bottom - 5) {
154				Window()->MoveBy(0, -TabHeight());
155			}
156
157			Window()->ResizeBy(0, TabHeight());
158		}
159
160		// Adapt scroll bar if there is one
161		if (fScrollView != NULL) {
162			BScrollBar* bar = fScrollView->ScrollBar(B_VERTICAL);
163			if (bar != NULL) {
164				bar->ResizeBy(0, -1);
165				bar->MoveBy(0, 1);
166			}
167		}
168	}
169
170	Invalidate(TabFrame(CountTabs() - 1).InsetByCopy(-2, -2));
171}
172
173
174BTab*
175SmartTabView::RemoveTab(int32 index)
176{
177	if (CountTabs() == 2) {
178		// Hide the tab bar again by resizing the container view
179
180		// Make sure the content size stays the same, but take special care
181		// of full screen mode
182		BScreen screen(Window());
183		if (Window()->DecoratorFrame().Height() + 2 * TabHeight()
184				< screen.Frame().Height()) {
185			if (Window()->Frame().bottom
186				> screen.Frame().bottom - 5 - TabHeight()) {
187				Window()->MoveBy(0, TabHeight());
188			}
189			Window()->ResizeBy(0, -TabHeight());
190		}
191
192		// Adapt scroll bar if there is one
193		if (fScrollView != NULL) {
194			BScrollBar* bar = fScrollView->ScrollBar(B_VERTICAL);
195			if (bar != NULL) {
196				bar->ResizeBy(0, 1);
197				bar->MoveBy(0, -1);
198			}
199		}
200
201		ContainerView()->MoveBy(0, -TabHeight());
202		ContainerView()->ResizeBy(0, TabHeight());
203	}
204
205	return BTabView::RemoveTab(index);
206}
207
208
209void
210SmartTabView::MoveTab(int32 index, int32 newIndex)
211{
212	// check the indexes
213	int32 count = CountTabs();
214	if (index == newIndex || index < 0 || newIndex < 0 || index >= count
215		|| newIndex >= count) {
216		return;
217	}
218
219	// Remove the tab from its position and add it to the end. Then cycle the
220	// tabs starting after the new position (save the tab already moved) to the
221	// end.
222	BTab* tab = BTabView::RemoveTab(index);
223	BTabView::AddTab(tab->View(), tab);
224
225	for (int32 i = newIndex; i < count - 1; i++) {
226		tab = BTabView::RemoveTab(newIndex);
227		BTabView::AddTab(tab->View(), tab);
228	}
229}
230
231
232BRect
233SmartTabView::DrawTabs()
234{
235	if (CountTabs() > 1)
236		return BTabView::DrawTabs();
237
238	return BRect();
239}
240
241
242/*!	If you have a vertical scroll view that overlaps with the menu bar, it will
243	be resized automatically when the tabs are hidden/shown.
244*/
245void
246SmartTabView::SetScrollView(BScrollView* scrollView)
247{
248	fScrollView = scrollView;
249}
250
251
252int32
253SmartTabView::_ClickedTabIndex(const BPoint& point)
254{
255	if (point.y <= TabHeight()) {
256		for (int32 i = 0; i < CountTabs(); i++) {
257			if (TabFrame(i).Contains(point))
258				return i;
259		}
260	}
261
262	return -1;
263}
264
265
266// #pragma mark - Listener
267
268
269SmartTabView::Listener::~Listener()
270{
271}
272
273
274void
275SmartTabView::Listener::TabSelected(SmartTabView* tabView, int32 index)
276{
277}
278
279
280void
281SmartTabView::Listener::TabDoubleClicked(SmartTabView* tabView, BPoint point,
282	int32 index)
283{
284}
285
286
287void
288SmartTabView::Listener::TabMiddleClicked(SmartTabView* tabView, BPoint point,
289	int32 index)
290{
291}
292
293
294void
295SmartTabView::Listener::TabRightClicked(SmartTabView* tabView, BPoint point,
296	int32 index)
297{
298}
299