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 30static const float kLeftTabInset = 4; 31 32 33TabContainerView::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 50TabContainerView::~TabContainerView() 51{ 52} 53 54 55BSize 56TabContainerView::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 65void 66TabContainerView::MessageReceived(BMessage* message) 67{ 68 switch (message->what) { 69 default: 70 BGroupView::MessageReceived(message); 71 } 72} 73 74 75void 76TabContainerView::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 98void 99TabContainerView::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 133void 134TabContainerView::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 154void 155TabContainerView::MouseMoved(BPoint where, uint32 transit, 156 const BMessage* dragMessage) 157{ 158 _MouseMoved(where, transit, dragMessage); 159} 160 161 162void 163TabContainerView::DoLayout() 164{ 165 BGroupView::DoLayout(); 166 167 _ValidateTabVisibility(); 168 _SendFakeMouseMoved(); 169} 170 171void 172TabContainerView::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 185void 186TabContainerView::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 214TabView* 215TabContainerView::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 261TabView* 262TabContainerView::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 273int32 274TabContainerView::IndexOf(TabView* tab) const 275{ 276 if (tab == NULL || GroupLayout() == NULL) 277 return -1; 278 279 return GroupLayout()->IndexOfItem(tab->LayoutItem()); 280} 281 282 283void 284TabContainerView::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 296void 297TabContainerView::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 324void 325TabContainerView::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 336void 337TabContainerView::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 352int32 353TabContainerView::FirstVisibleTabIndex() const 354{ 355 return fFirstVisibleTabIndex; 356} 357 358 359int32 360TabContainerView::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 388bool 389TabContainerView::CanScrollLeft() const 390{ 391 return fFirstVisibleTabIndex < MaxFirstVisibleTabIndex(); 392} 393 394 395bool 396TabContainerView::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 412TabView* 413TabContainerView::_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 432void 433TabContainerView::_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 461void 462TabContainerView::_ValidateTabVisibility() 463{ 464 if (fFirstVisibleTabIndex > MaxFirstVisibleTabIndex()) 465 SetFirstVisibleTabIndex(MaxFirstVisibleTabIndex()); 466 else 467 _UpdateTabVisibility(); 468} 469 470 471void 472TabContainerView::_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 502float 503TabContainerView::_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 517void 518TabContainerView::_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