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