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()->VirtualScreen().Frame(); 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 float factor = workspaceFrame.Width() / screenFrame.Width(); 154 frame.left = rintf(frame.left * factor); 155 frame.right = rintf(frame.right * factor); 156 157 factor = workspaceFrame.Height() / screenFrame.Height(); 158 frame.top = rintf(frame.top * factor); 159 frame.bottom = rintf(frame.bottom * factor); 160 161 frame.OffsetBy(workspaceFrame.LeftTop()); 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->TabRect(); 181 182 tabFrame = _WindowFrame(workspaceFrame, screenFrame, 183 tabFrame, tabFrame.LeftTop() - offset); 184 if (!workspaceFrame.Intersects(frame) && !workspaceFrame.Intersects(tabFrame)) 185 return; 186 187 // ToDo: let decorator do this! 188 rgb_color yellow; 189 if (decorator != NULL) 190 yellow = decorator->UIColor(B_WINDOW_TAB_COLOR); 191 rgb_color frameColor = (rgb_color){ 180, 180, 180, 255 }; 192 rgb_color white = (rgb_color){ 255, 255, 255, 255 }; 193 194 if (!active) { 195 _DarkenColor(yellow); 196 _DarkenColor(frameColor); 197 _DarkenColor(white); 198 } 199 if (window == fSelectedWindow) { 200 // TODO: what about standard navigation color here? 201 frameColor = (rgb_color){ 80, 80, 80, 255 }; 202 } 203 204 if (tabFrame.left < frame.left) 205 tabFrame.left = frame.left; 206 if (tabFrame.right >= frame.right) 207 tabFrame.right = frame.right - 1; 208 209 tabFrame.top = frame.top - 1; 210 tabFrame.bottom = frame.top - 1; 211 tabFrame = tabFrame & workspaceFrame; 212 213 if (decorator != NULL && tabFrame.IsValid()) { 214 drawingEngine->StrokeLine(tabFrame.LeftTop(), tabFrame.RightBottom(), yellow); 215 backgroundRegion.Exclude(tabFrame); 216 } 217 218 drawingEngine->StrokeRect(frame, frameColor); 219 220 frame = frame & workspaceFrame; 221 if (frame.IsValid()) { 222 drawingEngine->FillRect(frame.InsetByCopy(1, 1), white); 223 backgroundRegion.Exclude(frame); 224 } 225 226 // draw title 227 228 // TODO: disabled because it's much too slow this way - the mini-window 229 // functionality should probably be moved into the Window class, 230 // so that it has only to be recalculated on demand. With double buffered 231 // windows, this would also open up the door to have a more detailed 232 // preview. 233 #if 0 234 BString title(window->Title()); 235 236 ServerFont font = fDrawState->Font(); 237 font.SetSize(7); 238 fDrawState->SetFont(font); 239 240 fDrawState->Font().TruncateString(&title, B_TRUNCATE_END, frame.Width() - 4); 241 float width = drawingEngine->StringWidth(title.String(), title.Length(), 242 fDrawState, NULL); 243 float height = drawingEngine->StringHeight(title.String(), title.Length(), 244 fDrawState); 245 246 drawingEngine->DrawString(title.String(), title.Length(), 247 BPoint(frame.left + (frame.Width() - width) / 2, 248 frame.top + (frame.Height() + height) / 2), 249 fDrawState, NULL); 250 #endif 251 } 252 253 254 void 255 WorkspacesView::_DrawWorkspace(DrawingEngine* drawingEngine, 256 BRegion& redraw, int32 index) 257 { 258 BRect rect = _WorkspaceAt(index); 259 260 Workspace workspace(*Window()->Desktop(), index); 261 bool active = workspace.IsCurrent(); 262 if (active) { 263 // draw active frame 264 rgb_color black = (rgb_color){ 0, 0, 0, 255 }; 265 drawingEngine->StrokeRect(rect, black); 266 } else if (index == fSelectedWorkspace) { 267 rgb_color gray = (rgb_color){ 80, 80, 80, 255 }; 268 drawingEngine->StrokeRect(rect, gray); 269 } 270 271 rect.InsetBy(1, 1); 272 273 rgb_color color = workspace.Color(); 274 if (!active) 275 _DarkenColor(color); 276 277 // draw windows 278 279 BRegion backgroundRegion = redraw; 280 281 // ToDo: would be nice to get the real update region here 282 283 BRect screenFrame = _ScreenFrame(index); 284 285 BRegion workspaceRegion(rect); 286 backgroundRegion.IntersectWith(&workspaceRegion); 287 drawingEngine->ConstrainClippingRegion(&backgroundRegion); 288 289 // We draw from top down and cut the window out of the clipping region 290 // which reduces the flickering 291 ::Window* window; 292 BPoint leftTop; 293 while (workspace.GetPreviousWindow(window, leftTop) == B_OK) { 294 _DrawWindow(drawingEngine, rect, screenFrame, window, 295 leftTop, backgroundRegion, active); 296 } 297 298 // draw background 299 drawingEngine->FillRect(rect, color); 300 301 drawingEngine->ConstrainClippingRegion(&redraw); 302 } 303 304 305 void 306 WorkspacesView::_DarkenColor(rgb_color& color) const 307 { 308 color = tint_color(color, B_DARKEN_2_TINT); 309 } 310 311 312 void 313 WorkspacesView::_Invalidate() const 314 { 315 BRect frame = Bounds(); 316 ConvertToScreen(&frame); 317 318 BRegion region(frame); 319 Window()->MarkContentDirty(region); 320 } 321 322 323 void 324 WorkspacesView::Draw(DrawingEngine* drawingEngine, BRegion* effectiveClipping, 325 BRegion* windowContentClipping, bool deep) 326 { 327 // we can only draw within our own area 328 BRegion redraw(ScreenAndUserClipping(windowContentClipping)); 329 // add the current clipping 330 redraw.IntersectWith(effectiveClipping); 331 332 int32 columns, rows; 333 _GetGrid(columns, rows); 334 335 // draw grid 336 337 // make sure the grid around the active workspace is not drawn 338 // to reduce flicker 339 BRect activeRect = _WorkspaceAt(Window()->Desktop()->CurrentWorkspace()); 340 BRegion gridRegion(redraw); 341 gridRegion.Exclude(activeRect); 342 drawingEngine->ConstrainClippingRegion(&gridRegion); 343 344 BRect frame = Bounds(); 345 ConvertToScreen(&frame); 346 347 // horizontal lines 348 349 drawingEngine->StrokeLine(BPoint(frame.left, frame.top), 350 BPoint(frame.right, frame.top), ViewColor()); 351 352 for (int32 row = 0; row < rows; row++) { 353 BRect rect = _WorkspaceAt(row * columns); 354 drawingEngine->StrokeLine(BPoint(frame.left, rect.bottom), 355 BPoint(frame.right, rect.bottom), ViewColor()); 356 } 357 358 // vertical lines 359 360 drawingEngine->StrokeLine(BPoint(frame.left, frame.top), 361 BPoint(frame.left, frame.bottom), ViewColor()); 362 363 for (int32 column = 0; column < columns; column++) { 364 BRect rect = _WorkspaceAt(column); 365 drawingEngine->StrokeLine(BPoint(rect.right, frame.top), 366 BPoint(rect.right, frame.bottom), ViewColor()); 367 } 368 369 drawingEngine->ConstrainClippingRegion(&redraw); 370 371 // draw workspaces 372 373 for (int32 i = rows * columns; i-- > 0;) { 374 _DrawWorkspace(drawingEngine, redraw, i); 375 } 376 } 377 378 379 void 380 WorkspacesView::MouseDown(BMessage* message, BPoint where) 381 { 382 // reset tracking variables 383 fSelectedWorkspace = -1; 384 fSelectedWindow = NULL; 385 fHasMoved = false; 386 387 // check if the correct mouse button is pressed 388 int32 buttons; 389 if (message->FindInt32("buttons", &buttons) != B_OK 390 || (buttons & B_PRIMARY_MOUSE_BUTTON) == 0) 391 return; 392 393 int32 index; 394 BRect workspaceFrame = _WorkspaceAt(where, index); 395 if (index < 0) 396 return; 397 398 Workspace workspace(*Window()->Desktop(), index); 399 workspaceFrame.InsetBy(1, 1); 400 401 BRect screenFrame = _ScreenFrame(index); 402 403 ::Window* window; 404 BRect windowFrame; 405 BPoint leftTop; 406 while (workspace.GetPreviousWindow(window, leftTop) == B_OK) { 407 BRect frame = _WindowFrame(workspaceFrame, screenFrame, window->Frame(), 408 leftTop); 409 if (frame.Contains(where) && window->Feel() != kDesktopWindowFeel 410 && window->Feel() != kWindowScreenFeel) { 411 fSelectedWindow = window; 412 windowFrame = frame; 413 break; 414 } 415 } 416 417 // Some special functionality (clicked with modifiers) 418 419 int32 modifiers; 420 if (fSelectedWindow != NULL 421 && message->FindInt32("modifiers", &modifiers) == B_OK) { 422 if ((modifiers & B_CONTROL_KEY) != 0) { 423 // Activate window if clicked with the control key pressed, 424 // minimize it if control+shift - this mirrors Deskbar 425 // shortcuts (when pressing a team menu item). 426 if ((modifiers & B_SHIFT_KEY) != 0) 427 fSelectedWindow->ServerWindow()->NotifyMinimize(true); 428 else 429 Window()->Desktop()->ActivateWindow(fSelectedWindow); 430 fSelectedWindow = NULL; 431 } else if ((modifiers & B_OPTION_KEY) != 0) { 432 // Also, send window to back if clicked with the option 433 // key pressed. 434 Window()->Desktop()->SendWindowBehind(fSelectedWindow); 435 fSelectedWindow = NULL; 436 } 437 } 438 439 // If this window is movable, we keep it selected 440 // (we prevent our own window from being moved, too) 441 442 if (fSelectedWindow != NULL && (fSelectedWindow->Flags() & B_NOT_MOVABLE) != 0 443 || fSelectedWindow == Window()) { 444 fSelectedWindow = NULL; 445 } 446 447 fLeftTopOffset = where - windowFrame.LeftTop(); 448 fSelectedWorkspace = index; 449 450 if (index >= 0) 451 _Invalidate(); 452 } 453 454 455 void 456 WorkspacesView::MouseUp(BMessage* message, BPoint where) 457 { 458 if (!fHasMoved && fSelectedWorkspace >= 0) { 459 int32 index; 460 _WorkspaceAt(where, index); 461 if (index >= 0) 462 Window()->Desktop()->SetWorkspaceAsync(index); 463 } 464 465 if (fSelectedWindow != NULL) { 466 // We need to hide the selection frame again 467 _Invalidate(); 468 } 469 470 fSelectedWindow = NULL; 471 fSelectedWorkspace = -1; 472 } 473 474 475 void 476 WorkspacesView::MouseMoved(BMessage* message, BPoint where) 477 { 478 if (fSelectedWindow == NULL && fSelectedWorkspace < 0) 479 return; 480 481 // check if the correct mouse button is pressed 482 int32 buttons; 483 if (message->FindInt32("buttons", &buttons) != B_OK 484 || (buttons & B_PRIMARY_MOUSE_BUTTON) == 0) 485 return; 486 487 if (!fHasMoved) { 488 Window()->Desktop()->SetMouseEventWindow(Window()); 489 // don't let us off the mouse 490 } 491 492 int32 index; 493 BRect workspaceFrame = _WorkspaceAt(where, index); 494 495 if (fSelectedWindow == NULL) { 496 if (fSelectedWorkspace >= 0 && fSelectedWorkspace != index) { 497 fSelectedWorkspace = index; 498 _Invalidate(); 499 } 500 return; 501 } 502 503 workspaceFrame.InsetBy(1, 1); 504 505 if (index != fSelectedWorkspace) { 506 if (fSelectedWindow->IsNormal() && !fSelectedWindow->InWorkspace(index)) { 507 // move window to this new workspace 508 uint32 newWorkspaces = fSelectedWindow->Workspaces() 509 & ~(1UL << fSelectedWorkspace) | (1UL << index); 510 511 Window()->Desktop()->SetWindowWorkspaces(fSelectedWindow, 512 newWorkspaces); 513 } 514 fSelectedWorkspace = index; 515 } 516 517 BRect screenFrame = _ScreenFrame(index); 518 float left = rintf((where.x - workspaceFrame.left - fLeftTopOffset.x) 519 * screenFrame.Width() / workspaceFrame.Width()); 520 float top = rintf((where.y - workspaceFrame.top - fLeftTopOffset.y) 521 * screenFrame.Height() / workspaceFrame.Height()); 522 523 BPoint leftTop; 524 if (fSelectedWorkspace == Window()->Desktop()->CurrentWorkspace()) 525 leftTop = fSelectedWindow->Frame().LeftTop(); 526 else { 527 if (fSelectedWindow->Anchor(fSelectedWorkspace).position == kInvalidWindowPosition) 528 fSelectedWindow->Anchor(fSelectedWorkspace).position = fSelectedWindow->Frame().LeftTop(); 529 leftTop = fSelectedWindow->Anchor(fSelectedWorkspace).position; 530 } 531 532 Window()->Desktop()->MoveWindowBy(fSelectedWindow, left - leftTop.x, top - leftTop.y, 533 fSelectedWorkspace); 534 535 fHasMoved = true; 536 } 537 538 539 void 540 WorkspacesView::WindowChanged(::Window* window) 541 { 542 // TODO: be smarter about this! 543 _Invalidate(); 544 } 545 546 547 void 548 WorkspacesView::WindowRemoved(::Window* window) 549 { 550 if (fSelectedWindow == window) 551 fSelectedWindow = NULL; 552 } 553 554