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