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