xref: /haiku/src/servers/app/decorator/TabDecorator.cpp (revision 97f11716bfaa0f385eb0e28a52bf56a5023b9e99)
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