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