xref: /haiku/src/apps/terminal/SmartTabView.cpp (revision 1345706a9ff6ad0dc041339a02d4259998b0765d)
1 /*
2  * Copyright 2007-2009, Haiku. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  *	Authors:
6  *		Stefano Ceccherini (burton666@libero.it)
7  */
8 
9 
10 /*!	The SmartTabView class is a BTabView descendant that hides the tab bar
11 	as long as there is only a single tab.
12 	Furthermore, it provides a tab context menu, as well as allowing you to
13 	close buttons with the middle mouse button.
14 */
15 
16 
17 #include "SmartTabView.h"
18 
19 #include <Catalog.h>
20 #include <Locale.h>
21 #include <MenuItem.h>
22 #include <Message.h>
23 #include <Messenger.h>
24 #include <PopUpMenu.h>
25 #include <Screen.h>
26 #include <ScrollView.h>
27 #include <Window.h>
28 
29 #include <stdio.h>
30 
31 
32 const static uint32 kCloseTab = 'ClTb';
33 
34 
35 SmartTabView::SmartTabView(BRect frame, const char* name, button_width width,
36 		uint32 resizingMode, uint32 flags)
37 	:
38 	BTabView(frame, name, width, resizingMode, flags),
39 	fInsets(0, 0, 0, 0),
40 	fScrollView(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 
51 SmartTabView::~SmartTabView()
52 {
53 }
54 
55 
56 void
57 SmartTabView::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 #undef B_TRANSLATE_CONTEXT
66 #define B_TRANSLATE_CONTEXT "Terminal SmartTabView"
67 
68 void
69 SmartTabView::MouseDown(BPoint point)
70 {
71 	bool handled = false;
72 
73 	if (CountTabs() > 1) {
74 		int32 tabIndex = _ClickedTabIndex(point);
75 		if (tabIndex >= 0) {
76 			int32 buttons;
77 			Window()->CurrentMessage()->FindInt32("buttons", &buttons);
78 			if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0) {
79 				BMessage* message = new BMessage(kCloseTab);
80 				message->AddInt32("index", tabIndex);
81 
82 				BPopUpMenu* popUpMenu = new BPopUpMenu("tab menu");
83 				popUpMenu->AddItem(new BMenuItem(B_TRANSLATE("Close tab"),
84 					message));
85 				popUpMenu->SetAsyncAutoDestruct(true);
86 				popUpMenu->SetTargetForItems(BMessenger(this));
87 				popUpMenu->Go(ConvertToScreen(point), true, true, true);
88 
89 				handled = true;
90 			} else if ((buttons & B_TERTIARY_MOUSE_BUTTON) != 0) {
91 				RemoveAndDeleteTab(tabIndex);
92 				handled = true;
93 			}
94 		}
95 	}
96 
97 	if (!handled)
98 		BTabView::MouseDown(point);
99 }
100 
101 
102 void
103 SmartTabView::AttachedToWindow()
104 {
105 	BTabView::AttachedToWindow();
106 }
107 
108 
109 void
110 SmartTabView::AllAttached()
111 {
112 	BTabView::AllAttached();
113 }
114 
115 
116 void
117 SmartTabView::MessageReceived(BMessage *message)
118 {
119 	switch (message->what) {
120 		case kCloseTab:
121 		{
122 			int32 tabIndex = 0;
123 			if (message->FindInt32("index", &tabIndex) == B_OK)
124 				RemoveAndDeleteTab(tabIndex);
125 			break;
126 		}
127 		default:
128 			BTabView::MessageReceived(message);
129 			break;
130 	}
131 }
132 
133 
134 void
135 SmartTabView::Select(int32 index)
136 {
137 	BTabView::Select(index);
138 	BView *view = ViewForTab(index);
139 	if (view != NULL) {
140 		view->MoveTo(fInsets.LeftTop());
141 		view->ResizeTo(ContainerView()->Bounds().Width()
142 				- fInsets.left - fInsets.right,
143 			ContainerView()->Bounds().Height() - fInsets.top - fInsets.bottom);
144 	}
145 }
146 
147 
148 void
149 SmartTabView::RemoveAndDeleteTab(int32 index)
150 {
151 	// Select another tab
152 	if (index == Selection()) {
153 		if (index > 0)
154 			Select(index - 1);
155 		else if (index < CountTabs())
156 			Select(index + 1);
157 	}
158 	delete RemoveTab(index);
159 }
160 
161 
162 void
163 SmartTabView::AddTab(BView* target, BTab* tab)
164 {
165 	if (target == NULL)
166 		return;
167 
168 	BTabView::AddTab(target, tab);
169 
170 	if (CountTabs() == 1) {
171 		// Call select on the tab, since
172 		// we're resizing the contained view
173 		// inside that function
174 		Select(0);
175 	} else if (CountTabs() == 2) {
176 		// Need to resize the view, since we're switching from "normal"
177 		// to tabbed mode
178 		ContainerView()->ResizeBy(0, -TabHeight());
179 		ContainerView()->MoveBy(0, TabHeight());
180 
181 		// Make sure the content size stays the same, but take special care
182 		// of full screen mode
183 		BScreen screen(Window());
184 		if (Window()->DecoratorFrame().Height() + 2 * TabHeight()
185 				< screen.Frame().Height()) {
186 			if (Window()->Frame().bottom + TabHeight()
187 				> screen.Frame().bottom - 5) {
188 				Window()->MoveBy(0, -TabHeight());
189 			}
190 
191 			Window()->ResizeBy(0, TabHeight());
192 		}
193 
194 		// Adapt scroll bar if there is one
195 		if (fScrollView != NULL) {
196 			BScrollBar* bar = fScrollView->ScrollBar(B_VERTICAL);
197 			if (bar != NULL) {
198 				bar->ResizeBy(0, -1);
199 				bar->MoveBy(0, 1);
200 			}
201 		}
202 	}
203 
204 	Invalidate(TabFrame(CountTabs() - 1).InsetByCopy(-2, -2));
205 }
206 
207 
208 BTab*
209 SmartTabView::RemoveTab(int32 index)
210 {
211 	if (CountTabs() == 2) {
212 		// Hide the tab bar again by resizing the container view
213 
214 		// Make sure the content size stays the same, but take special care
215 		// of full screen mode
216 		BScreen screen(Window());
217 		if (Window()->DecoratorFrame().Height() + 2 * TabHeight()
218 				< screen.Frame().Height()) {
219 			if (Window()->Frame().bottom
220 				> screen.Frame().bottom - 5 - TabHeight()) {
221 				Window()->MoveBy(0, TabHeight());
222 			}
223 			Window()->ResizeBy(0, -TabHeight());
224 		}
225 
226 		// Adapt scroll bar if there is one
227 		if (fScrollView != NULL) {
228 			BScrollBar* bar = fScrollView->ScrollBar(B_VERTICAL);
229 			if (bar != NULL) {
230 				bar->ResizeBy(0, 1);
231 				bar->MoveBy(0, -1);
232 			}
233 		}
234 
235 		ContainerView()->MoveBy(0, -TabHeight());
236 		ContainerView()->ResizeBy(0, TabHeight());
237 	}
238 
239 	return BTabView::RemoveTab(index);
240 }
241 
242 
243 BRect
244 SmartTabView::DrawTabs()
245 {
246 	if (CountTabs() > 1)
247 		return BTabView::DrawTabs();
248 
249 	return BRect();
250 }
251 
252 
253 /*!	If you have a vertical scroll view that overlaps with the menu bar, it will
254 	be resized automatically when the tabs are hidden/shown.
255 */
256 void
257 SmartTabView::SetScrollView(BScrollView* scrollView)
258 {
259 	fScrollView = scrollView;
260 }
261 
262 
263 int32
264 SmartTabView::_ClickedTabIndex(const BPoint& point)
265 {
266 	if (point.y <= TabHeight()) {
267 		for (int32 i = 0; i < CountTabs(); i++) {
268 			if (TabFrame(i).Contains(point))
269 				return i;
270 		}
271 	}
272 
273 	return -1;
274 }
275