1/*
2 * Copyright 2005-2009, Haiku Inc.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Axel D��rfler, axeld@pinc-software.de
7 *		Stephan A��mus <superstippi@gmx.de>
8 */
9
10
11#include "WorkspacesView.h"
12
13#include "AppServer.h"
14#include "Decorator.h"
15#include "Desktop.h"
16#include "DrawingEngine.h"
17#include "DrawState.h"
18#include "InterfaceDefs.h"
19#include "ServerApp.h"
20#include "Window.h"
21#include "Workspace.h"
22
23#include <WindowPrivate.h>
24
25
26WorkspacesView::WorkspacesView(BRect frame, BPoint scrollingOffset,
27		const char* name, int32 token, uint32 resizeMode, uint32 flags)
28	:
29	View(frame, scrollingOffset, name, token, resizeMode, flags),
30	fSelectedWindow(NULL),
31	fSelectedWorkspace(-1),
32	fHasMoved(false)
33{
34	fDrawState->SetLowColor((rgb_color){ 255, 255, 255, 255 });
35	fDrawState->SetHighColor((rgb_color){ 0, 0, 0, 255 });
36}
37
38
39WorkspacesView::~WorkspacesView()
40{
41}
42
43
44void
45WorkspacesView::AttachedToWindow(::Window* window)
46{
47	View::AttachedToWindow(window);
48
49	window->AddWorkspacesView();
50	window->Desktop()->AddWorkspacesView(this);
51}
52
53
54void
55WorkspacesView::DetachedFromWindow()
56{
57	fWindow->Desktop()->RemoveWorkspacesView(this);
58	fWindow->RemoveWorkspacesView();
59
60	View::DetachedFromWindow();
61}
62
63
64void
65WorkspacesView::_GetGrid(int32& columns, int32& rows)
66{
67	DesktopSettings settings(Window()->Desktop());
68
69	columns = settings.WorkspacesColumns();
70	rows = settings.WorkspacesRows();
71}
72
73
74/*!	\brief Returns the frame of the screen for the specified workspace.
75*/
76BRect
77WorkspacesView::_ScreenFrame(int32 i)
78{
79	return Window()->Desktop()->WorkspaceFrame(i);
80}
81
82
83/*!	\brief Returns the frame of the specified workspace within the
84		workspaces view.
85*/
86BRect
87WorkspacesView::_WorkspaceAt(int32 i)
88{
89	int32 columns, rows;
90	_GetGrid(columns, rows);
91
92	BRect frame = Bounds();
93	LocalToScreenTransform().Apply(&frame);
94
95	int32 width = frame.IntegerWidth() / columns;
96	int32 height = frame.IntegerHeight() / rows;
97
98	int32 column = i % columns;
99	int32 row = i / columns;
100
101	BRect rect(column * width, row * height, (column + 1) * width,
102		(row + 1) * height);
103
104	rect.OffsetBy(frame.LeftTop());
105
106	// make sure there is no gap anywhere
107	if (column == columns - 1)
108		rect.right = frame.right;
109	if (row == rows - 1)
110		rect.bottom = frame.bottom;
111
112	return rect;
113}
114
115
116/*!	\brief Returns the workspace frame and index of the workspace
117		under \a where.
118
119	If, for some reason, there is no workspace located under \where,
120	an empty rectangle is returned, and \a index is set to -1.
121*/
122BRect
123WorkspacesView::_WorkspaceAt(BPoint where, int32& index)
124{
125	int32 columns, rows;
126	_GetGrid(columns, rows);
127
128	for (index = columns * rows; index-- > 0;) {
129		BRect workspaceFrame = _WorkspaceAt(index);
130
131		if (workspaceFrame.Contains(where))
132			return workspaceFrame;
133	}
134
135	return BRect();
136}
137
138
139BRect
140WorkspacesView::_WindowFrame(const BRect& workspaceFrame,
141	const BRect& screenFrame, const BRect& windowFrame,
142	BPoint windowPosition)
143{
144	BRect frame = windowFrame;
145	frame.OffsetTo(windowPosition);
146
147	// scale down the rect
148	float factor = workspaceFrame.Width() / screenFrame.Width();
149	frame.left = frame.left * factor;
150	frame.right = frame.right * factor;
151
152	factor = workspaceFrame.Height() / screenFrame.Height();
153	frame.top = frame.top * factor;
154	frame.bottom = frame.bottom * factor;
155
156	// offset by the workspace fame position
157	// and snap to integer coordinates without distorting the size too much
158	frame.OffsetTo(rintf(frame.left + workspaceFrame.left),
159		rintf(frame.top + workspaceFrame.top));
160	frame.right = rintf(frame.right);
161	frame.bottom = rintf(frame.bottom);
162
163	return frame;
164}
165
166
167void
168WorkspacesView::_DrawWindow(DrawingEngine* drawingEngine,
169	const BRect& workspaceFrame, const BRect& screenFrame, ::Window* window,
170	BPoint windowPosition, BRegion& backgroundRegion, bool workspaceActive)
171{
172	if (window->Feel() == kDesktopWindowFeel || window->IsHidden())
173		return;
174
175	BPoint offset = window->Frame().LeftTop() - windowPosition;
176	BRect frame = _WindowFrame(workspaceFrame, screenFrame, window->Frame(),
177		windowPosition);
178	Decorator *decorator = window->Decorator();
179	BRect tabFrame(0, 0, 0, 0);
180	if (decorator != NULL)
181		tabFrame = decorator->TitleBarRect();
182
183	tabFrame = _WindowFrame(workspaceFrame, screenFrame,
184		tabFrame, tabFrame.LeftTop() - offset);
185	if (!workspaceFrame.Intersects(frame)
186		&& !workspaceFrame.Intersects(tabFrame))
187		return;
188
189	rgb_color activeTabColor = (rgb_color){ 255, 203, 0, 255 };
190	rgb_color inactiveTabColor = (rgb_color){ 232, 232, 232, 255 };
191	rgb_color navColor = (rgb_color){ 0, 0, 229, 255 };
192	rgb_color activeFrameColor = (rgb_color){ 180, 180, 180, 255 };
193	rgb_color inactiveFrameColor = (rgb_color){ 180, 180, 180, 255 };
194	if (decorator != NULL) {
195		activeTabColor = decorator->UIColor(B_WINDOW_TAB_COLOR);
196		inactiveTabColor = decorator->UIColor(B_WINDOW_INACTIVE_TAB_COLOR);
197		navColor = decorator->UIColor(B_NAVIGATION_BASE_COLOR);
198		activeFrameColor = decorator->UIColor(B_WINDOW_BORDER_COLOR);
199		inactiveFrameColor = decorator->UIColor(B_WINDOW_INACTIVE_BORDER_COLOR);
200	}
201
202	rgb_color white = (rgb_color){ 255, 255, 255, 255 };
203
204	rgb_color tabColor = inactiveTabColor;
205	rgb_color frameColor = inactiveFrameColor;
206	if (window->IsFocus()) {
207		tabColor = activeTabColor;
208		frameColor = activeFrameColor;
209	}
210
211	if (!workspaceActive) {
212		_DarkenColor(tabColor);
213		_DarkenColor(frameColor);
214		_DarkenColor(white);
215	}
216
217	if (tabFrame.Height() > 0 && tabFrame.Width() > 0) {
218		float width = tabFrame.Width();
219		if (tabFrame.left < frame.left) {
220			// Shift the tab right
221			tabFrame.left = frame.left;
222			tabFrame.right = tabFrame.left + width;
223		}
224		if (tabFrame.right > frame.right) {
225			// Shift the tab left
226			tabFrame.right = frame.right;
227			tabFrame.left = tabFrame.right - width;
228		}
229
230		if (tabFrame.bottom >= tabFrame.top) {
231			// Shift the tab up
232			float tabHeight = tabFrame.Height();
233			tabFrame.bottom = frame.top - 1;
234			tabFrame.top = tabFrame.bottom - tabHeight;
235		}
236
237		tabFrame = tabFrame & workspaceFrame;
238
239		if (decorator != NULL && tabFrame.IsValid()) {
240			drawingEngine->FillRect(tabFrame, tabColor);
241			backgroundRegion.Exclude(tabFrame);
242		}
243	}
244
245	drawingEngine->StrokeRect(frame, frameColor);
246
247	BRect fillFrame = frame.InsetByCopy(1, 1) & workspaceFrame;
248	frame = frame & workspaceFrame;
249	if (!frame.IsValid())
250		return;
251
252	// fill the window itself
253	drawingEngine->FillRect(fillFrame, white);
254
255	// draw title
256
257	// TODO: the mini-window functionality should probably be moved into the
258	// Window class, so that it has only to be recalculated on demand. With
259	// double buffered windows, this would also open up the door to have a
260	// more detailed preview.
261	BString title(window->Title());
262
263	const ServerFont& font = fDrawState->Font();
264
265	font.TruncateString(&title, B_TRUNCATE_END, fillFrame.Width() - 4);
266	font_height fontHeight;
267	font.GetHeight(fontHeight);
268	float height = ceilf(fontHeight.ascent) + ceilf(fontHeight.descent);
269	if (title.Length() > 0 && height < frame.Height() - 2) {
270		rgb_color textColor = tint_color(white, B_DARKEN_4_TINT);
271		drawingEngine->SetHighColor(textColor);
272		drawingEngine->SetLowColor(white);
273
274		float width = font.StringWidth(title.String(), title.Length());
275
276		BPoint textOffset;
277		textOffset.x = rintf(frame.left + (frame.Width() - width) / 2);
278		textOffset.y = rintf(frame.top + (frame.Height() - height) / 2
279			+ fontHeight.ascent);
280		drawingEngine->DrawString(title.String(), title.Length(), textOffset);
281	}
282
283	// prevent the next window down from drawing over this window
284	backgroundRegion.Exclude(frame);
285}
286
287
288void
289WorkspacesView::_DrawWorkspace(DrawingEngine* drawingEngine,
290	BRegion& redraw, int32 index)
291{
292	BRect rect = _WorkspaceAt(index);
293
294	Workspace workspace(*Window()->Desktop(), index, true);
295	bool workspaceActive = workspace.IsCurrent();
296	if (workspaceActive) {
297		// draw active frame
298		rgb_color black = (rgb_color){ 0, 0, 0, 255 };
299		drawingEngine->StrokeRect(rect, black);
300	} else if (index == fSelectedWorkspace) {
301		rgb_color gray = (rgb_color){ 80, 80, 80, 255 };
302		drawingEngine->StrokeRect(rect, gray);
303	}
304
305	rect.InsetBy(1, 1);
306
307	rgb_color color = workspace.Color();
308	if (!workspaceActive)
309		_DarkenColor(color);
310
311	// draw windows
312
313	BRegion backgroundRegion = redraw;
314
315	// TODO: would be nice to get the real update region here
316
317	BRect screenFrame = _ScreenFrame(index);
318
319	BRegion workspaceRegion(rect);
320	backgroundRegion.IntersectWith(&workspaceRegion);
321	drawingEngine->ConstrainClippingRegion(&backgroundRegion);
322
323	ServerFont font = fDrawState->Font();
324	font.SetSize(fWindow->ServerWindow()->App()->PlainFont().Size());
325	float reducedSize = ceilf(max_c(8.0f,
326		min_c(Frame().Height(), Frame().Width()) / 15));
327	if (font.Size() > reducedSize)
328		font.SetSize(reducedSize);
329	fDrawState->SetFont(font);
330	drawingEngine->SetFont(font);
331
332	// We draw from top down and cut the window out of the clipping region
333	// which reduces the flickering
334	::Window* window;
335	BPoint leftTop;
336	while (workspace.GetPreviousWindow(window, leftTop) == B_OK) {
337		_DrawWindow(drawingEngine, rect, screenFrame, window,
338			leftTop, backgroundRegion, workspaceActive);
339	}
340
341	// draw background
342	drawingEngine->FillRect(rect, color);
343
344	drawingEngine->ConstrainClippingRegion(&redraw);
345}
346
347
348void
349WorkspacesView::_DarkenColor(rgb_color& color) const
350{
351	color = tint_color(color, B_DARKEN_2_TINT);
352}
353
354
355void
356WorkspacesView::_Invalidate() const
357{
358	BRect frame = Bounds();
359	LocalToScreenTransform().Apply(&frame);
360
361	BRegion region(frame), expose;
362	Window()->MarkContentDirty(region, expose);
363}
364
365
366void
367WorkspacesView::Draw(DrawingEngine* drawingEngine, const BRegion* effectiveClipping,
368	const BRegion* windowContentClipping, bool deep)
369{
370	// we can only draw within our own area
371	BRegion redraw(ScreenAndUserClipping(windowContentClipping));
372	// add the current clipping
373	redraw.IntersectWith(effectiveClipping);
374
375	int32 columns, rows;
376	_GetGrid(columns, rows);
377
378	// draw grid
379
380	// make sure the grid around the active workspace is not drawn
381	// to reduce flicker
382	BRect activeRect = _WorkspaceAt(Window()->Desktop()->CurrentWorkspace());
383	BRegion gridRegion(redraw);
384	gridRegion.Exclude(activeRect);
385	drawingEngine->ConstrainClippingRegion(&gridRegion);
386
387	BRect frame = Bounds();
388	LocalToScreenTransform().Apply(&frame);
389
390	// horizontal lines
391
392	drawingEngine->StrokeLine(BPoint(frame.left, frame.top),
393		BPoint(frame.right, frame.top), ViewColor());
394
395	for (int32 row = 0; row < rows; row++) {
396		BRect rect = _WorkspaceAt(row * columns);
397		drawingEngine->StrokeLine(BPoint(frame.left, rect.bottom),
398			BPoint(frame.right, rect.bottom), ViewColor());
399	}
400
401	// vertical lines
402
403	drawingEngine->StrokeLine(BPoint(frame.left, frame.top),
404		BPoint(frame.left, frame.bottom), ViewColor());
405
406	for (int32 column = 0; column < columns; column++) {
407		BRect rect = _WorkspaceAt(column);
408		drawingEngine->StrokeLine(BPoint(rect.right, frame.top),
409			BPoint(rect.right, frame.bottom), ViewColor());
410	}
411
412	drawingEngine->ConstrainClippingRegion(&redraw);
413
414	// draw workspaces
415
416	for (int32 i = rows * columns; i-- > 0;) {
417		_DrawWorkspace(drawingEngine, redraw, i);
418	}
419	fWindow->ServerWindow()->ResyncDrawState();
420}
421
422
423void
424WorkspacesView::MouseDown(BMessage* message, BPoint where)
425{
426	// reset tracking variables
427	fSelectedWorkspace = -1;
428	fSelectedWindow = NULL;
429	fHasMoved = false;
430
431	// check if the correct mouse button is pressed
432	int32 buttons;
433	if (message->FindInt32("buttons", &buttons) != B_OK
434		|| (buttons & B_PRIMARY_MOUSE_BUTTON) == 0)
435		return;
436
437	int32 index;
438	BRect workspaceFrame = _WorkspaceAt(where, index);
439	if (index < 0)
440		return;
441
442	Workspace workspace(*Window()->Desktop(), index);
443	workspaceFrame.InsetBy(1, 1);
444
445	BRect screenFrame = _ScreenFrame(index);
446
447	::Window* window;
448	BRect windowFrame;
449	BPoint leftTop;
450	while (workspace.GetPreviousWindow(window, leftTop) == B_OK) {
451		if (window->IsMinimized() || window->IsHidden())
452			continue;
453
454		BRect frame = _WindowFrame(workspaceFrame, screenFrame, window->Frame(),
455			leftTop);
456		if (frame.Contains(where) && window->Feel() != kDesktopWindowFeel
457			&& window->Feel() != kWindowScreenFeel) {
458			fSelectedWindow = window;
459			windowFrame = frame;
460			break;
461		}
462	}
463
464	// Some special functionality (clicked with modifiers)
465
466	int32 modifiers;
467	if (fSelectedWindow != NULL
468		&& message->FindInt32("modifiers", &modifiers) == B_OK) {
469		if ((modifiers & B_CONTROL_KEY) != 0) {
470			// Activate window if clicked with the control key pressed,
471			// minimize it if control+shift - this mirrors Deskbar
472			// shortcuts (when pressing a team menu item).
473			if ((modifiers & B_SHIFT_KEY) != 0)
474				fSelectedWindow->ServerWindow()->NotifyMinimize(true);
475			else
476				Window()->Desktop()->ActivateWindow(fSelectedWindow);
477			fSelectedWindow = NULL;
478		} else if ((modifiers & B_OPTION_KEY) != 0) {
479			// Also, send window to back if clicked with the option
480			// key pressed.
481			Window()->Desktop()->SendWindowBehind(fSelectedWindow);
482			fSelectedWindow = NULL;
483		}
484	}
485
486	// If this window is movable, we keep it selected
487	// (we prevent our own window from being moved, too)
488
489	if ((fSelectedWindow != NULL
490			&& (fSelectedWindow->Flags() & B_NOT_MOVABLE) != 0)
491		|| fSelectedWindow == Window()) {
492		fSelectedWindow = NULL;
493	}
494
495	fLeftTopOffset = where - windowFrame.LeftTop();
496	fSelectedWorkspace = index;
497	fClickPoint = where;
498
499	if (index >= 0)
500		_Invalidate();
501}
502
503
504void
505WorkspacesView::MouseUp(BMessage* message, BPoint where)
506{
507	if (!fHasMoved && fSelectedWorkspace >= 0) {
508		int32 index;
509		_WorkspaceAt(where, index);
510		if (index >= 0)
511			Window()->Desktop()->SetWorkspaceAsync(index);
512	}
513
514	if (fSelectedWindow != NULL) {
515		// We need to hide the selection frame again
516		_Invalidate();
517	}
518
519	fSelectedWindow = NULL;
520	fSelectedWorkspace = -1;
521}
522
523
524void
525WorkspacesView::MouseMoved(BMessage* message, BPoint where)
526{
527	if (fSelectedWindow == NULL && fSelectedWorkspace < 0)
528		return;
529
530	// check if the correct mouse button is pressed
531	int32 buttons;
532	if (message->FindInt32("buttons", &buttons) != B_OK
533		|| (buttons & B_PRIMARY_MOUSE_BUTTON) == 0)
534		return;
535
536	if (!fHasMoved) {
537		Window()->Desktop()->SetMouseEventWindow(Window());
538			// don't let us off the mouse
539	}
540
541	int32 index;
542	BRect workspaceFrame = _WorkspaceAt(where, index);
543
544	if (fSelectedWindow == NULL) {
545		if (fSelectedWorkspace >= 0 && fSelectedWorkspace != index) {
546			fSelectedWorkspace = index;
547			_Invalidate();
548		}
549		return;
550	}
551
552	workspaceFrame.InsetBy(1, 1);
553
554	if (index != fSelectedWorkspace) {
555		if (fSelectedWindow->IsNormal() && !fSelectedWindow->InWorkspace(index)) {
556			// move window to this new workspace
557			uint32 newWorkspaces = (fSelectedWindow->Workspaces()
558				& ~(1UL << fSelectedWorkspace)) | (1UL << index);
559
560			Window()->Desktop()->SetWindowWorkspaces(fSelectedWindow,
561				newWorkspaces);
562		}
563		fSelectedWorkspace = index;
564	}
565
566	BRect screenFrame = _ScreenFrame(index);
567	float left = rintf((where.x - workspaceFrame.left - fLeftTopOffset.x)
568		* screenFrame.Width() / workspaceFrame.Width());
569	float top = rintf((where.y - workspaceFrame.top - fLeftTopOffset.y)
570		* screenFrame.Height() / workspaceFrame.Height());
571
572	BPoint leftTop;
573	if (fSelectedWorkspace == Window()->Desktop()->CurrentWorkspace())
574		leftTop = fSelectedWindow->Frame().LeftTop();
575	else {
576		if (fSelectedWindow->Anchor(fSelectedWorkspace).position
577				== kInvalidWindowPosition) {
578			fSelectedWindow->Anchor(fSelectedWorkspace).position
579				= fSelectedWindow->Frame().LeftTop();
580		}
581		leftTop = fSelectedWindow->Anchor(fSelectedWorkspace).position;
582	}
583
584	// Don't treat every little mouse move as a window move - this would
585	// make it too hard to activate a workspace.
586	float diff = max_c(fabs(fClickPoint.x - where.x),
587		fabs(fClickPoint.y - where.y));
588	if (!fHasMoved && diff > 2)
589		fHasMoved = true;
590
591	if (fHasMoved) {
592		Window()->Desktop()->MoveWindowBy(fSelectedWindow, left - leftTop.x,
593			top - leftTop.y, fSelectedWorkspace);
594	}
595}
596
597
598void
599WorkspacesView::WindowChanged(::Window* window)
600{
601	// TODO: be smarter about this!
602	_Invalidate();
603}
604
605
606void
607WorkspacesView::WindowRemoved(::Window* window)
608{
609	if (fSelectedWindow == window)
610		fSelectedWindow = NULL;
611}
612
613