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