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