1 /* 2 * Copyright 2006-2009, Haiku. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Stephan Aßmus <superstippi@gmx.de> 7 */ 8 9 #include "TransformBox.h" 10 11 #include <stdio.h> 12 13 #include <agg_trans_affine.h> 14 #include <agg_math.h> 15 16 #include <View.h> 17 18 #include "support.h" 19 20 #include "TransformBoxStates.h" 21 #include "StateView.h" 22 #include "TransformCommand.h" 23 24 25 #define INSET 8.0 26 27 28 TransformBoxListener::TransformBoxListener() 29 { 30 } 31 32 33 TransformBoxListener::~TransformBoxListener() 34 { 35 } 36 37 38 // #pragma mark - 39 40 41 // constructor 42 TransformBox::TransformBox(StateView* view, BRect box) 43 : 44 ChannelTransform(), 45 Manipulator(NULL), 46 fOriginalBox(box), 47 48 fLeftTop(box.LeftTop()), 49 fRightTop(box.RightTop()), 50 fLeftBottom(box.LeftBottom()), 51 fRightBottom(box.RightBottom()), 52 53 fPivot((fLeftTop.x + fRightBottom.x) / 2.0, 54 (fLeftTop.y + fRightBottom.y) / 2.0), 55 fPivotOffset(B_ORIGIN), 56 fCurrentCommand(NULL), 57 fCurrentState(NULL), 58 59 fDragging(false), 60 fMousePos(-10000.0, -10000.0), 61 fModifiers(0), 62 63 fNudging(false), 64 65 fView(view), 66 67 fDragLTState(new DragCornerState(this, DragCornerState::LEFT_TOP_CORNER)), 68 fDragRTState(new DragCornerState(this, DragCornerState::RIGHT_TOP_CORNER)), 69 fDragLBState(new DragCornerState(this, DragCornerState::LEFT_BOTTOM_CORNER)), 70 fDragRBState(new DragCornerState(this, DragCornerState::RIGHT_BOTTOM_CORNER)), 71 72 fDragLState(new DragSideState(this, DragSideState::LEFT_SIDE)), 73 fDragRState(new DragSideState(this, DragSideState::RIGHT_SIDE)), 74 fDragTState(new DragSideState(this, DragSideState::TOP_SIDE)), 75 fDragBState(new DragSideState(this, DragSideState::BOTTOM_SIDE)), 76 77 fRotateState(new RotateBoxState(this)), 78 fTranslateState(new DragBoxState(this)), 79 fOffsetCenterState(new OffsetCenterState(this)) 80 { 81 } 82 83 84 // destructor 85 TransformBox::~TransformBox() 86 { 87 _NotifyDeleted(); 88 89 delete fCurrentCommand; 90 91 delete fDragLTState; 92 delete fDragRTState; 93 delete fDragLBState; 94 delete fDragRBState; 95 96 delete fDragLState; 97 delete fDragRState; 98 delete fDragTState; 99 delete fDragBState; 100 101 delete fRotateState; 102 delete fTranslateState; 103 delete fOffsetCenterState; 104 } 105 106 107 // Draw 108 void 109 TransformBox::Draw(BView* into, BRect updateRect) 110 { 111 // convert to canvas view coordinates 112 BPoint lt = fLeftTop; 113 BPoint rt = fRightTop; 114 BPoint lb = fLeftBottom; 115 BPoint rb = fRightBottom; 116 BPoint c = fPivot; 117 118 TransformFromCanvas(lt); 119 TransformFromCanvas(rt); 120 TransformFromCanvas(lb); 121 TransformFromCanvas(rb); 122 TransformFromCanvas(c); 123 124 into->SetDrawingMode(B_OP_COPY); 125 into->SetHighColor(255, 255, 255, 255); 126 into->SetLowColor(0, 0, 0, 255); 127 _StrokeBWLine(into, lt, rt); 128 _StrokeBWLine(into, rt, rb); 129 _StrokeBWLine(into, rb, lb); 130 _StrokeBWLine(into, lb, lt); 131 132 double rotation = ViewSpaceRotation(); 133 _StrokeBWPoint(into, lt, rotation); 134 _StrokeBWPoint(into, rt, rotation + 90.0); 135 _StrokeBWPoint(into, rb, rotation + 180.0); 136 _StrokeBWPoint(into, lb, rotation + 270.0); 137 138 BRect cr(c, c); 139 cr.InsetBy(-3.0, -3.0); 140 into->StrokeEllipse(cr, B_SOLID_HIGH); 141 cr.InsetBy(1.0, 1.0); 142 into->StrokeEllipse(cr, B_SOLID_LOW); 143 into->SetDrawingMode(B_OP_COPY); 144 } 145 146 147 // #pragma mark - 148 149 150 // MouseDown 151 bool 152 TransformBox::MouseDown(BPoint where) 153 { 154 fView->FilterMouse(&where); 155 // NOTE: filter mouse here and in MouseMoved only 156 TransformToCanvas(where); 157 158 fDragging = true; 159 if (fCurrentState) { 160 fCurrentState->SetOrigin(where); 161 162 delete fCurrentCommand; 163 fCurrentCommand = MakeCommand(fCurrentState->ActionName(), 164 fCurrentState->ActionNameIndex()); 165 } 166 167 return true; 168 } 169 170 171 // MouseMoved 172 void 173 TransformBox::MouseMoved(BPoint where) 174 { 175 fView->FilterMouse(&where); 176 // NOTE: filter mouse here and in MouseDown only 177 TransformToCanvas(where); 178 179 if (fMousePos != where) { 180 fMousePos = where; 181 if (fCurrentState) { 182 fCurrentState->DragTo(fMousePos, fModifiers); 183 fCurrentState->UpdateViewCursor(fView, fMousePos); 184 } 185 } 186 } 187 188 189 // MouseUp 190 Command* 191 TransformBox::MouseUp() 192 { 193 fDragging = false; 194 return FinishTransaction(); 195 } 196 197 198 // MouseOver 199 bool 200 TransformBox::MouseOver(BPoint where) 201 { 202 TransformToCanvas(where); 203 204 _SetState(_DragStateFor(where, ZoomLevel())); 205 fMousePos = where; 206 if (fCurrentState) { 207 fCurrentState->UpdateViewCursor(fView, fMousePos); 208 return true; 209 } 210 return false; 211 } 212 213 214 // DoubleClicked 215 bool 216 TransformBox::DoubleClicked(BPoint where) 217 { 218 return false; 219 } 220 221 222 // #pragma mark - 223 224 225 // Bounds 226 BRect 227 TransformBox::Bounds() 228 { 229 // convert from canvas view coordinates 230 BPoint lt = fLeftTop; 231 BPoint rt = fRightTop; 232 BPoint lb = fLeftBottom; 233 BPoint rb = fRightBottom; 234 BPoint c = fPivot; 235 236 TransformFromCanvas(lt); 237 TransformFromCanvas(rt); 238 TransformFromCanvas(lb); 239 TransformFromCanvas(rb); 240 TransformFromCanvas(c); 241 242 BRect r; 243 r.left = min5(lt.x, rt.x, lb.x, rb.x, c.x); 244 r.top = min5(lt.y, rt.y, lb.y, rb.y, c.y); 245 r.right = max5(lt.x, rt.x, lb.x, rb.x, c.x); 246 r.bottom = max5(lt.y, rt.y, lb.y, rb.y, c.y); 247 return r; 248 } 249 250 251 // TrackingBounds 252 BRect 253 TransformBox::TrackingBounds(BView* withinView) 254 { 255 return withinView->Bounds(); 256 } 257 258 259 // #pragma mark - 260 261 262 // ModifiersChanged 263 void 264 TransformBox::ModifiersChanged(uint32 modifiers) 265 { 266 fModifiers = modifiers; 267 if (fDragging && fCurrentState) { 268 fCurrentState->DragTo(fMousePos, fModifiers); 269 } 270 } 271 272 273 // HandleKeyDown 274 bool 275 TransformBox::HandleKeyDown(uint32 key, uint32 modifiers, Command** _command) 276 { 277 bool handled = true; 278 BPoint translation(B_ORIGIN); 279 280 float offset = 1.0; 281 if (modifiers & B_SHIFT_KEY) 282 offset /= ZoomLevel(); 283 284 switch (key) { 285 case B_UP_ARROW: 286 translation.y = -offset; 287 break; 288 case B_DOWN_ARROW: 289 translation.y = offset; 290 break; 291 case B_LEFT_ARROW: 292 translation.x = -offset; 293 break; 294 case B_RIGHT_ARROW: 295 translation.x = offset; 296 break; 297 298 default: 299 handled = false; 300 break; 301 } 302 303 if (!handled) 304 return false; 305 306 if (!fCurrentCommand) { 307 fCurrentCommand = MakeCommand("Translate", -1); 308 } 309 310 TranslateBy(translation); 311 312 return true; 313 } 314 315 316 // HandleKeyUp 317 bool 318 TransformBox::HandleKeyUp(uint32 key, uint32 modifiers, Command** _command) 319 { 320 if (fCurrentCommand) { 321 *_command = FinishTransaction(); 322 return true; 323 } 324 return false; 325 } 326 327 328 // UpdateCursor 329 bool 330 TransformBox::UpdateCursor() 331 { 332 if (fCurrentState) { 333 fCurrentState->UpdateViewCursor(fView, fMousePos); 334 return true; 335 } 336 return false; 337 } 338 339 340 // #pragma mark - 341 342 343 // AttachedToView 344 void 345 TransformBox::AttachedToView(BView* view) 346 { 347 view->Invalidate(Bounds().InsetByCopy(-INSET, -INSET)); 348 } 349 350 351 // DetachedFromView 352 void 353 TransformBox::DetachedFromView(BView* view) 354 { 355 view->Invalidate(Bounds().InsetByCopy(-INSET, -INSET)); 356 } 357 358 359 // pragma mark - 360 361 362 // Update 363 void 364 TransformBox::Update(bool deep) 365 { 366 // recalculate the points from the original box 367 fLeftTop = fOriginalBox.LeftTop(); 368 fRightTop = fOriginalBox.RightTop(); 369 fLeftBottom = fOriginalBox.LeftBottom(); 370 fRightBottom = fOriginalBox.RightBottom(); 371 372 fPivot.x = (fLeftTop.x + fRightBottom.x) / 2.0; 373 fPivot.y = (fLeftTop.y + fRightBottom.y) / 2.0; 374 375 fPivot += fPivotOffset; 376 377 // transform the points for display 378 Transform(&fLeftTop); 379 Transform(&fRightTop); 380 Transform(&fLeftBottom); 381 Transform(&fRightBottom); 382 383 Transform(&fPivot); 384 } 385 386 387 // OffsetCenter 388 void 389 TransformBox::OffsetCenter(BPoint offset) 390 { 391 if (offset != BPoint(0.0, 0.0)) { 392 fPivotOffset += offset; 393 Update(false); 394 } 395 } 396 397 398 // Center 399 BPoint 400 TransformBox::Center() const 401 { 402 return fPivot; 403 } 404 405 406 // SetBox 407 void 408 TransformBox::SetBox(BRect box) 409 { 410 if (fOriginalBox != box) { 411 fOriginalBox = box; 412 Update(false); 413 } 414 } 415 416 417 // FinishTransaction 418 Command* 419 TransformBox::FinishTransaction() 420 { 421 Command* command = fCurrentCommand; 422 if (fCurrentCommand) { 423 fCurrentCommand->SetNewTransformation(Pivot(), Translation(), 424 LocalRotation(), LocalXScale(), LocalYScale()); 425 fCurrentCommand = NULL; 426 } 427 return command; 428 } 429 430 431 // NudgeBy 432 void 433 TransformBox::NudgeBy(BPoint offset) 434 { 435 if (!fNudging && !fCurrentCommand) { 436 fCurrentCommand = MakeCommand("Move", 0/*MOVE*/); 437 fNudging = true; 438 } 439 if (fNudging) { 440 TranslateBy(offset); 441 } 442 } 443 444 445 // FinishNudging 446 Command* 447 TransformBox::FinishNudging() 448 { 449 fNudging = false; 450 return FinishTransaction(); 451 } 452 453 454 // TransformFromCanvas 455 void 456 TransformBox::TransformFromCanvas(BPoint& point) const 457 { 458 } 459 460 461 // TransformToCanvas 462 void 463 TransformBox::TransformToCanvas(BPoint& point) const 464 { 465 } 466 467 468 // ZoomLevel 469 float 470 TransformBox::ZoomLevel() const 471 { 472 return 1.0; 473 } 474 475 476 // ViewSpaceRotation 477 double 478 TransformBox::ViewSpaceRotation() const 479 { 480 // assume no inherited transformation 481 return LocalRotation(); 482 } 483 484 485 // #pragma mark - 486 487 488 // AddListener 489 bool 490 TransformBox::AddListener(TransformBoxListener* listener) 491 { 492 if (listener && !fListeners.HasItem((void*)listener)) 493 return fListeners.AddItem((void*)listener); 494 return false; 495 } 496 497 498 // RemoveListener 499 bool 500 TransformBox::RemoveListener(TransformBoxListener* listener) 501 { 502 return fListeners.RemoveItem((void*)listener); 503 } 504 505 506 // #pragma mark - 507 508 509 // TODO: why another version? 510 // point_line_dist 511 float 512 point_line_dist(BPoint start, BPoint end, BPoint p, float radius) 513 { 514 BRect r(min_c(start.x, end.x), min_c(start.y, end.y), max_c(start.x, end.x), 515 max_c(start.y, end.y)); 516 r.InsetBy(-radius, -radius); 517 if (r.Contains(p)) { 518 return fabs(agg::calc_line_point_distance(start.x, start.y, end.x, end.y, 519 p.x, p.y)); 520 } 521 522 return min_c(point_point_distance(start, p), point_point_distance(end, p)); 523 } 524 525 526 // _DragStateFor 527 //! where is expected in canvas view coordinates 528 DragState* 529 TransformBox::_DragStateFor(BPoint where, float canvasZoom) 530 { 531 DragState* state = NULL; 532 // convert to canvas zoom level 533 // 534 // the conversion is necessary, because the "hot regions" 535 // around a point should be the same size no matter what 536 // zoom level the canvas is displayed at 537 538 float inset = INSET / canvasZoom; 539 540 // priorities: 541 // transformation center point has highest priority ?!? 542 if (point_point_distance(where, fPivot) < inset) 543 state = fOffsetCenterState; 544 545 if (!state) { 546 // next, the inner area of the box 547 548 // for the following calculations 549 // we can apply the inverse transformation to all points 550 // this way we have to consider BRects only, not transformed polygons 551 BPoint lt = fLeftTop; 552 BPoint rb = fRightBottom; 553 BPoint w = where; 554 555 InverseTransform(&w); 556 InverseTransform(<); 557 InverseTransform(&rb); 558 559 // next priority has the inside of the box 560 BRect iR(lt, rb); 561 float hInset = min_c(inset, max_c(0, (iR.Width() - inset) / 2.0)); 562 float vInset = min_c(inset, max_c(0, (iR.Height() - inset) / 2.0)); 563 564 iR.InsetBy(hInset, vInset); 565 if (iR.Contains(w)) 566 state = fTranslateState; 567 } 568 569 if (!state) { 570 // next priority have the corners 571 float dLT = point_point_distance(fLeftTop, where); 572 float dRT = point_point_distance(fRightTop, where); 573 float dLB = point_point_distance(fLeftBottom, where); 574 float dRB = point_point_distance(fRightBottom, where); 575 float d = min4(dLT, dRT, dLB, dRB); 576 if (d < inset) { 577 if (d == dLT) 578 state = fDragLTState; 579 else if (d == dRT) 580 state = fDragRTState; 581 else if (d == dLB) 582 state = fDragLBState; 583 else if (d == dRB) 584 state = fDragRBState; 585 } 586 } 587 588 if (!state) { 589 // next priority have the sides 590 float dL = point_line_dist(fLeftTop, fLeftBottom, where, inset); 591 float dR = point_line_dist(fRightTop, fRightBottom, where, inset); 592 float dT = point_line_dist(fLeftTop, fRightTop, where, inset); 593 float dB = point_line_dist(fLeftBottom, fRightBottom, where, inset); 594 float d = min4(dL, dR, dT, dB); 595 if (d < inset) { 596 if (d == dL) 597 state = fDragLState; 598 else if (d == dR) 599 state = fDragRState; 600 else if (d == dT) 601 state = fDragTState; 602 else if (d == dB) 603 state = fDragBState; 604 } 605 } 606 607 if (!state) { 608 BPoint lt = fLeftTop; 609 BPoint rb = fRightBottom; 610 BPoint w = where; 611 612 InverseTransform(&w); 613 InverseTransform(<); 614 InverseTransform(&rb); 615 616 // check inside of the box again 617 BRect iR(lt, rb); 618 if (iR.Contains(w)) { 619 state = fTranslateState; 620 } else { 621 // last priority has the rotate state 622 state = fRotateState; 623 } 624 } 625 626 return state; 627 } 628 629 630 // _StrokeBWLine 631 void 632 TransformBox::_StrokeBWLine(BView* into, BPoint from, BPoint to) const 633 { 634 // find out how to offset the second line optimally 635 BPoint offset(0.0, 0.0); 636 // first, do we have a more horizontal line or a more vertical line? 637 float xDiff = to.x - from.x; 638 float yDiff = to.y - from.y; 639 if (fabs(xDiff) > fabs(yDiff)) { 640 // horizontal 641 if (xDiff > 0.0) { 642 offset.y = -1.0; 643 } else { 644 offset.y = 1.0; 645 } 646 } else { 647 // vertical 648 if (yDiff < 0.0) { 649 offset.x = -1.0; 650 } else { 651 offset.x = 1.0; 652 } 653 } 654 // stroke two lines in high and low color of the view 655 into->StrokeLine(from, to, B_SOLID_LOW); 656 from += offset; 657 to += offset; 658 into->StrokeLine(from, to, B_SOLID_HIGH); 659 } 660 661 662 // _StrokeBWPoint 663 void 664 TransformBox::_StrokeBWPoint(BView* into, BPoint point, double angle) const 665 { 666 double x = point.x; 667 double y = point.y; 668 669 double x1 = x; 670 double y1 = y - 5.0; 671 672 double x2 = x - 5.0; 673 double y2 = y - 5.0; 674 675 double x3 = x - 5.0; 676 double y3 = y; 677 678 agg::trans_affine m; 679 680 double xOffset = -x; 681 double yOffset = -y; 682 683 agg::trans_affine_rotation r(angle * M_PI / 180.0); 684 685 r.transform(&xOffset, &yOffset); 686 xOffset = x + xOffset; 687 yOffset = y + yOffset; 688 689 m.multiply(r); 690 m.multiply(agg::trans_affine_translation(xOffset, yOffset)); 691 692 m.transform(&x, &y); 693 m.transform(&x1, &y1); 694 m.transform(&x2, &y2); 695 m.transform(&x3, &y3); 696 697 BPoint p[4]; 698 p[0] = BPoint(x, y); 699 p[1] = BPoint(x1, y1); 700 p[2] = BPoint(x2, y2); 701 p[3] = BPoint(x3, y3); 702 703 into->FillPolygon(p, 4, B_SOLID_HIGH); 704 705 into->StrokeLine(p[0], p[1], B_SOLID_LOW); 706 into->StrokeLine(p[1], p[2], B_SOLID_LOW); 707 into->StrokeLine(p[2], p[3], B_SOLID_LOW); 708 into->StrokeLine(p[3], p[0], B_SOLID_LOW); 709 } 710 711 712 // #pragma mark - 713 714 715 // _NotifyDeleted 716 void 717 TransformBox::_NotifyDeleted() const 718 { 719 BList listeners(fListeners); 720 int32 count = listeners.CountItems(); 721 for (int32 i = 0; i < count; i++) { 722 TransformBoxListener* listener 723 = (TransformBoxListener*)listeners.ItemAtFast(i); 724 listener->TransformBoxDeleted(this); 725 } 726 } 727 728 729 // #pragma mark - 730 731 732 // _SetState 733 void 734 TransformBox::_SetState(DragState* state) 735 { 736 if (state != fCurrentState) { 737 fCurrentState = state; 738 fCurrentState->UpdateViewCursor(fView, fMousePos); 739 } 740 } 741 742