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