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