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 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 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.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 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 349 WorkspacesView::_DarkenColor(rgb_color& color) const 350 { 351 color = tint_color(color, B_DARKEN_2_TINT); 352 } 353 354 355 void 356 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 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 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 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 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 599 WorkspacesView::WindowChanged(::Window* window) 600 { 601 // TODO: be smarter about this! 602 _Invalidate(); 603 } 604 605 606 void 607 WorkspacesView::WindowRemoved(::Window* window) 608 { 609 if (fSelectedWindow == window) 610 fSelectedWindow = NULL; 611 } 612 613