xref: /haiku/src/servers/app/WorkspacesView.cpp (revision 4c07199d8201fcf267e90be0d24b76799d03cea6)
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 
26 WorkspacesView::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 
39 WorkspacesView::~WorkspacesView()
40 {
41 }
42 
43 
44 void
45 WorkspacesView::AttachedToWindow(::Window* window)
46 {
47 	View::AttachedToWindow(window);
48 
49 	window->AddWorkspacesView();
50 	window->Desktop()->AddWorkspacesView(this);
51 }
52 
53 
54 void
55 WorkspacesView::DetachedFromWindow()
56 {
57 	fWindow->Desktop()->RemoveWorkspacesView(this);
58 	fWindow->RemoveWorkspacesView();
59 
60 	View::DetachedFromWindow();
61 }
62 
63 
64 void
65 WorkspacesView::_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 */
76 BRect
77 WorkspacesView::_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 */
86 BRect
87 WorkspacesView::_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 */
122 BRect
123 WorkspacesView::_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 
139 BRect
140 WorkspacesView::_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 
167 void
168 WorkspacesView::_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 
288 void
289 WorkspacesView::_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 
348 void
349 WorkspacesView::_DarkenColor(rgb_color& color) const
350 {
351 	color = tint_color(color, B_DARKEN_2_TINT);
352 }
353 
354 
355 void
356 WorkspacesView::_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 
366 void
367 WorkspacesView::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 
423 void
424 WorkspacesView::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 
504 void
505 WorkspacesView::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 
524 void
525 WorkspacesView::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 
598 void
599 WorkspacesView::WindowChanged(::Window* window)
600 {
601 	// TODO: be smarter about this!
602 	_Invalidate();
603 }
604 
605 
606 void
607 WorkspacesView::WindowRemoved(::Window* window)
608 {
609 	if (fSelectedWindow == window)
610 		fSelectedWindow = NULL;
611 }
612 
613