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