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