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 24using std::nothrow; 25 26 27CanvasView::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 52CanvasView::~CanvasView() 53{ 54 SetIcon(NULL); 55 delete fRenderer; 56 delete fBitmap; 57 delete fBackground; 58} 59 60 61// #pragma mark - 62 63 64void 65CanvasView::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 88void 89CanvasView::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 99void 100CanvasView::Draw(BRect updateRect) 101{ 102 _DrawInto(this, updateRect); 103} 104 105 106// #pragma mark - 107 108 109void 110CanvasView::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 137void 138CanvasView::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 154void 155CanvasView::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 177void 178CanvasView::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 218bool 219CanvasView::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 238void 239CanvasView::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 259void 260CanvasView::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 273void 274CanvasView::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 285void 286CanvasView::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 302void 303CanvasView::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 319void 320CanvasView::SetMouseFilterMode(uint32 mode) 321{ 322 if (fMouseFilterMode == mode) 323 return; 324 325 fMouseFilterMode = mode; 326 Invalidate(_CanvasRect()); 327} 328 329 330void 331CanvasView::ConvertFromCanvas(BPoint* point) const 332{ 333 point->x = point->x * fZoomLevel + fCanvasOrigin.x; 334 point->y = point->y * fZoomLevel + fCanvasOrigin.y; 335} 336 337 338void 339CanvasView::ConvertToCanvas(BPoint* point) const 340{ 341 point->x = (point->x - fCanvasOrigin.x) / fZoomLevel; 342 point->y = (point->y - fCanvasOrigin.y) / fZoomLevel; 343} 344 345 346void 347CanvasView::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 360void 361CanvasView::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 373bool 374CanvasView::_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 405bool 406CanvasView::_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 422BRect 423CanvasView::_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 434void 435CanvasView::_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 485void 486CanvasView::_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 533void 534CanvasView::_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 556double 557CanvasView::_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 581double 582CanvasView::_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 604void 605CanvasView::_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 639BRect 640CanvasView::_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