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 fMousePos = where; 205 fCurrentState = _DragStateFor(where, ZoomLevel()); 206 fCurrentState->UpdateViewCursor(fView, fMousePos); 207 208 return true; 209 } 210 211 212 // DoubleClicked 213 bool 214 TransformBox::DoubleClicked(BPoint where) 215 { 216 return false; 217 } 218 219 220 // #pragma mark - 221 222 223 // Bounds 224 BRect 225 TransformBox::Bounds() 226 { 227 // convert from canvas view coordinates 228 BPoint lt = fLeftTop; 229 BPoint rt = fRightTop; 230 BPoint lb = fLeftBottom; 231 BPoint rb = fRightBottom; 232 BPoint c = fPivot; 233 234 TransformFromCanvas(lt); 235 TransformFromCanvas(rt); 236 TransformFromCanvas(lb); 237 TransformFromCanvas(rb); 238 TransformFromCanvas(c); 239 240 BRect r; 241 r.left = min5(lt.x, rt.x, lb.x, rb.x, c.x); 242 r.top = min5(lt.y, rt.y, lb.y, rb.y, c.y); 243 r.right = max5(lt.x, rt.x, lb.x, rb.x, c.x); 244 r.bottom = max5(lt.y, rt.y, lb.y, rb.y, c.y); 245 return r; 246 } 247 248 249 // TrackingBounds 250 BRect 251 TransformBox::TrackingBounds(BView* withinView) 252 { 253 return withinView->Bounds(); 254 } 255 256 257 // #pragma mark - 258 259 260 // ModifiersChanged 261 void 262 TransformBox::ModifiersChanged(uint32 modifiers) 263 { 264 fModifiers = modifiers; 265 if (fDragging && fCurrentState) { 266 fCurrentState->DragTo(fMousePos, fModifiers); 267 } 268 } 269 270 271 // HandleKeyDown 272 bool 273 TransformBox::HandleKeyDown(uint32 key, uint32 modifiers, Command** _command) 274 { 275 bool handled = true; 276 BPoint translation(B_ORIGIN); 277 278 float offset = 1.0; 279 if (modifiers & B_SHIFT_KEY) 280 offset /= ZoomLevel(); 281 282 switch (key) { 283 case B_UP_ARROW: 284 translation.y = -offset; 285 break; 286 case B_DOWN_ARROW: 287 translation.y = offset; 288 break; 289 case B_LEFT_ARROW: 290 translation.x = -offset; 291 break; 292 case B_RIGHT_ARROW: 293 translation.x = offset; 294 break; 295 296 default: 297 handled = false; 298 break; 299 } 300 301 if (!handled) 302 return false; 303 304 if (!fCurrentCommand) { 305 fCurrentCommand = MakeCommand("Translate", -1); 306 } 307 308 TranslateBy(translation); 309 310 return true; 311 } 312 313 314 // HandleKeyUp 315 bool 316 TransformBox::HandleKeyUp(uint32 key, uint32 modifiers, Command** _command) 317 { 318 if (fCurrentCommand) { 319 *_command = FinishTransaction(); 320 return true; 321 } 322 return false; 323 } 324 325 326 // UpdateCursor 327 bool 328 TransformBox::UpdateCursor() 329 { 330 if (fCurrentState) { 331 fCurrentState->UpdateViewCursor(fView, fMousePos); 332 return true; 333 } 334 return false; 335 } 336 337 338 // #pragma mark - 339 340 341 // AttachedToView 342 void 343 TransformBox::AttachedToView(BView* view) 344 { 345 view->Invalidate(Bounds().InsetByCopy(-INSET, -INSET)); 346 } 347 348 349 // DetachedFromView 350 void 351 TransformBox::DetachedFromView(BView* view) 352 { 353 view->Invalidate(Bounds().InsetByCopy(-INSET, -INSET)); 354 } 355 356 357 // pragma mark - 358 359 360 // Update 361 void 362 TransformBox::Update(bool deep) 363 { 364 // recalculate the points from the original box 365 fLeftTop = fOriginalBox.LeftTop(); 366 fRightTop = fOriginalBox.RightTop(); 367 fLeftBottom = fOriginalBox.LeftBottom(); 368 fRightBottom = fOriginalBox.RightBottom(); 369 370 fPivot.x = (fLeftTop.x + fRightBottom.x) / 2.0; 371 fPivot.y = (fLeftTop.y + fRightBottom.y) / 2.0; 372 373 fPivot += fPivotOffset; 374 375 // transform the points for display 376 Transform(&fLeftTop); 377 Transform(&fRightTop); 378 Transform(&fLeftBottom); 379 Transform(&fRightBottom); 380 381 Transform(&fPivot); 382 } 383 384 385 // OffsetCenter 386 void 387 TransformBox::OffsetCenter(BPoint offset) 388 { 389 if (offset != BPoint(0.0, 0.0)) { 390 fPivotOffset += offset; 391 Update(false); 392 } 393 } 394 395 396 // Center 397 BPoint 398 TransformBox::Center() const 399 { 400 return fPivot; 401 } 402 403 404 // SetBox 405 void 406 TransformBox::SetBox(BRect box) 407 { 408 if (fOriginalBox != box) { 409 fOriginalBox = box; 410 Update(false); 411 } 412 } 413 414 415 // FinishTransaction 416 Command* 417 TransformBox::FinishTransaction() 418 { 419 Command* command = fCurrentCommand; 420 if (fCurrentCommand) { 421 fCurrentCommand->SetNewTransformation(Pivot(), Translation(), 422 LocalRotation(), LocalXScale(), LocalYScale()); 423 fCurrentCommand = NULL; 424 } 425 return command; 426 } 427 428 429 // NudgeBy 430 void 431 TransformBox::NudgeBy(BPoint offset) 432 { 433 if (!fNudging && !fCurrentCommand) { 434 fCurrentCommand = MakeCommand("Move", 0/*MOVE*/); 435 fNudging = true; 436 } 437 if (fNudging) { 438 TranslateBy(offset); 439 } 440 } 441 442 443 // FinishNudging 444 Command* 445 TransformBox::FinishNudging() 446 { 447 fNudging = false; 448 return FinishTransaction(); 449 } 450 451 452 // TransformFromCanvas 453 void 454 TransformBox::TransformFromCanvas(BPoint& point) const 455 { 456 } 457 458 459 // TransformToCanvas 460 void 461 TransformBox::TransformToCanvas(BPoint& point) const 462 { 463 } 464 465 466 // ZoomLevel 467 float 468 TransformBox::ZoomLevel() const 469 { 470 return 1.0; 471 } 472 473 474 // ViewSpaceRotation 475 double 476 TransformBox::ViewSpaceRotation() const 477 { 478 // assume no inherited transformation 479 return LocalRotation(); 480 } 481 482 483 // #pragma mark - 484 485 486 // AddListener 487 bool 488 TransformBox::AddListener(TransformBoxListener* listener) 489 { 490 if (listener && !fListeners.HasItem((void*)listener)) 491 return fListeners.AddItem((void*)listener); 492 return false; 493 } 494 495 496 // RemoveListener 497 bool 498 TransformBox::RemoveListener(TransformBoxListener* listener) 499 { 500 return fListeners.RemoveItem((void*)listener); 501 } 502 503 504 // #pragma mark - 505 506 507 // TODO: why another version? 508 // point_line_dist 509 float 510 point_line_dist(BPoint start, BPoint end, BPoint p, float radius) 511 { 512 BRect r(min_c(start.x, end.x), min_c(start.y, end.y), max_c(start.x, end.x), 513 max_c(start.y, end.y)); 514 r.InsetBy(-radius, -radius); 515 if (r.Contains(p)) { 516 return fabs(agg::calc_line_point_distance(start.x, start.y, end.x, end.y, 517 p.x, p.y)); 518 } 519 520 return min_c(point_point_distance(start, p), point_point_distance(end, p)); 521 } 522 523 524 // _DragStateFor 525 //! where is expected in canvas view coordinates 526 DragState* 527 TransformBox::_DragStateFor(BPoint where, float canvasZoom) 528 { 529 DragState* state = NULL; 530 // convert to canvas zoom level 531 // 532 // the conversion is necessary, because the "hot regions" 533 // around a point should be the same size no matter what 534 // zoom level the canvas is displayed at 535 536 float inset = INSET / canvasZoom; 537 538 // priorities: 539 // transformation center point has highest priority ?!? 540 if (point_point_distance(where, fPivot) < inset) 541 state = fOffsetCenterState; 542 543 if (!state) { 544 // next, the inner area of the box 545 546 // for the following calculations 547 // we can apply the inverse transformation to all points 548 // this way we have to consider BRects only, not transformed polygons 549 BPoint lt = fLeftTop; 550 BPoint rb = fRightBottom; 551 BPoint w = where; 552 553 InverseTransform(&w); 554 InverseTransform(<); 555 InverseTransform(&rb); 556 557 // next priority has the inside of the box 558 BRect iR(lt, rb); 559 float hInset = min_c(inset, max_c(0, (iR.Width() - inset) / 2.0)); 560 float vInset = min_c(inset, max_c(0, (iR.Height() - inset) / 2.0)); 561 562 iR.InsetBy(hInset, vInset); 563 if (iR.Contains(w)) 564 state = fTranslateState; 565 } 566 567 if (!state) { 568 // next priority have the corners 569 float dLT = point_point_distance(fLeftTop, where); 570 float dRT = point_point_distance(fRightTop, where); 571 float dLB = point_point_distance(fLeftBottom, where); 572 float dRB = point_point_distance(fRightBottom, where); 573 float d = min4(dLT, dRT, dLB, dRB); 574 if (d < inset) { 575 if (d == dLT) 576 state = fDragLTState; 577 else if (d == dRT) 578 state = fDragRTState; 579 else if (d == dLB) 580 state = fDragLBState; 581 else if (d == dRB) 582 state = fDragRBState; 583 } 584 } 585 586 if (!state) { 587 // next priority have the sides 588 float dL = point_line_dist(fLeftTop, fLeftBottom, where, inset); 589 float dR = point_line_dist(fRightTop, fRightBottom, where, inset); 590 float dT = point_line_dist(fLeftTop, fRightTop, where, inset); 591 float dB = point_line_dist(fLeftBottom, fRightBottom, where, inset); 592 float d = min4(dL, dR, dT, dB); 593 if (d < inset) { 594 if (d == dL) 595 state = fDragLState; 596 else if (d == dR) 597 state = fDragRState; 598 else if (d == dT) 599 state = fDragTState; 600 else if (d == dB) 601 state = fDragBState; 602 } 603 } 604 605 if (!state) { 606 BPoint lt = fLeftTop; 607 BPoint rb = fRightBottom; 608 BPoint w = where; 609 610 InverseTransform(&w); 611 InverseTransform(<); 612 InverseTransform(&rb); 613 614 // check inside of the box again 615 BRect iR(lt, rb); 616 if (iR.Contains(w)) { 617 state = fTranslateState; 618 } else { 619 // last priority has the rotate state 620 state = fRotateState; 621 } 622 } 623 624 return state; 625 } 626 627 628 // _StrokeBWLine 629 void 630 TransformBox::_StrokeBWLine(BView* into, BPoint from, BPoint to) const 631 { 632 // find out how to offset the second line optimally 633 BPoint offset(0.0, 0.0); 634 // first, do we have a more horizontal line or a more vertical line? 635 float xDiff = to.x - from.x; 636 float yDiff = to.y - from.y; 637 if (fabs(xDiff) > fabs(yDiff)) { 638 // horizontal 639 if (xDiff > 0.0) { 640 offset.y = -1.0; 641 } else { 642 offset.y = 1.0; 643 } 644 } else { 645 // vertical 646 if (yDiff < 0.0) { 647 offset.x = -1.0; 648 } else { 649 offset.x = 1.0; 650 } 651 } 652 // stroke two lines in high and low color of the view 653 into->StrokeLine(from, to, B_SOLID_LOW); 654 from += offset; 655 to += offset; 656 into->StrokeLine(from, to, B_SOLID_HIGH); 657 } 658 659 660 // _StrokeBWPoint 661 void 662 TransformBox::_StrokeBWPoint(BView* into, BPoint point, double angle) const 663 { 664 double x = point.x; 665 double y = point.y; 666 667 double x1 = x; 668 double y1 = y - 5.0; 669 670 double x2 = x - 5.0; 671 double y2 = y - 5.0; 672 673 double x3 = x - 5.0; 674 double y3 = y; 675 676 agg::trans_affine m; 677 678 double xOffset = -x; 679 double yOffset = -y; 680 681 agg::trans_affine_rotation r(angle * M_PI / 180.0); 682 683 r.transform(&xOffset, &yOffset); 684 xOffset = x + xOffset; 685 yOffset = y + yOffset; 686 687 m.multiply(r); 688 m.multiply(agg::trans_affine_translation(xOffset, yOffset)); 689 690 m.transform(&x, &y); 691 m.transform(&x1, &y1); 692 m.transform(&x2, &y2); 693 m.transform(&x3, &y3); 694 695 BPoint p[4]; 696 p[0] = BPoint(x, y); 697 p[1] = BPoint(x1, y1); 698 p[2] = BPoint(x2, y2); 699 p[3] = BPoint(x3, y3); 700 701 into->FillPolygon(p, 4, B_SOLID_HIGH); 702 703 into->StrokeLine(p[0], p[1], B_SOLID_LOW); 704 into->StrokeLine(p[1], p[2], B_SOLID_LOW); 705 into->StrokeLine(p[2], p[3], B_SOLID_LOW); 706 into->StrokeLine(p[3], p[0], B_SOLID_LOW); 707 } 708 709 710 // #pragma mark - 711 712 713 // _NotifyDeleted 714 void 715 TransformBox::_NotifyDeleted() const 716 { 717 BList listeners(fListeners); 718 int32 count = listeners.CountItems(); 719 for (int32 i = 0; i < count; i++) { 720 TransformBoxListener* listener 721 = (TransformBoxListener*)listeners.ItemAtFast(i); 722 listener->TransformBoxDeleted(this); 723 } 724 } 725 726