1 /* 2 * Copyright 2006-2007, Haiku. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Stephan Aßmus <superstippi@gmx.de> 7 */ 8 9 #include "CanvasView.h" 10 11 #include <Bitmap.h> 12 #include <Cursor.h> 13 #include <Message.h> 14 #include <Region.h> 15 #include <Window.h> 16 17 #include <stdio.h> 18 19 #include "cursors.h" 20 #include "ui_defines.h" 21 22 #include "CommandStack.h" 23 #include "IconRenderer.h" 24 25 using std::nothrow; 26 27 // constructor 28 CanvasView::CanvasView(BRect frame) 29 : StateView(frame, "canvas view", B_FOLLOW_ALL, 30 B_WILL_DRAW | B_FRAME_EVENTS), 31 fBitmap(new BBitmap(BRect(0, 0, 63, 63), 0, B_RGB32)), 32 fBackground(new BBitmap(BRect(0, 0, 63, 63), 0, B_RGB32)), 33 fIcon(NULL), 34 fRenderer(new IconRenderer(fBitmap)), 35 fDirtyIconArea(fBitmap->Bounds()), 36 37 fCanvasOrigin(0.0, 0.0), 38 fZoomLevel(1.0), 39 40 fSpaceHeldDown(false), 41 fScrollTracking(false), 42 fScrollTrackingStart(0.0, 0.0), 43 44 fMouseFilterMode(SNAPPING_OFF), 45 46 fOffsreenBitmap(NULL), 47 fOffsreenView(NULL) 48 { 49 _MakeBackground(); 50 fRenderer->SetBackground(fBackground); 51 } 52 53 // destructor 54 CanvasView::~CanvasView() 55 { 56 SetIcon(NULL); 57 delete fRenderer; 58 delete fBitmap; 59 delete fBackground; 60 61 _FreeBackBitmap(); 62 } 63 64 // #pragma mark - 65 66 // AttachedToWindow 67 void 68 CanvasView::AttachedToWindow() 69 { 70 StateView::AttachedToWindow(); 71 72 SetViewColor(B_TRANSPARENT_COLOR); 73 SetLowColor(kStripesHigh); 74 SetHighColor(kStripesLow); 75 76 BRect bounds(Bounds()); 77 78 _AllocBackBitmap(bounds.Width(), bounds.Height()); 79 80 // layout icon in the center 81 BRect bitmapBounds(fBitmap->Bounds()); 82 fCanvasOrigin.x = floorf((bounds.left + bounds.right 83 - bitmapBounds.right - bitmapBounds.left) / 2.0 + 0.5); 84 fCanvasOrigin.y = floorf((bounds.top + bounds.bottom 85 - bitmapBounds.bottom - bitmapBounds.top) / 2.0 + 0.5); 86 87 _SetZoom(8.0, false); 88 } 89 90 // FrameResized 91 void 92 CanvasView::FrameResized(float width, float height) 93 { 94 _AllocBackBitmap(width, height); 95 96 // keep canvas centered 97 BPoint oldCanvasOrigin = fCanvasOrigin; 98 SetDataRect(_LayoutCanvas()); 99 if (oldCanvasOrigin != fCanvasOrigin) 100 Invalidate(); 101 } 102 103 // Draw 104 void 105 CanvasView::Draw(BRect updateRect) 106 { 107 if (!fOffsreenView) { 108 _DrawInto(this, updateRect); 109 } else { 110 BPoint boundsLeftTop = Bounds().LeftTop(); 111 if (fOffsreenBitmap->Lock()) { 112 fOffsreenView->PushState(); 113 114 // apply scrolling offset to offscreen view 115 fOffsreenView->SetOrigin(-boundsLeftTop.x, -boundsLeftTop.y); 116 // mirror the clipping of this view 117 // to the offscreen view for performance 118 BRegion clipping; 119 GetClippingRegion(&clipping); 120 fOffsreenView->ConstrainClippingRegion(&clipping); 121 122 _DrawInto(fOffsreenView, updateRect); 123 124 fOffsreenView->PopState(); 125 fOffsreenView->Sync(); 126 127 fOffsreenBitmap->Unlock(); 128 } 129 // compensate scrolling offset in BView 130 BRect bitmapRect = updateRect; 131 bitmapRect.OffsetBy(-boundsLeftTop.x, -boundsLeftTop.y); 132 133 SetDrawingMode(B_OP_COPY); 134 DrawBitmap(fOffsreenBitmap, bitmapRect, updateRect); 135 } 136 } 137 138 // #pragma mark - 139 140 // MouseDown 141 void 142 CanvasView::MouseDown(BPoint where) 143 { 144 if (!IsFocus()) 145 MakeFocus(true); 146 147 uint32 buttons; 148 if (Window()->CurrentMessage()->FindInt32("buttons", 149 (int32*)&buttons) < B_OK) 150 buttons = 0; 151 152 // handle clicks of the third mouse button ourselves (panning), 153 // otherwise have StateView handle it (normal clicks) 154 if (fSpaceHeldDown || buttons & B_TERTIARY_MOUSE_BUTTON) { 155 // switch into scrolling mode and update cursor 156 fScrollTracking = true; 157 where.x = roundf(where.x); 158 where.y = roundf(where.y); 159 fScrollOffsetStart = ScrollOffset(); 160 fScrollTrackingStart = where - fScrollOffsetStart; 161 _UpdateToolCursor(); 162 SetMouseEventMask(B_POINTER_EVENTS, 163 B_LOCK_WINDOW_FOCUS | B_SUSPEND_VIEW_FOCUS); 164 } else { 165 StateView::MouseDown(where); 166 } 167 } 168 169 // MouseUp 170 void 171 CanvasView::MouseUp(BPoint where) 172 { 173 if (fScrollTracking) { 174 // stop scroll tracking and update cursor 175 fScrollTracking = false; 176 _UpdateToolCursor(); 177 // update StateView mouse position 178 uint32 transit = Bounds().Contains(where) ? 179 B_INSIDE_VIEW : B_OUTSIDE_VIEW; 180 StateView::MouseMoved(where, transit, NULL); 181 } else { 182 StateView::MouseUp(where); 183 } 184 } 185 186 // MouseMoved 187 void 188 CanvasView::MouseMoved(BPoint where, uint32 transit, const BMessage* dragMessage) 189 { 190 if (fScrollTracking) { 191 uint32 buttons; 192 GetMouse(&where, &buttons, false); 193 if (!buttons) { 194 MouseUp(where); 195 return; 196 } 197 where.x = roundf(where.x); 198 where.y = roundf(where.y); 199 where -= ScrollOffset(); 200 BPoint offset = where - fScrollTrackingStart; 201 SetScrollOffset(fScrollOffsetStart - offset); 202 } else { 203 // normal mouse movement handled by StateView 204 if (!fSpaceHeldDown) 205 StateView::MouseMoved(where, transit, dragMessage); 206 } 207 } 208 209 // FilterMouse 210 void 211 CanvasView::FilterMouse(BPoint* where) const 212 { 213 switch (fMouseFilterMode) { 214 215 case SNAPPING_64: 216 ConvertToCanvas(where); 217 where->x = floorf(where->x + 0.5); 218 where->y = floorf(where->y + 0.5); 219 ConvertFromCanvas(where); 220 break; 221 222 case SNAPPING_32: 223 ConvertToCanvas(where); 224 where->x /= 2.0; 225 where->y /= 2.0; 226 where->x = floorf(where->x + 0.5); 227 where->y = floorf(where->y + 0.5); 228 where->x *= 2.0; 229 where->y *= 2.0; 230 ConvertFromCanvas(where); 231 break; 232 233 case SNAPPING_16: 234 ConvertToCanvas(where); 235 where->x /= 4.0; 236 where->y /= 4.0; 237 where->x = floorf(where->x + 0.5); 238 where->y = floorf(where->y + 0.5); 239 where->x *= 4.0; 240 where->y *= 4.0; 241 ConvertFromCanvas(where); 242 break; 243 244 case SNAPPING_OFF: 245 default: 246 break; 247 } 248 } 249 250 // MouseWheelChanged 251 bool 252 CanvasView::MouseWheelChanged(BPoint where, float x, float y) 253 { 254 if (!Bounds().Contains(where)) 255 return false; 256 257 if (y > 0.0) { 258 _SetZoom(_NextZoomOutLevel(fZoomLevel)); 259 return true; 260 } else if (y < 0.0) { 261 _SetZoom(_NextZoomInLevel(fZoomLevel)); 262 return true; 263 } 264 return false; 265 } 266 267 // #pragma mark - 268 269 // ScrollOffsetChanged 270 void 271 CanvasView::ScrollOffsetChanged(BPoint oldOffset, BPoint newOffset) 272 { 273 BPoint offset = newOffset - oldOffset; 274 275 if (offset == B_ORIGIN) 276 // prevent circular code (MouseMoved might call ScrollBy...) 277 return; 278 279 ScrollBy(offset.x, offset.y); 280 281 if (!fScrollTracking) 282 MouseMoved(fMouseInfo.position + offset, fMouseInfo.transit, NULL); 283 } 284 285 // VisibleSizeChanged 286 void 287 CanvasView::VisibleSizeChanged(float oldWidth, float oldHeight, 288 float newWidth, float newHeight) 289 { 290 } 291 292 // #pragma mark - 293 294 // AreaInvalidated 295 void 296 CanvasView::AreaInvalidated(const BRect& area) 297 { 298 if (fDirtyIconArea.Contains(area)) 299 return; 300 301 fDirtyIconArea = fDirtyIconArea | area; 302 303 BRect viewArea(area); 304 ConvertFromCanvas(&viewArea); 305 Invalidate(viewArea); 306 } 307 308 309 // #pragma mark - 310 311 // SetIcon 312 void 313 CanvasView::SetIcon(Icon* icon) 314 { 315 if (fIcon == icon) 316 return; 317 318 if (fIcon) 319 fIcon->RemoveListener(this); 320 321 fIcon = icon; 322 fRenderer->SetIcon(icon); 323 324 if (fIcon) 325 fIcon->AddListener(this); 326 } 327 328 // SetMouseFilterMode 329 void 330 CanvasView::SetMouseFilterMode(uint32 mode) 331 { 332 if (fMouseFilterMode == mode) 333 return; 334 335 fMouseFilterMode = mode; 336 Invalidate(_CanvasRect()); 337 } 338 339 // ConvertFromCanvas 340 void 341 CanvasView::ConvertFromCanvas(BPoint* point) const 342 { 343 point->x = point->x * fZoomLevel + fCanvasOrigin.x; 344 point->y = point->y * fZoomLevel + fCanvasOrigin.y; 345 } 346 347 // ConvertToCanvas 348 void 349 CanvasView::ConvertToCanvas(BPoint* point) const 350 { 351 point->x = (point->x - fCanvasOrigin.x) / fZoomLevel; 352 point->y = (point->y - fCanvasOrigin.y) / fZoomLevel; 353 } 354 355 // ConvertFromCanvas 356 void 357 CanvasView::ConvertFromCanvas(BRect* r) const 358 { 359 r->left = r->left * fZoomLevel + fCanvasOrigin.x; 360 r->top = r->top * fZoomLevel + fCanvasOrigin.y; 361 r->right++; 362 r->bottom++; 363 r->right = r->right * fZoomLevel + fCanvasOrigin.x; 364 r->bottom = r->bottom * fZoomLevel + fCanvasOrigin.y; 365 r->right--; 366 r->bottom--; 367 } 368 369 // ConvertToCanvas 370 void 371 CanvasView::ConvertToCanvas(BRect* r) const 372 { 373 r->left = (r->left - fCanvasOrigin.x) / fZoomLevel; 374 r->top = (r->top - fCanvasOrigin.y) / fZoomLevel; 375 r->right = (r->right - fCanvasOrigin.x) / fZoomLevel; 376 r->bottom = (r->bottom - fCanvasOrigin.y) / fZoomLevel; 377 } 378 379 // #pragma mark - 380 381 // _HandleKeyDown 382 bool 383 CanvasView::_HandleKeyDown(uint32 key, uint32 modifiers) 384 { 385 switch (key) { 386 case 'z': 387 case 'y': 388 if (modifiers & B_SHIFT_KEY) 389 CommandStack()->Redo(); 390 else 391 CommandStack()->Undo(); 392 break; 393 394 case '+': 395 _SetZoom(_NextZoomInLevel(fZoomLevel)); 396 break; 397 case '-': 398 _SetZoom(_NextZoomOutLevel(fZoomLevel)); 399 break; 400 401 case B_SPACE: 402 fSpaceHeldDown = true; 403 _UpdateToolCursor(); 404 break; 405 406 default: 407 return StateView::_HandleKeyDown(key, modifiers); 408 } 409 410 return true; 411 } 412 413 // _HandleKeyUp 414 bool 415 CanvasView::_HandleKeyUp(uint32 key, uint32 modifiers) 416 { 417 switch (key) { 418 case B_SPACE: 419 fSpaceHeldDown = false; 420 _UpdateToolCursor(); 421 break; 422 423 default: 424 return StateView::_HandleKeyUp(key, modifiers); 425 } 426 427 return true; 428 } 429 430 // _CanvasRect() 431 BRect 432 CanvasView::_CanvasRect() const 433 { 434 BRect r = fBitmap->Bounds(); 435 ConvertFromCanvas(&r); 436 return r; 437 } 438 439 // _AllocBackBitmap 440 void 441 CanvasView::_AllocBackBitmap(float width, float height) 442 { 443 // sanity check 444 if (width <= 0.0 || height <= 0.0) 445 return; 446 447 if (fOffsreenBitmap) { 448 // see if the bitmap needs to be expanded 449 BRect b = fOffsreenBitmap->Bounds(); 450 if (b.Width() >= width && b.Height() >= height) 451 return; 452 453 // it does; clean up: 454 _FreeBackBitmap(); 455 } 456 457 BRect b(0.0, 0.0, width, height); 458 fOffsreenBitmap = new (nothrow) BBitmap(b, B_RGB32, true); 459 if (!fOffsreenBitmap) { 460 fprintf(stderr, "CanvasView::_AllocBackBitmap(): failed to allocate\n"); 461 return; 462 } 463 if (fOffsreenBitmap->IsValid()) { 464 fOffsreenView = new BView(b, 0, B_FOLLOW_NONE, B_WILL_DRAW); 465 BFont font; 466 GetFont(&font); 467 fOffsreenView->SetFont(&font); 468 fOffsreenView->SetHighColor(HighColor()); 469 fOffsreenView->SetLowColor(LowColor()); 470 fOffsreenView->SetFlags(Flags()); 471 fOffsreenBitmap->AddChild(fOffsreenView); 472 } else { 473 _FreeBackBitmap(); 474 fprintf(stderr, "CanvasView::_AllocBackBitmap(): bitmap invalid\n"); 475 } 476 } 477 478 // _FreeBackBitmap 479 void 480 CanvasView::_FreeBackBitmap() 481 { 482 if (fOffsreenBitmap) { 483 delete fOffsreenBitmap; 484 fOffsreenBitmap = NULL; 485 fOffsreenView = NULL; 486 } 487 } 488 489 // _DrawInto 490 void 491 CanvasView::_DrawInto(BView* view, BRect updateRect) 492 { 493 if (fDirtyIconArea.IsValid()) { 494 fRenderer->Render(fDirtyIconArea); 495 fDirtyIconArea.Set(LONG_MAX, LONG_MAX, LONG_MIN, LONG_MIN); 496 } 497 498 // icon 499 BRect canvas(_CanvasRect()); 500 view->DrawBitmap(fBitmap, fBitmap->Bounds(), canvas); 501 502 // grid 503 int32 gridLines = 0; 504 int32 scale = 1; 505 switch (fMouseFilterMode) { 506 case SNAPPING_64: 507 gridLines = 63; 508 break; 509 case SNAPPING_32: 510 gridLines = 31; 511 scale = 2; 512 break; 513 case SNAPPING_16: 514 gridLines = 15; 515 scale = 4; 516 break; 517 case SNAPPING_OFF: 518 default: 519 break; 520 } 521 view->SetDrawingMode(B_OP_BLEND); 522 for (int32 i = 1; i <= gridLines; i++) { 523 BPoint cross(i * scale, i * scale); 524 ConvertFromCanvas(&cross); 525 view->StrokeLine(BPoint(canvas.left, cross.y), 526 BPoint(canvas.right, cross.y)); 527 view->StrokeLine(BPoint(cross.x, canvas.top), 528 BPoint(cross.x, canvas.bottom)); 529 } 530 view->SetDrawingMode(B_OP_COPY); 531 532 // outside icon 533 BRegion outside(Bounds() & updateRect); 534 outside.Exclude(canvas); 535 view->FillRegion(&outside, kStripes); 536 537 StateView::Draw(view, updateRect); 538 } 539 540 // _MakeBackground 541 void 542 CanvasView::_MakeBackground() 543 { 544 uint8* row = (uint8*)fBackground->Bits(); 545 uint32 bpr = fBackground->BytesPerRow(); 546 uint32 width = fBackground->Bounds().IntegerWidth() + 1; 547 uint32 height = fBackground->Bounds().IntegerHeight() + 1; 548 549 const GammaTable& lut = fRenderer->GammaTable(); 550 uint8 redLow = lut.dir(kAlphaLow.red); 551 uint8 greenLow = lut.dir(kAlphaLow.blue); 552 uint8 blueLow = lut.dir(kAlphaLow.green); 553 uint8 redHigh = lut.dir(kAlphaHigh.red); 554 uint8 greenHigh = lut.dir(kAlphaHigh.blue); 555 uint8 blueHigh = lut.dir(kAlphaHigh.green); 556 557 for (uint32 y = 0; y < height; y++) { 558 uint8* p = row; 559 for (uint32 x = 0; x < width; x++) { 560 p[3] = 255; 561 if (x % 8 >= 4) { 562 if (y % 8 >= 4) { 563 p[0] = blueLow; 564 p[1] = greenLow; 565 p[2] = redLow; 566 } else { 567 p[0] = blueHigh; 568 p[1] = greenHigh; 569 p[2] = redHigh; 570 } 571 } else { 572 if (y % 8 >= 4) { 573 p[0] = blueHigh; 574 p[1] = greenHigh; 575 p[2] = redHigh; 576 } else { 577 p[0] = blueLow; 578 p[1] = greenLow; 579 p[2] = redLow; 580 } 581 } 582 p += 4; 583 } 584 row += bpr; 585 } 586 } 587 588 // _UpdateToolCursor 589 void 590 CanvasView::_UpdateToolCursor() 591 { 592 if (fIcon) { 593 if (fScrollTracking || fSpaceHeldDown) { 594 // indicate scrolling mode 595 const uchar* cursorData = fScrollTracking ? kGrabCursor : kHandCursor; 596 BCursor cursor(cursorData); 597 SetViewCursor(&cursor, true); 598 } else { 599 // pass on to current state of StateView 600 UpdateStateCursor(); 601 } 602 } else { 603 BCursor cursor(kStopCursor); 604 SetViewCursor(&cursor, true); 605 } 606 } 607 608 // #pragma mark - 609 610 // _NextZoomInLevel 611 double 612 CanvasView::_NextZoomInLevel(double zoom) const 613 { 614 if (zoom < 1) 615 return 1; 616 if (zoom < 1.5) 617 return 1.5; 618 if (zoom < 2) 619 return 2; 620 if (zoom < 3) 621 return 3; 622 if (zoom < 4) 623 return 4; 624 if (zoom < 6) 625 return 6; 626 if (zoom < 8) 627 return 8; 628 if (zoom < 16) 629 return 16; 630 if (zoom < 32) 631 return 32; 632 return 64; 633 } 634 635 // _NextZoomOutLevel 636 double 637 CanvasView::_NextZoomOutLevel(double zoom) const 638 { 639 if (zoom > 32) 640 return 32; 641 if (zoom > 16) 642 return 16; 643 if (zoom > 8) 644 return 8; 645 if (zoom > 6) 646 return 6; 647 if (zoom > 4) 648 return 4; 649 if (zoom > 3) 650 return 3; 651 if (zoom > 2) 652 return 2; 653 if (zoom > 1.5) 654 return 1.5; 655 return 1; 656 } 657 658 // _SetZoom 659 void 660 CanvasView::_SetZoom(double zoomLevel, bool mouseIsAnchor) 661 { 662 if (fZoomLevel == zoomLevel) 663 return; 664 665 // TODO: still not working 100% correctly 666 667 // zoom into center of view 668 BRect bounds(Bounds()); 669 BPoint anchor; 670 anchor.x = (bounds.left + bounds.right + 1) / 2.0; 671 anchor.y = (bounds.top + bounds.bottom + 1) / 2.0; 672 673 BPoint canvasAnchor = anchor; 674 ConvertToCanvas(&canvasAnchor); 675 676 fZoomLevel = zoomLevel; 677 SetDataRect(_LayoutCanvas()); 678 679 ConvertFromCanvas(&canvasAnchor); 680 681 BPoint offset; 682 offset.x = roundf(canvasAnchor.x - anchor.x); 683 offset.y = roundf(canvasAnchor.y - anchor.y); 684 685 SetScrollOffset(ScrollOffset() + offset); 686 687 Invalidate(); 688 } 689 690 // _LayoutCanvas 691 BRect 692 CanvasView::_LayoutCanvas() 693 { 694 if (!fBitmap) 695 return BRect(0, 0, -1, -1); 696 697 // size of zoomed bitmap 698 BRect r(fBitmap->Bounds()); 699 r.OffsetTo(0, 0); 700 r.right = floorf((r.Width() + 1) * fZoomLevel + 0.5) - 1; 701 r.bottom = floorf((r.Height() + 1) * fZoomLevel + 0.5) - 1; 702 703 // TODO: ask manipulators to extend size 704 705 BRect bitmapRect = r; 706 707 // resize for empty area around bitmap 708 // (the size we want, but might still be much smaller than view) 709 r.right += r.Width() * 0.5; 710 r.bottom += r.Height() * 0.5; 711 712 // left top of canvas within empty area 713 BRect bounds(Bounds()); 714 bounds.OffsetTo(B_ORIGIN); 715 bounds = bounds | r; 716 fCanvasOrigin.x = floorf((bounds.left + bounds.right 717 - bitmapRect.right - bitmapRect.left) / 2.0 + 0.5); 718 fCanvasOrigin.y = floorf((bounds.top + bounds.bottom 719 - bitmapRect.bottom - bitmapRect.top) / 2.0 + 0.5); 720 721 return bounds; 722 } 723 724