xref: /haiku/src/servers/app/WorkspacesView.cpp (revision 2231cbd5fe30db1df3dfedb02d912a19ddab530f)
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 	ConvertToScreen(&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.left < frame.left)
218 		tabFrame.left = frame.left;
219 	if (tabFrame.right >= frame.right)
220 		tabFrame.right = frame.right - 1;
221 
222 	tabFrame.bottom = frame.top - 1;
223 	tabFrame.top = min_c(tabFrame.top, tabFrame.bottom);
224 	tabFrame = tabFrame & workspaceFrame;
225 
226 	if (decorator != NULL && tabFrame.IsValid()) {
227 		drawingEngine->FillRect(tabFrame, tabColor);
228 		backgroundRegion.Exclude(tabFrame);
229 	}
230 
231 	drawingEngine->StrokeRect(frame, frameColor);
232 
233 	BRect fillFrame = frame.InsetByCopy(1, 1) & workspaceFrame;
234 	frame = frame & workspaceFrame;
235 	if (!frame.IsValid())
236 		return;
237 
238 	// fill the window itself
239 	drawingEngine->FillRect(fillFrame, white);
240 
241 	// draw title
242 
243 	// TODO: the mini-window functionality should probably be moved into the
244 	// Window class, so that it has only to be recalculated on demand. With
245 	// double buffered windows, this would also open up the door to have a
246 	// more detailed preview.
247 	BString title(window->Title());
248 
249 	const ServerFont& font = fDrawState->Font();
250 
251 	font.TruncateString(&title, B_TRUNCATE_END, fillFrame.Width() - 4);
252 	font_height fontHeight;
253 	font.GetHeight(fontHeight);
254 	float height = ceilf(fontHeight.ascent) + ceilf(fontHeight.descent);
255 	if (title.Length() > 0 && height < frame.Height() - 2) {
256 		rgb_color textColor = tint_color(white, B_DARKEN_4_TINT);
257 		drawingEngine->SetHighColor(textColor);
258 		drawingEngine->SetLowColor(white);
259 
260 		float width = font.StringWidth(title.String(), title.Length());
261 
262 		BPoint textOffset;
263 		textOffset.x = rintf(frame.left + (frame.Width() - width) / 2);
264 		textOffset.y = rintf(frame.top + (frame.Height() - height) / 2
265 			+ fontHeight.ascent);
266 		drawingEngine->DrawString(title.String(), title.Length(), textOffset);
267 	}
268 
269 	// prevent the next window down from drawing over this window
270 	backgroundRegion.Exclude(frame);
271 }
272 
273 
274 void
275 WorkspacesView::_DrawWorkspace(DrawingEngine* drawingEngine,
276 	BRegion& redraw, int32 index)
277 {
278 	BRect rect = _WorkspaceAt(index);
279 
280 	Workspace workspace(*Window()->Desktop(), index);
281 	bool workspaceActive = workspace.IsCurrent();
282 	if (workspaceActive) {
283 		// draw active frame
284 		rgb_color black = (rgb_color){ 0, 0, 0, 255 };
285 		drawingEngine->StrokeRect(rect, black);
286 	} else if (index == fSelectedWorkspace) {
287 		rgb_color gray = (rgb_color){ 80, 80, 80, 255 };
288 		drawingEngine->StrokeRect(rect, gray);
289 	}
290 
291 	rect.InsetBy(1, 1);
292 
293 	rgb_color color = workspace.Color();
294 	if (!workspaceActive)
295 		_DarkenColor(color);
296 
297 	// draw windows
298 
299 	BRegion backgroundRegion = redraw;
300 
301 	// TODO: would be nice to get the real update region here
302 
303 	BRect screenFrame = _ScreenFrame(index);
304 
305 	BRegion workspaceRegion(rect);
306 	backgroundRegion.IntersectWith(&workspaceRegion);
307 	drawingEngine->ConstrainClippingRegion(&backgroundRegion);
308 
309 	ServerFont font = fDrawState->Font();
310 	font.SetSize(fWindow->ServerWindow()->App()->PlainFont().Size());
311 	float reducedSize = ceilf(max_c(8.0f,
312 		min_c(Frame().Height(), Frame().Width()) / 15));
313 	if (font.Size() > reducedSize)
314 		font.SetSize(reducedSize);
315 	fDrawState->SetFont(font);
316 	drawingEngine->SetFont(font);
317 
318 	// We draw from top down and cut the window out of the clipping region
319 	// which reduces the flickering
320 	::Window* window;
321 	BPoint leftTop;
322 	while (workspace.GetPreviousWindow(window, leftTop) == B_OK) {
323 		_DrawWindow(drawingEngine, rect, screenFrame, window,
324 			leftTop, backgroundRegion, workspaceActive);
325 	}
326 
327 	// draw background
328 	drawingEngine->FillRect(rect, color);
329 
330 	drawingEngine->ConstrainClippingRegion(&redraw);
331 }
332 
333 
334 void
335 WorkspacesView::_DarkenColor(rgb_color& color) const
336 {
337 	color = tint_color(color, B_DARKEN_2_TINT);
338 }
339 
340 
341 void
342 WorkspacesView::_Invalidate() const
343 {
344 	BRect frame = Bounds();
345 	ConvertToScreen(&frame);
346 
347 	BRegion region(frame);
348 	Window()->MarkContentDirty(region);
349 }
350 
351 
352 void
353 WorkspacesView::Draw(DrawingEngine* drawingEngine, BRegion* effectiveClipping,
354 	BRegion* windowContentClipping, bool deep)
355 {
356 	// we can only draw within our own area
357 	BRegion redraw(ScreenAndUserClipping(windowContentClipping));
358 	// add the current clipping
359 	redraw.IntersectWith(effectiveClipping);
360 
361 	int32 columns, rows;
362 	_GetGrid(columns, rows);
363 
364 	// draw grid
365 
366 	// make sure the grid around the active workspace is not drawn
367 	// to reduce flicker
368 	BRect activeRect = _WorkspaceAt(Window()->Desktop()->CurrentWorkspace());
369 	BRegion gridRegion(redraw);
370 	gridRegion.Exclude(activeRect);
371 	drawingEngine->ConstrainClippingRegion(&gridRegion);
372 
373 	BRect frame = Bounds();
374 	ConvertToScreen(&frame);
375 
376 	// horizontal lines
377 
378 	drawingEngine->StrokeLine(BPoint(frame.left, frame.top),
379 		BPoint(frame.right, frame.top), ViewColor());
380 
381 	for (int32 row = 0; row < rows; row++) {
382 		BRect rect = _WorkspaceAt(row * columns);
383 		drawingEngine->StrokeLine(BPoint(frame.left, rect.bottom),
384 			BPoint(frame.right, rect.bottom), ViewColor());
385 	}
386 
387 	// vertical lines
388 
389 	drawingEngine->StrokeLine(BPoint(frame.left, frame.top),
390 		BPoint(frame.left, frame.bottom), ViewColor());
391 
392 	for (int32 column = 0; column < columns; column++) {
393 		BRect rect = _WorkspaceAt(column);
394 		drawingEngine->StrokeLine(BPoint(rect.right, frame.top),
395 			BPoint(rect.right, frame.bottom), ViewColor());
396 	}
397 
398 	drawingEngine->ConstrainClippingRegion(&redraw);
399 
400 	// draw workspaces
401 
402 	for (int32 i = rows * columns; i-- > 0;) {
403 		_DrawWorkspace(drawingEngine, redraw, i);
404 	}
405 	fWindow->ServerWindow()->ResyncDrawState();
406 }
407 
408 
409 void
410 WorkspacesView::MouseDown(BMessage* message, BPoint where)
411 {
412 	// reset tracking variables
413 	fSelectedWorkspace = -1;
414 	fSelectedWindow = NULL;
415 	fHasMoved = false;
416 
417 	// check if the correct mouse button is pressed
418 	int32 buttons;
419 	if (message->FindInt32("buttons", &buttons) != B_OK
420 		|| (buttons & B_PRIMARY_MOUSE_BUTTON) == 0)
421 		return;
422 
423 	int32 index;
424 	BRect workspaceFrame = _WorkspaceAt(where, index);
425 	if (index < 0)
426 		return;
427 
428 	Workspace workspace(*Window()->Desktop(), index);
429 	workspaceFrame.InsetBy(1, 1);
430 
431 	BRect screenFrame = _ScreenFrame(index);
432 
433 	::Window* window;
434 	BRect windowFrame;
435 	BPoint leftTop;
436 	while (workspace.GetPreviousWindow(window, leftTop) == B_OK) {
437 		BRect frame = _WindowFrame(workspaceFrame, screenFrame, window->Frame(),
438 			leftTop);
439 		if (frame.Contains(where) && window->Feel() != kDesktopWindowFeel
440 			&& window->Feel() != kWindowScreenFeel) {
441 			fSelectedWindow = window;
442 			windowFrame = frame;
443 			break;
444 		}
445 	}
446 
447 	// Some special functionality (clicked with modifiers)
448 
449 	int32 modifiers;
450 	if (fSelectedWindow != NULL
451 		&& message->FindInt32("modifiers", &modifiers) == B_OK) {
452 		if ((modifiers & B_CONTROL_KEY) != 0) {
453 			// Activate window if clicked with the control key pressed,
454 			// minimize it if control+shift - this mirrors Deskbar
455 			// shortcuts (when pressing a team menu item).
456 			if ((modifiers & B_SHIFT_KEY) != 0)
457 				fSelectedWindow->ServerWindow()->NotifyMinimize(true);
458 			else
459 				Window()->Desktop()->ActivateWindow(fSelectedWindow);
460 			fSelectedWindow = NULL;
461 		} else if ((modifiers & B_OPTION_KEY) != 0) {
462 			// Also, send window to back if clicked with the option
463 			// key pressed.
464 			Window()->Desktop()->SendWindowBehind(fSelectedWindow);
465 			fSelectedWindow = NULL;
466 		}
467 	}
468 
469 	// If this window is movable, we keep it selected
470 	// (we prevent our own window from being moved, too)
471 
472 	if ((fSelectedWindow != NULL
473 			&& (fSelectedWindow->Flags() & B_NOT_MOVABLE) != 0)
474 		|| fSelectedWindow == Window()) {
475 		fSelectedWindow = NULL;
476 	}
477 
478 	fLeftTopOffset = where - windowFrame.LeftTop();
479 	fSelectedWorkspace = index;
480 	fClickPoint = where;
481 
482 	if (index >= 0)
483 		_Invalidate();
484 }
485 
486 
487 void
488 WorkspacesView::MouseUp(BMessage* message, BPoint where)
489 {
490 	if (!fHasMoved && fSelectedWorkspace >= 0) {
491 		int32 index;
492 		_WorkspaceAt(where, index);
493 		if (index >= 0)
494 			Window()->Desktop()->SetWorkspaceAsync(index);
495 	}
496 
497 	if (fSelectedWindow != NULL) {
498 		// We need to hide the selection frame again
499 		_Invalidate();
500 	}
501 
502 	fSelectedWindow = NULL;
503 	fSelectedWorkspace = -1;
504 }
505 
506 
507 void
508 WorkspacesView::MouseMoved(BMessage* message, BPoint where)
509 {
510 	if (fSelectedWindow == NULL && fSelectedWorkspace < 0)
511 		return;
512 
513 	// check if the correct mouse button is pressed
514 	int32 buttons;
515 	if (message->FindInt32("buttons", &buttons) != B_OK
516 		|| (buttons & B_PRIMARY_MOUSE_BUTTON) == 0)
517 		return;
518 
519 	if (!fHasMoved) {
520 		Window()->Desktop()->SetMouseEventWindow(Window());
521 			// don't let us off the mouse
522 	}
523 
524 	int32 index;
525 	BRect workspaceFrame = _WorkspaceAt(where, index);
526 
527 	if (fSelectedWindow == NULL) {
528 		if (fSelectedWorkspace >= 0 && fSelectedWorkspace != index) {
529 			fSelectedWorkspace = index;
530 			_Invalidate();
531 		}
532 		return;
533 	}
534 
535 	workspaceFrame.InsetBy(1, 1);
536 
537 	if (index != fSelectedWorkspace) {
538 		if (fSelectedWindow->IsNormal() && !fSelectedWindow->InWorkspace(index)) {
539 			// move window to this new workspace
540 			uint32 newWorkspaces = (fSelectedWindow->Workspaces()
541 				& ~(1UL << fSelectedWorkspace)) | (1UL << index);
542 
543 			Window()->Desktop()->SetWindowWorkspaces(fSelectedWindow,
544 				newWorkspaces);
545 		}
546 		fSelectedWorkspace = index;
547 	}
548 
549 	BRect screenFrame = _ScreenFrame(index);
550 	float left = rintf((where.x - workspaceFrame.left - fLeftTopOffset.x)
551 		* screenFrame.Width() / workspaceFrame.Width());
552 	float top = rintf((where.y - workspaceFrame.top - fLeftTopOffset.y)
553 		* screenFrame.Height() / workspaceFrame.Height());
554 
555 	BPoint leftTop;
556 	if (fSelectedWorkspace == Window()->Desktop()->CurrentWorkspace())
557 		leftTop = fSelectedWindow->Frame().LeftTop();
558 	else {
559 		if (fSelectedWindow->Anchor(fSelectedWorkspace).position
560 				== kInvalidWindowPosition) {
561 			fSelectedWindow->Anchor(fSelectedWorkspace).position
562 				= fSelectedWindow->Frame().LeftTop();
563 		}
564 		leftTop = fSelectedWindow->Anchor(fSelectedWorkspace).position;
565 	}
566 
567 	// Don't treat every little mouse move as a window move - this would
568 	// make it too hard to activate a workspace.
569 	float diff = max_c(fabs(fClickPoint.x - where.x),
570 		fabs(fClickPoint.y - where.y));
571 	if (!fHasMoved && diff > 2)
572 		fHasMoved = true;
573 
574 	if (fHasMoved) {
575 		Window()->Desktop()->MoveWindowBy(fSelectedWindow, left - leftTop.x,
576 			top - leftTop.y, fSelectedWorkspace);
577 	}
578 }
579 
580 
581 void
582 WorkspacesView::WindowChanged(::Window* window)
583 {
584 	// TODO: be smarter about this!
585 	_Invalidate();
586 }
587 
588 
589 void
590 WorkspacesView::WindowRemoved(::Window* window)
591 {
592 	if (fSelectedWindow == window)
593 		fSelectedWindow = NULL;
594 }
595 
596