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