1 /*
2 * Copyright (C) 2010 Rene Gollent <rene@gollent.com>
3 * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de>
4 *
5 * All rights reserved. Distributed under the terms of the MIT License.
6 */
7
8 #include "TabContainerView.h"
9
10 #include <stdio.h>
11
12 #include <Application.h>
13 #include <AbstractLayoutItem.h>
14 #include <Bitmap.h>
15 #include <Button.h>
16 #include <CardLayout.h>
17 #include <Catalog.h>
18 #include <ControlLook.h>
19 #include <GroupView.h>
20 #include <SpaceLayoutItem.h>
21 #include <Window.h>
22
23 #include "TabView.h"
24
25
26 #undef B_TRANSLATION_CONTEXT
27 #define B_TRANSLATION_CONTEXT "Tab Manager"
28
29
30 static const float kLeftTabInset = 4;
31
32
TabContainerView(Controller * controller)33 TabContainerView::TabContainerView(Controller* controller)
34 :
35 BGroupView(B_HORIZONTAL, 0.0),
36 fLastMouseEventTab(NULL),
37 fMouseDown(false),
38 fClickCount(0),
39 fSelectedTab(NULL),
40 fController(controller),
41 fFirstVisibleTabIndex(0)
42 {
43 SetFlags(Flags() | B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE);
44 SetViewColor(B_TRANSPARENT_COLOR);
45 GroupLayout()->SetInsets(kLeftTabInset, 0, 0, 1);
46 GroupLayout()->AddItem(BSpaceLayoutItem::CreateGlue(), 0.0f);
47 }
48
49
~TabContainerView()50 TabContainerView::~TabContainerView()
51 {
52 }
53
54
55 BSize
MinSize()56 TabContainerView::MinSize()
57 {
58 // Eventually, we want to be scrolling if the tabs don't fit.
59 BSize size(BGroupView::MinSize());
60 size.width = 300;
61 return size;
62 }
63
64
65 void
MessageReceived(BMessage * message)66 TabContainerView::MessageReceived(BMessage* message)
67 {
68 switch (message->what) {
69 default:
70 BGroupView::MessageReceived(message);
71 }
72 }
73
74
75 void
Draw(BRect updateRect)76 TabContainerView::Draw(BRect updateRect)
77 {
78 // draw tab frame
79 BRect rect(Bounds());
80 rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
81 uint32 borders = BControlLook::B_TOP_BORDER
82 | BControlLook::B_BOTTOM_BORDER;
83 be_control_look->DrawTabFrame(this, rect, updateRect, base, 0,
84 borders, B_NO_BORDER);
85
86 // draw tabs on top of frame
87 BGroupLayout* layout = GroupLayout();
88 int32 count = layout->CountItems() - 1;
89 for (int32 i = 0; i < count; i++) {
90 TabLayoutItem* item = dynamic_cast<TabLayoutItem*>(layout->ItemAt(i));
91 if (item == NULL || !item->IsVisible())
92 continue;
93 item->Parent()->Draw(item->Frame());
94 }
95 }
96
97
98 void
MouseDown(BPoint where)99 TabContainerView::MouseDown(BPoint where)
100 {
101 if (Window() == NULL)
102 return;
103
104 BMessage* currentMessage = Window()->CurrentMessage();
105 if (currentMessage == NULL)
106 return;
107
108 uint32 buttons;
109 if (currentMessage->FindInt32("buttons", (int32*)&buttons) != B_OK)
110 buttons = B_PRIMARY_MOUSE_BUTTON;
111
112 uint32 clicks;
113 if (currentMessage->FindInt32("clicks", (int32*)&clicks) != B_OK)
114 clicks = 1;
115
116 fMouseDown = true;
117 SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
118
119 if (fLastMouseEventTab != NULL)
120 fLastMouseEventTab->MouseDown(where, buttons);
121 else {
122 if ((buttons & B_TERTIARY_MOUSE_BUTTON) != 0) {
123 // Middle click outside tabs should always open a new tab.
124 fController->DoubleClickOutsideTabs();
125 } else if (clicks > 1)
126 fClickCount++;
127 else
128 fClickCount = 1;
129 }
130 }
131
132
133 void
MouseUp(BPoint where)134 TabContainerView::MouseUp(BPoint where)
135 {
136 fMouseDown = false;
137 if (fLastMouseEventTab) {
138 fLastMouseEventTab->MouseUp(where);
139 fClickCount = 0;
140 } else if (fClickCount > 1) {
141 // NOTE: fClickCount is >= 1 only if the first click was outside
142 // any tab. So even if fLastMouseEventTab has been reset to NULL
143 // because this tab was removed during mouse down, we wouldn't
144 // run the "outside tabs" code below.
145 fController->DoubleClickOutsideTabs();
146 fClickCount = 0;
147 }
148 // Always check the tab under the mouse again, since we don't update
149 // it with fMouseDown == true.
150 _SendFakeMouseMoved();
151 }
152
153
154 void
MouseMoved(BPoint where,uint32 transit,const BMessage * dragMessage)155 TabContainerView::MouseMoved(BPoint where, uint32 transit,
156 const BMessage* dragMessage)
157 {
158 _MouseMoved(where, transit, dragMessage);
159 }
160
161
162 void
DoLayout()163 TabContainerView::DoLayout()
164 {
165 BGroupView::DoLayout();
166
167 _ValidateTabVisibility();
168 _SendFakeMouseMoved();
169 }
170
171 void
AddTab(const char * label,int32 index)172 TabContainerView::AddTab(const char* label, int32 index)
173 {
174 TabView* tab;
175 if (fController != NULL)
176 tab = fController->CreateTabView();
177 else
178 tab = new TabView();
179
180 tab->SetLabel(label);
181 AddTab(tab, index);
182 }
183
184
185 void
AddTab(TabView * tab,int32 index)186 TabContainerView::AddTab(TabView* tab, int32 index)
187 {
188 tab->SetContainerView(this);
189
190 if (index == -1)
191 index = GroupLayout()->CountItems() - 1;
192
193 tab->Update();
194
195 GroupLayout()->AddItem(index, tab->LayoutItem());
196
197 if (fSelectedTab == NULL)
198 SelectTab(tab);
199
200 bool isLast = index == GroupLayout()->CountItems() - 1;
201 if (isLast) {
202 TabLayoutItem* item
203 = dynamic_cast<TabLayoutItem*>(GroupLayout()->ItemAt(index - 1));
204 if (item != NULL)
205 item->Parent()->Update();
206 }
207
208
209 SetFirstVisibleTabIndex(MaxFirstVisibleTabIndex());
210 _ValidateTabVisibility();
211 }
212
213
214 TabView*
RemoveTab(int32 index)215 TabContainerView::RemoveTab(int32 index)
216 {
217 TabLayoutItem* item
218 = dynamic_cast<TabLayoutItem*>(GroupLayout()->RemoveItem(index));
219 if (item == NULL)
220 return NULL;
221
222 BRect dirty(Bounds());
223 dirty.left = item->Frame().left;
224 TabView* removedTab = item->Parent();
225 removedTab->SetContainerView(NULL);
226
227 if (removedTab == fLastMouseEventTab)
228 fLastMouseEventTab = NULL;
229
230 // Update tabs after or before the removed tab.
231 item = dynamic_cast<TabLayoutItem*>(GroupLayout()->ItemAt(index));
232 if (item != NULL) {
233 // This tab is behind the removed tab.
234 TabView* tab = item->Parent();
235 tab->Update();
236 if (removedTab == fSelectedTab) {
237 fSelectedTab = NULL;
238 SelectTab(tab);
239 } else if (fController != NULL && tab == fSelectedTab)
240 fController->UpdateSelection(index);
241 } else {
242 // The removed tab was the last tab.
243 item = dynamic_cast<TabLayoutItem*>(GroupLayout()->ItemAt(index - 1));
244 if (item != NULL) {
245 TabView* tab = item->Parent();
246 tab->Update();
247 if (removedTab == fSelectedTab) {
248 fSelectedTab = NULL;
249 SelectTab(tab);
250 }
251 }
252 }
253
254 Invalidate(dirty);
255 _ValidateTabVisibility();
256
257 return removedTab;
258 }
259
260
261 TabView*
TabAt(int32 index) const262 TabContainerView::TabAt(int32 index) const
263 {
264 TabLayoutItem* item = dynamic_cast<TabLayoutItem*>(
265 GroupLayout()->ItemAt(index));
266 if (item != NULL)
267 return item->Parent();
268
269 return NULL;
270 }
271
272
273 int32
IndexOf(TabView * tab) const274 TabContainerView::IndexOf(TabView* tab) const
275 {
276 if (tab == NULL || GroupLayout() == NULL)
277 return -1;
278
279 return GroupLayout()->IndexOfItem(tab->LayoutItem());
280 }
281
282
283 void
SelectTab(int32 index)284 TabContainerView::SelectTab(int32 index)
285 {
286 TabView* tab = NULL;
287 TabLayoutItem* item = dynamic_cast<TabLayoutItem*>(
288 GroupLayout()->ItemAt(index));
289 if (item != NULL)
290 tab = item->Parent();
291
292 SelectTab(tab);
293 }
294
295
296 void
SelectTab(TabView * tab)297 TabContainerView::SelectTab(TabView* tab)
298 {
299 if (tab == fSelectedTab)
300 return;
301
302 // update old selected tab
303 if (fSelectedTab != NULL)
304 fSelectedTab->Update();
305
306 fSelectedTab = tab;
307
308 // update new selected tab
309 if (fSelectedTab != NULL)
310 fSelectedTab->Update();
311
312 int32 index = -1;
313 if (fSelectedTab != NULL)
314 index = GroupLayout()->IndexOfItem(tab->LayoutItem());
315
316 if (!tab->LayoutItem()->IsVisible())
317 SetFirstVisibleTabIndex(index);
318
319 if (fController != NULL)
320 fController->UpdateSelection(index);
321 }
322
323
324 void
SetTabLabel(int32 index,const char * label)325 TabContainerView::SetTabLabel(int32 index, const char* label)
326 {
327 TabLayoutItem* item = dynamic_cast<TabLayoutItem*>(
328 GroupLayout()->ItemAt(index));
329 if (item == NULL)
330 return;
331
332 item->Parent()->SetLabel(label);
333 }
334
335
336 void
SetFirstVisibleTabIndex(int32 index)337 TabContainerView::SetFirstVisibleTabIndex(int32 index)
338 {
339 if (index < 0)
340 index = 0;
341 if (index > MaxFirstVisibleTabIndex())
342 index = MaxFirstVisibleTabIndex();
343 if (fFirstVisibleTabIndex == index)
344 return;
345
346 fFirstVisibleTabIndex = index;
347
348 _UpdateTabVisibility();
349 }
350
351
352 int32
FirstVisibleTabIndex() const353 TabContainerView::FirstVisibleTabIndex() const
354 {
355 return fFirstVisibleTabIndex;
356 }
357
358
359 int32
MaxFirstVisibleTabIndex() const360 TabContainerView::MaxFirstVisibleTabIndex() const
361 {
362 float availableWidth = _AvailableWidthForTabs();
363 if (availableWidth < 0)
364 return 0;
365 float visibleTabsWidth = 0;
366
367 BGroupLayout* layout = GroupLayout();
368 int32 i = layout->CountItems() - 2;
369 for (; i >= 0; i--) {
370 TabLayoutItem* item = dynamic_cast<TabLayoutItem*>(
371 layout->ItemAt(i));
372 if (item == NULL)
373 continue;
374
375 float itemWidth = item->MinSize().width;
376 if (availableWidth >= visibleTabsWidth + itemWidth)
377 visibleTabsWidth += itemWidth;
378 else {
379 // The tab before this tab is the last one that can be visible.
380 return i + 1;
381 }
382 }
383
384 return 0;
385 }
386
387
388 bool
CanScrollLeft() const389 TabContainerView::CanScrollLeft() const
390 {
391 return fFirstVisibleTabIndex < MaxFirstVisibleTabIndex();
392 }
393
394
395 bool
CanScrollRight() const396 TabContainerView::CanScrollRight() const
397 {
398 BGroupLayout* layout = GroupLayout();
399 int32 count = layout->CountItems() - 1;
400 if (count > 0) {
401 TabLayoutItem* item = dynamic_cast<TabLayoutItem*>(
402 layout->ItemAt(count - 1));
403 return !item->IsVisible();
404 }
405 return false;
406 }
407
408
409 // #pragma mark -
410
411
412 TabView*
_TabAt(const BPoint & where) const413 TabContainerView::_TabAt(const BPoint& where) const
414 {
415 BGroupLayout* layout = GroupLayout();
416 int32 count = layout->CountItems() - 1;
417 for (int32 i = 0; i < count; i++) {
418 TabLayoutItem* item = dynamic_cast<TabLayoutItem*>(layout->ItemAt(i));
419 if (item == NULL || !item->IsVisible())
420 continue;
421 // Account for the fact that the tab frame does not contain the
422 // visible bottom border.
423 BRect frame = item->Frame();
424 frame.bottom++;
425 if (frame.Contains(where))
426 return item->Parent();
427 }
428 return NULL;
429 }
430
431
432 void
_MouseMoved(BPoint where,uint32 _transit,const BMessage * dragMessage)433 TabContainerView::_MouseMoved(BPoint where, uint32 _transit,
434 const BMessage* dragMessage)
435 {
436 TabView* tab = _TabAt(where);
437 if (fMouseDown) {
438 uint32 transit = tab == fLastMouseEventTab
439 ? B_INSIDE_VIEW : B_OUTSIDE_VIEW;
440 if (fLastMouseEventTab)
441 fLastMouseEventTab->MouseMoved(where, transit, dragMessage);
442 return;
443 }
444
445 if (fLastMouseEventTab != NULL && fLastMouseEventTab == tab)
446 fLastMouseEventTab->MouseMoved(where, B_INSIDE_VIEW, dragMessage);
447 else {
448 if (fLastMouseEventTab)
449 fLastMouseEventTab->MouseMoved(where, B_EXITED_VIEW, dragMessage);
450 fLastMouseEventTab = tab;
451 if (fLastMouseEventTab)
452 fLastMouseEventTab->MouseMoved(where, B_ENTERED_VIEW, dragMessage);
453 else {
454 fController->SetToolTip(
455 B_TRANSLATE("Double-click or middle-click to open new tab."));
456 }
457 }
458 }
459
460
461 void
_ValidateTabVisibility()462 TabContainerView::_ValidateTabVisibility()
463 {
464 if (fFirstVisibleTabIndex > MaxFirstVisibleTabIndex())
465 SetFirstVisibleTabIndex(MaxFirstVisibleTabIndex());
466 else
467 _UpdateTabVisibility();
468 }
469
470
471 void
_UpdateTabVisibility()472 TabContainerView::_UpdateTabVisibility()
473 {
474 float availableWidth = _AvailableWidthForTabs();
475 if (availableWidth < 0)
476 return;
477 float visibleTabsWidth = 0;
478
479 bool canScrollTabsLeft = fFirstVisibleTabIndex > 0;
480 bool canScrollTabsRight = false;
481
482 BGroupLayout* layout = GroupLayout();
483 int32 count = layout->CountItems() - 1;
484 for (int32 i = 0; i < count; i++) {
485 TabLayoutItem* item = dynamic_cast<TabLayoutItem*>(
486 layout->ItemAt(i));
487 if (i < fFirstVisibleTabIndex)
488 item->SetVisible(false);
489 else {
490 float itemWidth = item->MinSize().width;
491 bool visible = availableWidth >= visibleTabsWidth + itemWidth;
492 item->SetVisible(visible && !canScrollTabsRight);
493 visibleTabsWidth += itemWidth;
494 if (!visible)
495 canScrollTabsRight = true;
496 }
497 }
498 fController->UpdateTabScrollability(canScrollTabsLeft, canScrollTabsRight);
499 }
500
501
502 float
_AvailableWidthForTabs() const503 TabContainerView::_AvailableWidthForTabs() const
504 {
505 float width = Bounds().Width() - 10;
506 // TODO: Don't really know why -10 is needed above.
507
508 float left;
509 float right;
510 GroupLayout()->GetInsets(&left, NULL, &right, NULL);
511 width -= left + right;
512
513 return width;
514 }
515
516
517 void
_SendFakeMouseMoved()518 TabContainerView::_SendFakeMouseMoved()
519 {
520 BPoint where;
521 uint32 buttons;
522 GetMouse(&where, &buttons, false);
523 if (Bounds().Contains(where))
524 _MouseMoved(where, B_INSIDE_VIEW, NULL);
525 }
526