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
52static bool
53int_equal(float x, float y)
54{
55	return abs(x - y) <= 1;
56}
57
58
59static const float kBorderResizeLength = 22.0;
60static 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
68TabDecorator::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
83TabDecorator::~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*/
98void
99TabDecorator::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
117void
118TabDecorator::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
133Decorator::Region
134TabDecorator::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
177bool
178TabDecorator::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
205void
206TabDecorator::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
229void
230TabDecorator::_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
319void
320TabDecorator::_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
347void
348TabDecorator::_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
459void
460TabDecorator::_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
528void
529TabDecorator::_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
541void
542TabDecorator::_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
566void
567TabDecorator::_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
592void
593TabDecorator::_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
747void
748TabDecorator::_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
761bool
762TabDecorator::_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
827bool
828TabDecorator::_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
841bool
842TabDecorator::_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
856bool
857TabDecorator::_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
873bool
874TabDecorator::_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
900void
901TabDecorator::_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
934void
935TabDecorator::_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
947void
948TabDecorator::_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
965void
966TabDecorator::_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
986void
987TabDecorator::_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
1066float
1067TabDecorator::_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
1076float
1077TabDecorator::_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
1094void
1095TabDecorator::_CalculateTabsRegion()
1096{
1097	fTabsRegion.MakeEmpty();
1098	for (int32 i = 0; i < fTabList.CountItems(); i++)
1099		fTabsRegion.Include(fTabList.ItemAt(i)->tabRect);
1100}
1101