repository_infos: Enable automatic updates on the main Haiku repostiory.
[haiku.git] / src / apps / webpositive / tabview / TabContainerView.cpp
blob300173759abf6429d0b7c4c3ed1bafdadb44af14
1 /*
2 * Copyright (C) 2010 Rene Gollent <rene@gollent.com>
3 * Copyright (C) 2010 Stephan Aßmus <superstippi@gmx.de>
5 * All rights reserved. Distributed under the terms of the MIT License.
6 */
8 #include "TabContainerView.h"
10 #include <stdio.h>
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>
23 #include "TabView.h"
26 #undef B_TRANSLATION_CONTEXT
27 #define B_TRANSLATION_CONTEXT "Tab Manager"
30 static const float kLeftTabInset = 4;
33 TabContainerView::TabContainerView(Controller* controller)
35 BGroupView(B_HORIZONTAL, 0.0),
36 fLastMouseEventTab(NULL),
37 fMouseDown(false),
38 fClickCount(0),
39 fSelectedTab(NULL),
40 fController(controller),
41 fFirstVisibleTabIndex(0)
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);
50 TabContainerView::~TabContainerView()
55 BSize
56 TabContainerView::MinSize()
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;
65 void
66 TabContainerView::MessageReceived(BMessage* message)
68 switch (message->what) {
69 default:
70 BGroupView::MessageReceived(message);
75 void
76 TabContainerView::Draw(BRect updateRect)
78 rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
79 BRect frame(Bounds());
81 // Draw empty area before first tab.
82 uint32 borders = BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER;
83 BRect leftFrame(frame.left, frame.top, kLeftTabInset, frame.bottom);
84 be_control_look->DrawInactiveTab(this, leftFrame, updateRect, base, 0,
85 borders);
87 // Draw all tabs, keeping track of where they end.
88 BGroupLayout* layout = GroupLayout();
89 int32 count = layout->CountItems() - 1;
90 for (int32 i = 0; i < count; i++) {
91 TabLayoutItem* item = dynamic_cast<TabLayoutItem*>(
92 layout->ItemAt(i));
93 if (!item || !item->IsVisible())
94 continue;
95 item->Parent()->Draw(updateRect);
96 frame.left = item->Frame().right + 1;
99 // Draw empty area after last tab.
100 be_control_look->DrawInactiveTab(this, frame, updateRect, base, 0, borders);
104 void
105 TabContainerView::MouseDown(BPoint where)
107 uint32 buttons;
108 if (Window()->CurrentMessage()->FindInt32("buttons", (int32*)&buttons) != B_OK)
109 buttons = B_PRIMARY_MOUSE_BUTTON;
110 uint32 clicks;
111 if (Window()->CurrentMessage()->FindInt32("clicks", (int32*)&clicks) != B_OK)
112 clicks = 1;
113 fMouseDown = true;
114 SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS);
115 if (fLastMouseEventTab)
116 fLastMouseEventTab->MouseDown(where, buttons);
117 else {
118 if ((buttons & B_TERTIARY_MOUSE_BUTTON) != 0) {
119 // Middle click outside tabs should always open a new tab.
120 fController->DoubleClickOutsideTabs();
121 } else if (clicks > 1)
122 fClickCount++;
123 else
124 fClickCount = 1;
129 void
130 TabContainerView::MouseUp(BPoint where)
132 fMouseDown = false;
133 if (fLastMouseEventTab) {
134 fLastMouseEventTab->MouseUp(where);
135 fClickCount = 0;
136 } else if (fClickCount > 1) {
137 // NOTE: fClickCount is >= 1 only if the first click was outside
138 // any tab. So even if fLastMouseEventTab has been reset to NULL
139 // because this tab was removed during mouse down, we wouldn't
140 // run the "outside tabs" code below.
141 fController->DoubleClickOutsideTabs();
142 fClickCount = 0;
144 // Always check the tab under the mouse again, since we don't update
145 // it with fMouseDown == true.
146 _SendFakeMouseMoved();
150 void
151 TabContainerView::MouseMoved(BPoint where, uint32 transit,
152 const BMessage* dragMessage)
154 _MouseMoved(where, transit, dragMessage);
158 void
159 TabContainerView::DoLayout()
161 BGroupView::DoLayout();
163 _ValidateTabVisibility();
164 _SendFakeMouseMoved();
167 void
168 TabContainerView::AddTab(const char* label, int32 index)
170 TabView* tab;
171 if (fController)
172 tab = fController->CreateTabView();
173 else
174 tab = new TabView();
175 tab->SetLabel(label);
176 AddTab(tab, index);
180 void
181 TabContainerView::AddTab(TabView* tab, int32 index)
183 tab->SetContainerView(this);
185 if (index == -1)
186 index = GroupLayout()->CountItems() - 1;
188 bool hasFrames = fController != NULL && fController->HasFrames();
189 bool isFirst = index == 0 && hasFrames;
190 bool isLast = index == GroupLayout()->CountItems() - 1 && hasFrames;
191 bool isFront = fSelectedTab == NULL;
192 tab->Update(isFirst, isLast, isFront);
194 GroupLayout()->AddItem(index, tab->LayoutItem());
196 if (isFront)
197 SelectTab(tab);
198 if (isLast) {
199 TabLayoutItem* item
200 = dynamic_cast<TabLayoutItem*>(GroupLayout()->ItemAt(index - 1));
201 if (item)
202 item->Parent()->SetIsLast(false);
205 SetFirstVisibleTabIndex(MaxFirstVisibleTabIndex());
206 _ValidateTabVisibility();
210 TabView*
211 TabContainerView::RemoveTab(int32 index)
213 TabLayoutItem* item
214 = dynamic_cast<TabLayoutItem*>(GroupLayout()->RemoveItem(index));
216 if (!item)
217 return NULL;
219 BRect dirty(Bounds());
220 dirty.left = item->Frame().left;
221 TabView* removedTab = item->Parent();
222 removedTab->SetContainerView(NULL);
224 if (removedTab == fLastMouseEventTab)
225 fLastMouseEventTab = NULL;
227 // Update tabs after or before the removed tab.
228 bool hasFrames = fController != NULL && fController->HasFrames();
229 item = dynamic_cast<TabLayoutItem*>(GroupLayout()->ItemAt(index));
230 if (item) {
231 // This tab is behind the removed tab.
232 TabView* tab = item->Parent();
233 tab->Update(index == 0 && hasFrames,
234 index == GroupLayout()->CountItems() - 2 && hasFrames,
235 tab == fSelectedTab);
236 if (removedTab == fSelectedTab) {
237 fSelectedTab = NULL;
238 SelectTab(tab);
239 } else if (fController && tab == fSelectedTab)
240 fController->TabSelected(index);
241 } else {
242 // The removed tab was the last tab.
243 item = dynamic_cast<TabLayoutItem*>(GroupLayout()->ItemAt(index - 1));
244 if (item) {
245 TabView* tab = item->Parent();
246 tab->Update(index == 0 && hasFrames,
247 index == GroupLayout()->CountItems() - 2 && hasFrames,
248 tab == fSelectedTab);
249 if (removedTab == fSelectedTab) {
250 fSelectedTab = NULL;
251 SelectTab(tab);
256 Invalidate(dirty);
257 _ValidateTabVisibility();
259 return removedTab;
263 TabView*
264 TabContainerView::TabAt(int32 index) const
266 TabLayoutItem* item = dynamic_cast<TabLayoutItem*>(
267 GroupLayout()->ItemAt(index));
268 if (item)
269 return item->Parent();
270 return NULL;
274 int32
275 TabContainerView::IndexOf(TabView* tab) const
277 return GroupLayout()->IndexOfItem(tab->LayoutItem());
281 void
282 TabContainerView::SelectTab(int32 index)
284 TabView* tab = NULL;
285 TabLayoutItem* item = dynamic_cast<TabLayoutItem*>(
286 GroupLayout()->ItemAt(index));
287 if (item)
288 tab = item->Parent();
290 SelectTab(tab);
294 void
295 TabContainerView::SelectTab(TabView* tab)
297 if (tab == fSelectedTab)
298 return;
300 if (fSelectedTab)
301 fSelectedTab->SetIsFront(false);
303 fSelectedTab = tab;
305 if (fSelectedTab)
306 fSelectedTab->SetIsFront(true);
308 if (fController != NULL) {
309 int32 index = -1;
310 if (fSelectedTab != NULL)
311 index = GroupLayout()->IndexOfItem(tab->LayoutItem());
313 if (!tab->LayoutItem()->IsVisible())
314 SetFirstVisibleTabIndex(index);
316 fController->TabSelected(index);
321 void
322 TabContainerView::SetTabLabel(int32 tabIndex, const char* label)
324 TabLayoutItem* item = dynamic_cast<TabLayoutItem*>(
325 GroupLayout()->ItemAt(tabIndex));
326 if (item == NULL)
327 return;
329 item->Parent()->SetLabel(label);
333 void
334 TabContainerView::SetFirstVisibleTabIndex(int32 index)
336 if (index < 0)
337 index = 0;
338 if (index > MaxFirstVisibleTabIndex())
339 index = MaxFirstVisibleTabIndex();
340 if (fFirstVisibleTabIndex == index)
341 return;
343 fFirstVisibleTabIndex = index;
345 _UpdateTabVisibility();
349 int32
350 TabContainerView::FirstVisibleTabIndex() const
352 return fFirstVisibleTabIndex;
356 int32
357 TabContainerView::MaxFirstVisibleTabIndex() const
359 float availableWidth = _AvailableWidthForTabs();
360 if (availableWidth < 0)
361 return 0;
362 float visibleTabsWidth = 0;
364 BGroupLayout* layout = GroupLayout();
365 int32 i = layout->CountItems() - 2;
366 for (; i >= 0; i--) {
367 TabLayoutItem* item = dynamic_cast<TabLayoutItem*>(
368 layout->ItemAt(i));
369 if (item == NULL)
370 continue;
372 float itemWidth = item->MinSize().width;
373 if (availableWidth >= visibleTabsWidth + itemWidth)
374 visibleTabsWidth += itemWidth;
375 else {
376 // The tab before this tab is the last one that can be visible.
377 return i + 1;
381 return 0;
385 bool
386 TabContainerView::CanScrollLeft() const
388 return fFirstVisibleTabIndex < MaxFirstVisibleTabIndex();
392 bool
393 TabContainerView::CanScrollRight() const
395 BGroupLayout* layout = GroupLayout();
396 int32 count = layout->CountItems() - 1;
397 if (count > 0) {
398 TabLayoutItem* item = dynamic_cast<TabLayoutItem*>(
399 layout->ItemAt(count - 1));
400 return !item->IsVisible();
402 return false;
406 // #pragma mark -
409 TabView*
410 TabContainerView::_TabAt(const BPoint& where) const
412 BGroupLayout* layout = GroupLayout();
413 int32 count = layout->CountItems() - 1;
414 for (int32 i = 0; i < count; i++) {
415 TabLayoutItem* item = dynamic_cast<TabLayoutItem*>(layout->ItemAt(i));
416 if (item == NULL || !item->IsVisible())
417 continue;
418 // Account for the fact that the tab frame does not contain the
419 // visible bottom border.
420 BRect frame = item->Frame();
421 frame.bottom++;
422 if (frame.Contains(where))
423 return item->Parent();
425 return NULL;
429 void
430 TabContainerView::_MouseMoved(BPoint where, uint32 _transit,
431 const BMessage* dragMessage)
433 TabView* tab = _TabAt(where);
434 if (fMouseDown) {
435 uint32 transit = tab == fLastMouseEventTab
436 ? B_INSIDE_VIEW : B_OUTSIDE_VIEW;
437 if (fLastMouseEventTab)
438 fLastMouseEventTab->MouseMoved(where, transit, dragMessage);
439 return;
442 if (fLastMouseEventTab != NULL && fLastMouseEventTab == tab)
443 fLastMouseEventTab->MouseMoved(where, B_INSIDE_VIEW, dragMessage);
444 else {
445 if (fLastMouseEventTab)
446 fLastMouseEventTab->MouseMoved(where, B_EXITED_VIEW, dragMessage);
447 fLastMouseEventTab = tab;
448 if (fLastMouseEventTab)
449 fLastMouseEventTab->MouseMoved(where, B_ENTERED_VIEW, dragMessage);
450 else {
451 fController->SetToolTip(
452 B_TRANSLATE("Double-click or middle-click to open new tab."));
458 void
459 TabContainerView::_ValidateTabVisibility()
461 if (fFirstVisibleTabIndex > MaxFirstVisibleTabIndex())
462 SetFirstVisibleTabIndex(MaxFirstVisibleTabIndex());
463 else
464 _UpdateTabVisibility();
468 void
469 TabContainerView::_UpdateTabVisibility()
471 float availableWidth = _AvailableWidthForTabs();
472 if (availableWidth < 0)
473 return;
474 float visibleTabsWidth = 0;
476 bool canScrollTabsLeft = fFirstVisibleTabIndex > 0;
477 bool canScrollTabsRight = false;
479 BGroupLayout* layout = GroupLayout();
480 int32 count = layout->CountItems() - 1;
481 for (int32 i = 0; i < count; i++) {
482 TabLayoutItem* item = dynamic_cast<TabLayoutItem*>(
483 layout->ItemAt(i));
484 if (i < fFirstVisibleTabIndex)
485 item->SetVisible(false);
486 else {
487 float itemWidth = item->MinSize().width;
488 bool visible = availableWidth >= visibleTabsWidth + itemWidth;
489 item->SetVisible(visible && !canScrollTabsRight);
490 visibleTabsWidth += itemWidth;
491 if (!visible)
492 canScrollTabsRight = true;
495 fController->UpdateTabScrollability(canScrollTabsLeft, canScrollTabsRight);
499 float
500 TabContainerView::_AvailableWidthForTabs() const
502 float width = Bounds().Width() - 10;
503 // TODO: Don't really know why -10 is needed above.
505 float left;
506 float right;
507 GroupLayout()->GetInsets(&left, NULL, &right, NULL);
508 width -= left + right;
510 return width;
514 void
515 TabContainerView::_SendFakeMouseMoved()
517 BPoint where;
518 uint32 buttons;
519 GetMouse(&where, &buttons, false);
520 if (Bounds().Contains(where))
521 _MouseMoved(where, B_INSIDE_VIEW, NULL);