1 /*
2 * Copyright 2001-2020 Haiku, Inc.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 * Stephan Aßmus, superstippi@gmx.de
7 * DarkWyrm, bpmagic@columbus.rr.com
8 * Ryan Leavengood, leavengood@gmail.com
9 * Philippe Saint-Pierre, stpere@gmail.com
10 * John Scipione, jscipione@gmail.com
11 * Ingo Weinhold, ingo_weinhold@gmx.de
12 * Clemens Zeidler, haiku@clemens-zeidler.de
13 * Joseph Groover, looncraz@looncraz.net
14 * Jacob Secunda, secundaja@gmail.com
15 */
16
17
18 /*! Decorator made up of tabs */
19
20
21 #include "TabDecorator.h"
22
23 #include <algorithm>
24 #include <cmath>
25 #include <new>
26 #include <stdio.h>
27
28 #include <Autolock.h>
29 #include <Debug.h>
30 #include <GradientLinear.h>
31 #include <Rect.h>
32 #include <View.h>
33
34 #include <WindowPrivate.h>
35
36 #include "BitmapDrawingEngine.h"
37 #include "DesktopSettings.h"
38 #include "DrawingEngine.h"
39 #include "DrawState.h"
40 #include "FontManager.h"
41 #include "PatternHandler.h"
42
43
44 //#define DEBUG_DECORATOR
45 #ifdef DEBUG_DECORATOR
46 # define STRACE(x) printf x
47 #else
48 # define STRACE(x) ;
49 #endif
50
51
52 static bool
int_equal(float x,float y)53 int_equal(float x, float y)
54 {
55 return abs(x - y) <= 1;
56 }
57
58
59 static const float kBorderResizeLength = 22.0;
60 static const float kResizeKnobSize = 18.0;
61
62
63 // #pragma mark -
64
65
66 // TODO: get rid of DesktopSettings here, and introduce private accessor
67 // methods to the Decorator base class
TabDecorator(DesktopSettings & settings,BRect frame,Desktop * desktop)68 TabDecorator::TabDecorator(DesktopSettings& settings, BRect frame,
69 Desktop* desktop)
70 :
71 Decorator(settings, frame, desktop),
72 fOldMovingTab(0, 0, -1, -1)
73 {
74 STRACE(("TabDecorator:\n"));
75 STRACE(("\tFrame (%.1f,%.1f,%.1f,%.1f)\n",
76 frame.left, frame.top, frame.right, frame.bottom));
77
78 // TODO: If the decorator was created with a frame too small, it should
79 // resize itself!
80 }
81
82
~TabDecorator()83 TabDecorator::~TabDecorator()
84 {
85 STRACE(("TabDecorator: ~TabDecorator()\n"));
86 }
87
88
89 // #pragma mark - Public methods
90
91
92 /*! \brief Updates the decorator in the rectangular area \a updateRect.
93
94 Updates all areas which intersect the frame and tab.
95
96 \param updateRect The rectangular area to update.
97 */
98 void
Draw(BRect updateRect)99 TabDecorator::Draw(BRect updateRect)
100 {
101 STRACE(("TabDecorator::Draw(BRect "
102 "updateRect(l:%.1f, t:%.1f, r:%.1f, b:%.1f))\n",
103 updateRect.left, updateRect.top, updateRect.right, updateRect.bottom));
104
105 fDrawingEngine->SetDrawState(&fDrawState);
106
107 _DrawFrame(updateRect & fBorderRect);
108
109 if (IsOutlineResizing())
110 _DrawOutlineFrame(updateRect & fOutlineBorderRect);
111
112 _DrawTabs(updateRect & fTitleBarRect);
113 }
114
115
116 //! Forces a complete decorator update
117 void
Draw()118 TabDecorator::Draw()
119 {
120 STRACE(("TabDecorator: Draw()"));
121
122 fDrawingEngine->SetDrawState(&fDrawState);
123
124 _DrawFrame(fBorderRect);
125
126 if (IsOutlineResizing())
127 _DrawOutlineFrame(fOutlineBorderRect);
128
129 _DrawTabs(fTitleBarRect);
130 }
131
132
133 Decorator::Region
RegionAt(BPoint where,int32 & tab) const134 TabDecorator::RegionAt(BPoint where, int32& tab) const
135 {
136 // Let the base class version identify hits of the buttons and the tab.
137 Region region = Decorator::RegionAt(where, tab);
138 if (region != REGION_NONE)
139 return region;
140
141 // check the resize corner
142 if (fTopTab->look == B_DOCUMENT_WINDOW_LOOK && fResizeRect.Contains(where))
143 return REGION_RIGHT_BOTTOM_CORNER;
144
145 // hit-test the borders
146 if (fLeftBorder.Contains(where))
147 return REGION_LEFT_BORDER;
148 if (fTopBorder.Contains(where))
149 return REGION_TOP_BORDER;
150
151 // Part of the bottom and right borders may be a resize-region, so we have
152 // to check explicitly, if it has been it.
153 if (fRightBorder.Contains(where))
154 region = REGION_RIGHT_BORDER;
155 else if (fBottomBorder.Contains(where))
156 region = REGION_BOTTOM_BORDER;
157 else
158 return REGION_NONE;
159
160 // check resize area
161 if ((fTopTab->flags & B_NOT_RESIZABLE) == 0
162 && (fTopTab->look == B_TITLED_WINDOW_LOOK
163 || fTopTab->look == B_FLOATING_WINDOW_LOOK
164 || fTopTab->look == B_MODAL_WINDOW_LOOK
165 || fTopTab->look == kLeftTitledWindowLook)) {
166 BRect resizeRect(BPoint(fBottomBorder.right - fBorderResizeLength,
167 fBottomBorder.bottom - fBorderResizeLength),
168 fBottomBorder.RightBottom());
169 if (resizeRect.Contains(where))
170 return REGION_RIGHT_BOTTOM_CORNER;
171 }
172
173 return region;
174 }
175
176
177 bool
SetRegionHighlight(Region region,uint8 highlight,BRegion * dirty,int32 tabIndex)178 TabDecorator::SetRegionHighlight(Region region, uint8 highlight,
179 BRegion* dirty, int32 tabIndex)
180 {
181 Decorator::Tab* tab
182 = static_cast<Decorator::Tab*>(_TabAt(tabIndex));
183 if (tab != NULL) {
184 tab->isHighlighted = highlight != 0;
185 // Invalidate the bitmap caches for the close/zoom button, when the
186 // highlight changes.
187 switch (region) {
188 case REGION_CLOSE_BUTTON:
189 if (highlight != RegionHighlight(region))
190 memset(&tab->closeBitmaps, 0, sizeof(tab->closeBitmaps));
191 break;
192 case REGION_ZOOM_BUTTON:
193 if (highlight != RegionHighlight(region))
194 memset(&tab->zoomBitmaps, 0, sizeof(tab->zoomBitmaps));
195 break;
196 default:
197 break;
198 }
199 }
200
201 return Decorator::SetRegionHighlight(region, highlight, dirty, tabIndex);
202 }
203
204
205 void
UpdateColors(DesktopSettings & settings)206 TabDecorator::UpdateColors(DesktopSettings& settings)
207 {
208 // Desktop is write locked, so be quick about it.
209 fFocusFrameColor = settings.UIColor(B_WINDOW_BORDER_COLOR);
210 fFocusTabColor = settings.UIColor(B_WINDOW_TAB_COLOR);
211 fFocusTabColorLight = tint_color(fFocusTabColor,
212 (B_LIGHTEN_MAX_TINT + B_LIGHTEN_2_TINT) / 2);
213 fFocusTabColorBevel = tint_color(fFocusTabColor, B_LIGHTEN_2_TINT);
214 fFocusTabColorShadow = tint_color(fFocusTabColor,
215 (B_DARKEN_1_TINT + B_NO_TINT) / 2);
216 fFocusTextColor = settings.UIColor(B_WINDOW_TEXT_COLOR);
217
218 fNonFocusFrameColor = settings.UIColor(B_WINDOW_INACTIVE_BORDER_COLOR);
219 fNonFocusTabColor = settings.UIColor(B_WINDOW_INACTIVE_TAB_COLOR);
220 fNonFocusTabColorLight = tint_color(fNonFocusTabColor,
221 (B_LIGHTEN_MAX_TINT + B_LIGHTEN_2_TINT) / 2);
222 fNonFocusTabColorBevel = tint_color(fNonFocusTabColor, B_LIGHTEN_2_TINT);
223 fNonFocusTabColorShadow = tint_color(fNonFocusTabColor,
224 (B_DARKEN_1_TINT + B_NO_TINT) / 2);
225 fNonFocusTextColor = settings.UIColor(B_WINDOW_INACTIVE_TEXT_COLOR);
226 }
227
228
229 void
_DoLayout()230 TabDecorator::_DoLayout()
231 {
232 STRACE(("TabDecorator: Do Layout\n"));
233 // Here we determine the size of every rectangle that we use
234 // internally when we are given the size of the client rectangle.
235
236 bool hasTab = false;
237
238 // TODO: Put this computation somewhere more central!
239 const float scaleFactor = max_c(fDrawState.Font().Size() / 12.0f, 1.0f);
240
241 switch ((int)fTopTab->look) {
242 case B_MODAL_WINDOW_LOOK:
243 fBorderWidth = 5;
244 break;
245
246 case B_TITLED_WINDOW_LOOK:
247 case B_DOCUMENT_WINDOW_LOOK:
248 hasTab = true;
249 fBorderWidth = 5;
250 break;
251 case B_FLOATING_WINDOW_LOOK:
252 case kLeftTitledWindowLook:
253 hasTab = true;
254 fBorderWidth = 3;
255 break;
256
257 case B_BORDERED_WINDOW_LOOK:
258 fBorderWidth = 1;
259 break;
260
261 default:
262 fBorderWidth = 0;
263 }
264
265 fBorderWidth = int32(fBorderWidth * scaleFactor);
266 fResizeKnobSize = kResizeKnobSize * scaleFactor;
267 fBorderResizeLength = kBorderResizeLength * scaleFactor;
268
269 // calculate left/top/right/bottom borders
270 if (fBorderWidth > 0) {
271 // NOTE: no overlapping, the left and right border rects
272 // don't include the corners!
273 fLeftBorder.Set(fFrame.left - fBorderWidth, fFrame.top,
274 fFrame.left - 1, fFrame.bottom);
275
276 fRightBorder.Set(fFrame.right + 1, fFrame.top ,
277 fFrame.right + fBorderWidth, fFrame.bottom);
278
279 fTopBorder.Set(fFrame.left - fBorderWidth, fFrame.top - fBorderWidth,
280 fFrame.right + fBorderWidth, fFrame.top - 1);
281
282 fBottomBorder.Set(fFrame.left - fBorderWidth, fFrame.bottom + 1,
283 fFrame.right + fBorderWidth, fFrame.bottom + fBorderWidth);
284 } else {
285 // no border
286 fLeftBorder.Set(0.0, 0.0, -1.0, -1.0);
287 fRightBorder.Set(0.0, 0.0, -1.0, -1.0);
288 fTopBorder.Set(0.0, 0.0, -1.0, -1.0);
289 fBottomBorder.Set(0.0, 0.0, -1.0, -1.0);
290 }
291
292 fBorderRect = BRect(fTopBorder.LeftTop(), fBottomBorder.RightBottom());
293
294 // calculate resize rect
295 if (fBorderWidth > 1) {
296 fResizeRect.Set(fBottomBorder.right - fResizeKnobSize,
297 fBottomBorder.bottom - fResizeKnobSize, fBottomBorder.right,
298 fBottomBorder.bottom);
299 } else {
300 // no border or one pixel border (menus and such)
301 fResizeRect.Set(0, 0, -1, -1);
302 }
303
304 if (hasTab) {
305 _DoTabLayout();
306 return;
307 } else {
308 // no tab
309 for (int32 i = 0; i < fTabList.CountItems(); i++) {
310 Decorator::Tab* tab = fTabList.ItemAt(i);
311 tab->tabRect.Set(0.0, 0.0, -1.0, -1.0);
312 }
313 fTabsRegion.MakeEmpty();
314 fTitleBarRect.Set(0.0, 0.0, -1.0, -1.0);
315 }
316 }
317
318
319 void
_DoOutlineLayout()320 TabDecorator::_DoOutlineLayout()
321 {
322 fOutlineBorderWidth = 1;
323
324 // calculate left/top/right/bottom outline borders
325 // NOTE: no overlapping, the left and right border rects
326 // don't include the corners!
327 fLeftOutlineBorder.Set(fFrame.left - fOutlineBorderWidth, fFrame.top,
328 fFrame.left - 1, fFrame.bottom);
329
330 fRightOutlineBorder.Set(fFrame.right + 1, fFrame.top ,
331 fFrame.right + fOutlineBorderWidth, fFrame.bottom);
332
333 fTopOutlineBorder.Set(fFrame.left - fOutlineBorderWidth,
334 fFrame.top - fOutlineBorderWidth,
335 fFrame.right + fOutlineBorderWidth, fFrame.top - 1);
336
337 fBottomOutlineBorder.Set(fFrame.left - fOutlineBorderWidth,
338 fFrame.bottom + 1,
339 fFrame.right + fOutlineBorderWidth,
340 fFrame.bottom + fOutlineBorderWidth);
341
342 fOutlineBorderRect = BRect(fTopOutlineBorder.LeftTop(),
343 fBottomOutlineBorder.RightBottom());
344 }
345
346
347 void
_DoTabLayout()348 TabDecorator::_DoTabLayout()
349 {
350 float tabOffset = 0;
351 if (fTabList.CountItems() == 1) {
352 float tabSize;
353 tabOffset = _SingleTabOffsetAndSize(tabSize);
354 }
355
356 float sumTabWidth = 0;
357 // calculate our tab rect
358 for (int32 i = 0; i < fTabList.CountItems(); i++) {
359 Decorator::Tab* tab = _TabAt(i);
360
361 BRect& tabRect = tab->tabRect;
362 // distance from one item of the tab bar to another.
363 // In this case the text and close/zoom rects
364 tab->textOffset = _DefaultTextOffset();
365
366 font_height fontHeight;
367 fDrawState.Font().GetHeight(fontHeight);
368
369 if (tab->look != kLeftTitledWindowLook) {
370 const float spacing = fBorderWidth * 1.4f;
371 tabRect.Set(fFrame.left - fBorderWidth,
372 fFrame.top - fBorderWidth
373 - ceilf(fontHeight.ascent + fontHeight.descent + spacing),
374 ((fFrame.right - fFrame.left) < (spacing * 5) ?
375 fFrame.left + (spacing * 5) : fFrame.right) + fBorderWidth,
376 fFrame.top - fBorderWidth);
377 } else {
378 tabRect.Set(fFrame.left - fBorderWidth
379 - ceilf(fontHeight.ascent + fontHeight.descent + fBorderWidth),
380 fFrame.top - fBorderWidth, fFrame.left - fBorderWidth,
381 fFrame.bottom + fBorderWidth);
382 }
383
384 // format tab rect for a floating window - make the rect smaller
385 if (tab->look == B_FLOATING_WINDOW_LOOK) {
386 tabRect.InsetBy(0, 2);
387 tabRect.OffsetBy(0, 2);
388 }
389
390 float offset;
391 float size;
392 float inset;
393 _GetButtonSizeAndOffset(tabRect, &offset, &size, &inset);
394
395 // tab->minTabSize contains just the room for the buttons
396 tab->minTabSize = inset * 2 + tab->textOffset;
397 if ((tab->flags & B_NOT_CLOSABLE) == 0)
398 tab->minTabSize += offset + size;
399 if ((tab->flags & B_NOT_ZOOMABLE) == 0)
400 tab->minTabSize += offset + size;
401
402 // tab->maxTabSize contains tab->minTabSize + the width required for the
403 // title
404 tab->maxTabSize = fDrawingEngine
405 ? ceilf(fDrawingEngine->StringWidth(Title(tab), strlen(Title(tab)),
406 fDrawState.Font())) : 0.0;
407 if (tab->maxTabSize > 0.0)
408 tab->maxTabSize += tab->textOffset;
409 tab->maxTabSize += tab->minTabSize;
410
411 float tabSize = (tab->look != kLeftTitledWindowLook
412 ? fFrame.Width() : fFrame.Height()) + fBorderWidth * 2;
413 if (tabSize < tab->minTabSize)
414 tabSize = tab->minTabSize;
415 if (tabSize > tab->maxTabSize)
416 tabSize = tab->maxTabSize;
417
418 // layout buttons and truncate text
419 if (tab->look != kLeftTitledWindowLook)
420 tabRect.right = tabRect.left + tabSize;
421 else
422 tabRect.bottom = tabRect.top + tabSize;
423
424 // make sure fTabOffset is within limits and apply it to
425 // the tabRect
426 tab->tabOffset = (uint32)tabOffset;
427 if (tab->tabLocation != 0.0 && fTabList.CountItems() == 1
428 && tab->tabOffset > (fRightBorder.right - fLeftBorder.left
429 - tabRect.Width())) {
430 tab->tabOffset = uint32(fRightBorder.right - fLeftBorder.left
431 - tabRect.Width());
432 }
433 tabRect.OffsetBy(tab->tabOffset, 0);
434 tabOffset += tabRect.Width();
435
436 sumTabWidth += tabRect.Width();
437 }
438
439 float windowWidth = fFrame.Width() + 2 * fBorderWidth;
440 if (CountTabs() > 1 && sumTabWidth > windowWidth)
441 _DistributeTabSize(sumTabWidth - windowWidth);
442
443 // finally, layout the buttons and text within the tab rect
444 for (int32 i = 0; i < fTabList.CountItems(); i++) {
445 Decorator::Tab* tab = fTabList.ItemAt(i);
446
447 if (i == 0)
448 fTitleBarRect = tab->tabRect;
449 else
450 fTitleBarRect = fTitleBarRect | tab->tabRect;
451
452 _LayoutTabItems(tab, tab->tabRect);
453 }
454
455 fTabsRegion = fTitleBarRect;
456 }
457
458
459 void
_DistributeTabSize(float delta)460 TabDecorator::_DistributeTabSize(float delta)
461 {
462 int32 tabCount = fTabList.CountItems();
463 ASSERT(tabCount > 1);
464
465 float maxTabSize = 0;
466 float secMaxTabSize = 0;
467 int32 nTabsWithMaxSize = 0;
468 for (int32 i = 0; i < tabCount; i++) {
469 Decorator::Tab* tab = fTabList.ItemAt(i);
470 if (tab == NULL)
471 continue;
472
473 float tabWidth = tab->tabRect.Width();
474 if (int_equal(maxTabSize, tabWidth)) {
475 nTabsWithMaxSize++;
476 continue;
477 }
478 if (maxTabSize < tabWidth) {
479 secMaxTabSize = maxTabSize;
480 maxTabSize = tabWidth;
481 nTabsWithMaxSize = 1;
482 } else if (secMaxTabSize <= tabWidth)
483 secMaxTabSize = tabWidth;
484 }
485
486 float minus = ceilf(std::min(maxTabSize - secMaxTabSize, delta));
487 if (minus < 1.0)
488 return;
489 delta -= minus;
490 minus /= nTabsWithMaxSize;
491
492 Decorator::Tab* previousTab = NULL;
493 for (int32 i = 0; i < tabCount; i++) {
494 Decorator::Tab* tab = fTabList.ItemAt(i);
495 if (tab == NULL)
496 continue;
497
498 if (int_equal(maxTabSize, tab->tabRect.Width()))
499 tab->tabRect.right -= minus;
500
501 if (previousTab != NULL) {
502 float offsetX = previousTab->tabRect.right - tab->tabRect.left;
503 tab->tabRect.OffsetBy(offsetX, 0);
504 }
505
506 previousTab = tab;
507 }
508
509 if (delta > 0) {
510 _DistributeTabSize(delta);
511 return;
512 }
513
514 // done
515 if (previousTab != NULL)
516 previousTab->tabRect.right = floorf(fFrame.right + fBorderWidth);
517
518 for (int32 i = 0; i < tabCount; i++) {
519 Decorator::Tab* tab = fTabList.ItemAt(i);
520 if (tab == NULL)
521 continue;
522
523 tab->tabOffset = uint32(tab->tabRect.left - fLeftBorder.left);
524 }
525 }
526
527
528 void
_DrawOutlineFrame(BRect rect)529 TabDecorator::_DrawOutlineFrame(BRect rect)
530 {
531 drawing_mode oldMode;
532
533 fDrawingEngine->SetDrawingMode(B_OP_ALPHA, oldMode);
534 fDrawingEngine->SetPattern(B_MIXED_COLORS);
535 fDrawingEngine->StrokeRect(rect);
536
537 fDrawingEngine->SetDrawingMode(oldMode);
538 }
539
540
541 void
_SetTitle(Decorator::Tab * tab,const char * string,BRegion * updateRegion)542 TabDecorator::_SetTitle(Decorator::Tab* tab, const char* string,
543 BRegion* updateRegion)
544 {
545 // TODO: we could be much smarter about the update region
546
547 BRect rect = TabRect((int32) 0) | TabRect(CountTabs() - 1);
548 // Get a rect of all the tabs
549
550 _DoLayout();
551 _DoOutlineLayout();
552
553 if (updateRegion == NULL)
554 return;
555
556 rect = rect | TabRect(CountTabs() - 1);
557 // Update the rect to guarantee it updates all the tabs
558
559 rect.bottom++;
560 // the border will look differently when the title is adjacent
561
562 updateRegion->Include(rect);
563 }
564
565
566 void
_MoveBy(BPoint offset)567 TabDecorator::_MoveBy(BPoint offset)
568 {
569 STRACE(("TabDecorator: Move By (%.1f, %.1f)\n", offset.x, offset.y));
570
571 // Move all internal rectangles the appropriate amount
572 for (int32 i = 0; i < fTabList.CountItems(); i++) {
573 Decorator::Tab* tab = fTabList.ItemAt(i);
574 tab->zoomRect.OffsetBy(offset);
575 tab->closeRect.OffsetBy(offset);
576 tab->tabRect.OffsetBy(offset);
577 }
578
579 fFrame.OffsetBy(offset);
580 fTitleBarRect.OffsetBy(offset);
581 fTabsRegion.OffsetBy(offset);
582 fResizeRect.OffsetBy(offset);
583 fBorderRect.OffsetBy(offset);
584
585 fLeftBorder.OffsetBy(offset);
586 fRightBorder.OffsetBy(offset);
587 fTopBorder.OffsetBy(offset);
588 fBottomBorder.OffsetBy(offset);
589 }
590
591
592 void
_ResizeBy(BPoint offset,BRegion * dirty)593 TabDecorator::_ResizeBy(BPoint offset, BRegion* dirty)
594 {
595 STRACE(("TabDecorator: Resize By (%.1f, %.1f)\n", offset.x, offset.y));
596
597 // Move all internal rectangles the appropriate amount
598 fFrame.right += offset.x;
599 fFrame.bottom += offset.y;
600
601 // Handle invalidation of resize rect
602 if (dirty != NULL && !(fTopTab->flags & B_NOT_RESIZABLE)) {
603 BRect realResizeRect;
604 switch ((int)fTopTab->look) {
605 case B_DOCUMENT_WINDOW_LOOK:
606 realResizeRect = fResizeRect;
607 // Resize rect at old location
608 dirty->Include(realResizeRect);
609 realResizeRect.OffsetBy(offset);
610 // Resize rect at new location
611 dirty->Include(realResizeRect);
612 break;
613
614 case B_TITLED_WINDOW_LOOK:
615 case B_FLOATING_WINDOW_LOOK:
616 case B_MODAL_WINDOW_LOOK:
617 case kLeftTitledWindowLook:
618 // The bottom border resize line
619 realResizeRect.Set(fRightBorder.right - fBorderResizeLength,
620 fBottomBorder.top,
621 fRightBorder.right - fBorderResizeLength,
622 fBottomBorder.bottom - 1);
623 // Old location
624 dirty->Include(realResizeRect);
625 realResizeRect.OffsetBy(offset);
626 // New location
627 dirty->Include(realResizeRect);
628
629 // The right border resize line
630 realResizeRect.Set(fRightBorder.left,
631 fBottomBorder.bottom - fBorderResizeLength,
632 fRightBorder.right - 1,
633 fBottomBorder.bottom - fBorderResizeLength);
634 // Old location
635 dirty->Include(realResizeRect);
636 realResizeRect.OffsetBy(offset);
637 // New location
638 dirty->Include(realResizeRect);
639 break;
640
641 default:
642 break;
643 }
644 }
645
646 fResizeRect.OffsetBy(offset);
647
648 fBorderRect.right += offset.x;
649 fBorderRect.bottom += offset.y;
650
651 fLeftBorder.bottom += offset.y;
652 fTopBorder.right += offset.x;
653
654 fRightBorder.OffsetBy(offset.x, 0.0);
655 fRightBorder.bottom += offset.y;
656
657 fBottomBorder.OffsetBy(0.0, offset.y);
658 fBottomBorder.right += offset.x;
659
660 if (dirty) {
661 if (offset.x > 0.0) {
662 BRect t(fRightBorder.left - offset.x, fTopBorder.top,
663 fRightBorder.right, fTopBorder.bottom);
664 dirty->Include(t);
665 t.Set(fRightBorder.left - offset.x, fBottomBorder.top,
666 fRightBorder.right, fBottomBorder.bottom);
667 dirty->Include(t);
668 dirty->Include(fRightBorder);
669 } else if (offset.x < 0.0) {
670 dirty->Include(BRect(fRightBorder.left, fTopBorder.top,
671 fRightBorder.right, fBottomBorder.bottom));
672 }
673 if (offset.y > 0.0) {
674 BRect t(fLeftBorder.left, fLeftBorder.bottom - offset.y,
675 fLeftBorder.right, fLeftBorder.bottom);
676 dirty->Include(t);
677 t.Set(fRightBorder.left, fRightBorder.bottom - offset.y,
678 fRightBorder.right, fRightBorder.bottom);
679 dirty->Include(t);
680 dirty->Include(fBottomBorder);
681 } else if (offset.y < 0.0) {
682 dirty->Include(fBottomBorder);
683 }
684 }
685
686 // resize tab and layout tab items
687 if (fTitleBarRect.IsValid()) {
688 if (fTabList.CountItems() > 1) {
689 _DoTabLayout();
690 if (dirty != NULL)
691 dirty->Include(fTitleBarRect);
692 return;
693 }
694
695 Decorator::Tab* tab = _TabAt(0);
696 BRect& tabRect = tab->tabRect;
697 BRect oldTabRect(tabRect);
698
699 float tabSize;
700 float tabOffset = _SingleTabOffsetAndSize(tabSize);
701
702 float delta = tabOffset - tab->tabOffset;
703 tab->tabOffset = (uint32)tabOffset;
704 if (fTopTab->look != kLeftTitledWindowLook)
705 tabRect.OffsetBy(delta, 0.0);
706 else
707 tabRect.OffsetBy(0.0, delta);
708
709 if (tabSize < tab->minTabSize)
710 tabSize = tab->minTabSize;
711 if (tabSize > tab->maxTabSize)
712 tabSize = tab->maxTabSize;
713
714 if (fTopTab->look != kLeftTitledWindowLook
715 && tabSize != tabRect.Width()) {
716 tabRect.right = tabRect.left + tabSize;
717 } else if (fTopTab->look == kLeftTitledWindowLook
718 && tabSize != tabRect.Height()) {
719 tabRect.bottom = tabRect.top + tabSize;
720 }
721
722 if (oldTabRect != tabRect) {
723 _LayoutTabItems(tab, tabRect);
724
725 if (dirty) {
726 // NOTE: the tab rect becoming smaller only would
727 // handled be the Desktop anyways, so it is sufficient
728 // to include it into the dirty region in it's
729 // final state
730 BRect redraw(tabRect);
731 if (delta != 0.0) {
732 redraw = redraw | oldTabRect;
733 if (fTopTab->look != kLeftTitledWindowLook)
734 redraw.bottom++;
735 else
736 redraw.right++;
737 }
738 dirty->Include(redraw);
739 }
740 }
741 fTitleBarRect = tabRect;
742 fTabsRegion = fTitleBarRect;
743 }
744 }
745
746
747 void
_SetFocus(Decorator::Tab * tab)748 TabDecorator::_SetFocus(Decorator::Tab* tab)
749 {
750 Decorator::Tab* decoratorTab = static_cast<Decorator::Tab*>(tab);
751
752 decoratorTab->buttonFocus = IsFocus(tab)
753 || ((decoratorTab->look == B_FLOATING_WINDOW_LOOK
754 || decoratorTab->look == kLeftTitledWindowLook)
755 && (decoratorTab->flags & B_AVOID_FOCUS) != 0);
756 if (CountTabs() > 1)
757 _LayoutTabItems(decoratorTab, decoratorTab->tabRect);
758 }
759
760
761 bool
_SetTabLocation(Decorator::Tab * _tab,float location,bool isShifting,BRegion * updateRegion)762 TabDecorator::_SetTabLocation(Decorator::Tab* _tab, float location,
763 bool isShifting, BRegion* updateRegion)
764 {
765 STRACE(("TabDecorator: Set Tab Location(%.1f)\n", location));
766
767 if (CountTabs() > 1) {
768 if (isShifting == false) {
769 _DoTabLayout();
770 if (updateRegion != NULL)
771 updateRegion->Include(fTitleBarRect);
772
773 fOldMovingTab = BRect(0, 0, -1, -1);
774 return true;
775 } else {
776 if (fOldMovingTab.IsValid() == false)
777 fOldMovingTab = _tab->tabRect;
778 }
779 }
780
781 Decorator::Tab* tab = static_cast<Decorator::Tab*>(_tab);
782 BRect& tabRect = tab->tabRect;
783 if (tabRect.IsValid() == false)
784 return false;
785
786 if (location < 0)
787 location = 0;
788
789 float maxLocation
790 = fRightBorder.right - fLeftBorder.left - tabRect.Width();
791 if (CountTabs() > 1)
792 maxLocation = fTitleBarRect.right - fLeftBorder.left - tabRect.Width();
793
794 if (location > maxLocation)
795 location = maxLocation;
796
797 float delta = floor(location - tab->tabOffset);
798 if (delta == 0.0)
799 return false;
800
801 // redraw old rect (1 pixel on the border must also be updated)
802 BRect rect(tabRect);
803 rect.bottom++;
804 if (updateRegion != NULL)
805 updateRegion->Include(rect);
806
807 tabRect.OffsetBy(delta, 0);
808 tab->tabOffset = (int32)location;
809 _LayoutTabItems(_tab, tabRect);
810 tab->tabLocation = maxLocation > 0.0 ? tab->tabOffset / maxLocation : 0.0;
811
812 if (fTabList.CountItems() == 1)
813 fTitleBarRect = tabRect;
814
815 _CalculateTabsRegion();
816
817 // redraw new rect as well
818 rect = tabRect;
819 rect.bottom++;
820 if (updateRegion != NULL)
821 updateRegion->Include(rect);
822
823 return true;
824 }
825
826
827 bool
_SetSettings(const BMessage & settings,BRegion * updateRegion)828 TabDecorator::_SetSettings(const BMessage& settings, BRegion* updateRegion)
829 {
830 float tabLocation;
831 bool modified = false;
832 for (int32 i = 0; i < fTabList.CountItems(); i++) {
833 if (settings.FindFloat("tab location", i, &tabLocation) != B_OK)
834 return false;
835 modified |= SetTabLocation(i, tabLocation, updateRegion);
836 }
837 return modified;
838 }
839
840
841 bool
_AddTab(DesktopSettings & settings,int32 index,BRegion * updateRegion)842 TabDecorator::_AddTab(DesktopSettings& settings, int32 index,
843 BRegion* updateRegion)
844 {
845 _UpdateFont(settings);
846
847 _DoLayout();
848 _DoOutlineLayout();
849
850 if (updateRegion != NULL)
851 updateRegion->Include(fTitleBarRect);
852 return true;
853 }
854
855
856 bool
_RemoveTab(int32 index,BRegion * updateRegion)857 TabDecorator::_RemoveTab(int32 index, BRegion* updateRegion)
858 {
859 BRect oldRect = TabRect(index) | TabRect(CountTabs() - 1);
860 // Get a rect of all the tabs to the right - they will all be moved
861
862 _DoLayout();
863 _DoOutlineLayout();
864
865 if (updateRegion != NULL) {
866 updateRegion->Include(oldRect);
867 updateRegion->Include(fTitleBarRect);
868 }
869 return true;
870 }
871
872
873 bool
_MoveTab(int32 from,int32 to,bool isMoving,BRegion * updateRegion)874 TabDecorator::_MoveTab(int32 from, int32 to, bool isMoving,
875 BRegion* updateRegion)
876 {
877 Decorator::Tab* toTab = _TabAt(to);
878 if (toTab == NULL)
879 return false;
880
881 if (from < to) {
882 fOldMovingTab.OffsetBy(toTab->tabRect.Width(), 0);
883 toTab->tabRect.OffsetBy(-fOldMovingTab.Width(), 0);
884 } else {
885 fOldMovingTab.OffsetBy(-toTab->tabRect.Width(), 0);
886 toTab->tabRect.OffsetBy(fOldMovingTab.Width(), 0);
887 }
888
889 toTab->tabOffset = uint32(toTab->tabRect.left - fLeftBorder.left);
890 _LayoutTabItems(toTab, toTab->tabRect);
891
892 _CalculateTabsRegion();
893
894 if (updateRegion != NULL)
895 updateRegion->Include(fTitleBarRect);
896 return true;
897 }
898
899
900 void
_GetFootprint(BRegion * region)901 TabDecorator::_GetFootprint(BRegion *region)
902 {
903 STRACE(("TabDecorator: GetFootprint\n"));
904
905 // This function calculates the decorator's footprint in coordinates
906 // relative to the view. This is most often used to set a Window
907 // object's visible region.
908
909 if (region == NULL)
910 return;
911
912 if (fTopTab->look == B_NO_BORDER_WINDOW_LOOK)
913 return;
914
915 region->Include(fTopBorder);
916 region->Include(fLeftBorder);
917 region->Include(fRightBorder);
918 region->Include(fBottomBorder);
919
920 if (fTopTab->look == B_BORDERED_WINDOW_LOOK)
921 return;
922
923 region->Include(&fTabsRegion);
924
925 if (fTopTab->look == B_DOCUMENT_WINDOW_LOOK) {
926 // include the rectangular resize knob on the bottom right
927 float knobSize = fResizeKnobSize - fBorderWidth;
928 region->Include(BRect(fFrame.right - knobSize, fFrame.bottom - knobSize,
929 fFrame.right, fFrame.bottom));
930 }
931 }
932
933
934 void
_DrawButtons(Decorator::Tab * tab,const BRect & invalid)935 TabDecorator::_DrawButtons(Decorator::Tab* tab, const BRect& invalid)
936 {
937 STRACE(("TabDecorator: _DrawButtons\n"));
938
939 // Draw the buttons if we're supposed to
940 if (!(tab->flags & B_NOT_CLOSABLE) && invalid.Intersects(tab->closeRect))
941 _DrawClose(tab, false, tab->closeRect);
942 if (!(tab->flags & B_NOT_ZOOMABLE) && invalid.Intersects(tab->zoomRect))
943 _DrawZoom(tab, false, tab->zoomRect);
944 }
945
946
947 void
_UpdateFont(DesktopSettings & settings)948 TabDecorator::_UpdateFont(DesktopSettings& settings)
949 {
950 ServerFont font;
951 if (fTopTab->look == B_FLOATING_WINDOW_LOOK
952 || fTopTab->look == kLeftTitledWindowLook) {
953 settings.GetDefaultPlainFont(font);
954 if (fTopTab->look == kLeftTitledWindowLook)
955 font.SetRotation(90.0f);
956 } else
957 settings.GetDefaultBoldFont(font);
958
959 font.SetFlags(B_FORCE_ANTIALIASING);
960 font.SetSpacing(B_STRING_SPACING);
961 fDrawState.SetFont(font);
962 }
963
964
965 void
_GetButtonSizeAndOffset(const BRect & tabRect,float * _offset,float * _size,float * _inset) const966 TabDecorator::_GetButtonSizeAndOffset(const BRect& tabRect, float* _offset,
967 float* _size, float* _inset) const
968 {
969 float tabSize = fTopTab->look == kLeftTitledWindowLook ?
970 tabRect.Width() : tabRect.Height();
971
972 bool smallTab = fTopTab->look == B_FLOATING_WINDOW_LOOK
973 || fTopTab->look == kLeftTitledWindowLook;
974
975 *_offset = smallTab ? floorf(fDrawState.Font().Size() / 2.6)
976 : floorf(fDrawState.Font().Size() / 2.3);
977 *_inset = smallTab ? floorf(fDrawState.Font().Size() / 5.0)
978 : floorf(fDrawState.Font().Size() / 6.0);
979
980 // "+ 2" so that the rects are centered within the solid area
981 // (without the 2 pixels for the top border)
982 *_size = tabSize - 2 * *_offset + *_inset;
983 }
984
985
986 void
_LayoutTabItems(Decorator::Tab * _tab,const BRect & tabRect)987 TabDecorator::_LayoutTabItems(Decorator::Tab* _tab, const BRect& tabRect)
988 {
989 Decorator::Tab* tab = static_cast<Decorator::Tab*>(_tab);
990
991 float offset;
992 float size;
993 float inset;
994 _GetButtonSizeAndOffset(tabRect, &offset, &size, &inset);
995
996 // default textOffset
997 tab->textOffset = _DefaultTextOffset();
998
999 BRect& closeRect = tab->closeRect;
1000 BRect& zoomRect = tab->zoomRect;
1001
1002 // calulate close rect based on the tab rectangle
1003 if (tab->look != kLeftTitledWindowLook) {
1004 closeRect.Set(tabRect.left + offset, tabRect.top + offset,
1005 tabRect.left + offset + size, tabRect.top + offset + size);
1006
1007 zoomRect.Set(tabRect.right - offset - size, tabRect.top + offset,
1008 tabRect.right - offset, tabRect.top + offset + size);
1009
1010 // hidden buttons have no width
1011 if ((tab->flags & B_NOT_CLOSABLE) != 0)
1012 closeRect.right = closeRect.left - offset;
1013 if ((tab->flags & B_NOT_ZOOMABLE) != 0)
1014 zoomRect.left = zoomRect.right + offset;
1015 } else {
1016 closeRect.Set(tabRect.left + offset, tabRect.top + offset,
1017 tabRect.left + offset + size, tabRect.top + offset + size);
1018
1019 zoomRect.Set(tabRect.left + offset, tabRect.bottom - offset - size,
1020 tabRect.left + size + offset, tabRect.bottom - offset);
1021
1022 // hidden buttons have no height
1023 if ((tab->flags & B_NOT_CLOSABLE) != 0)
1024 closeRect.bottom = closeRect.top - offset;
1025 if ((tab->flags & B_NOT_ZOOMABLE) != 0)
1026 zoomRect.top = zoomRect.bottom + offset;
1027 }
1028
1029 // calculate room for title
1030 // TODO: the +2 is there because the title often appeared
1031 // truncated for no apparent reason - OTOH the title does
1032 // also not appear perfectly in the middle
1033 if (tab->look != kLeftTitledWindowLook)
1034 size = (zoomRect.left - closeRect.right) - tab->textOffset * 2 + inset;
1035 else
1036 size = (zoomRect.top - closeRect.bottom) - tab->textOffset * 2 + inset;
1037
1038 bool stackMode = fTabList.CountItems() > 1;
1039 if (stackMode && IsFocus(tab) == false) {
1040 zoomRect.Set(0, 0, 0, 0);
1041 size = (tab->tabRect.right - closeRect.right) - tab->textOffset * 2
1042 + inset;
1043 }
1044 uint8 truncateMode = B_TRUNCATE_MIDDLE;
1045 if (stackMode) {
1046 if (tab->tabRect.Width() < 100)
1047 truncateMode = B_TRUNCATE_END;
1048 float titleWidth = fDrawState.Font().StringWidth(Title(tab),
1049 BString(Title(tab)).Length());
1050 if (size < titleWidth) {
1051 float oldTextOffset = tab->textOffset;
1052 tab->textOffset -= (titleWidth - size) / 2;
1053 const float kMinTextOffset = 5.;
1054 if (tab->textOffset < kMinTextOffset)
1055 tab->textOffset = kMinTextOffset;
1056 size += oldTextOffset * 2;
1057 size -= tab->textOffset * 2;
1058 }
1059 }
1060 tab->truncatedTitle = Title(tab);
1061 fDrawState.Font().TruncateString(&tab->truncatedTitle, truncateMode, size);
1062 tab->truncatedTitleLength = tab->truncatedTitle.Length();
1063 }
1064
1065
1066 float
_DefaultTextOffset() const1067 TabDecorator::_DefaultTextOffset() const
1068 {
1069 if (fTopTab->look == B_FLOATING_WINDOW_LOOK
1070 || fTopTab->look == kLeftTitledWindowLook)
1071 return int32(fBorderWidth * 3.4f);
1072 return int32(fBorderWidth * 3.6f);
1073 }
1074
1075
1076 float
_SingleTabOffsetAndSize(float & tabSize)1077 TabDecorator::_SingleTabOffsetAndSize(float& tabSize)
1078 {
1079 float maxLocation;
1080 if (fTopTab->look != kLeftTitledWindowLook) {
1081 tabSize = fRightBorder.right - fLeftBorder.left;
1082 } else {
1083 tabSize = fBottomBorder.bottom - fTopBorder.top;
1084 }
1085 Decorator::Tab* tab = _TabAt(0);
1086 maxLocation = tabSize - tab->maxTabSize;
1087 if (maxLocation < 0)
1088 maxLocation = 0;
1089
1090 return floorf(tab->tabLocation * maxLocation);
1091 }
1092
1093
1094 void
_CalculateTabsRegion()1095 TabDecorator::_CalculateTabsRegion()
1096 {
1097 fTabsRegion.MakeEmpty();
1098 for (int32 i = 0; i < fTabList.CountItems(); i++)
1099 fTabsRegion.Include(fTabList.ItemAt(i)->tabRect);
1100 }
1101