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