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