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 28TransformBoxListener::TransformBoxListener() 29{ 30} 31 32 33TransformBoxListener::~TransformBoxListener() 34{ 35} 36 37 38// #pragma mark - 39 40 41// constructor 42TransformBox::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 85TransformBox::~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 108void 109TransformBox::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 151bool 152TransformBox::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 172void 173TransformBox::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 190Command* 191TransformBox::MouseUp() 192{ 193 fDragging = false; 194 return FinishTransaction(); 195} 196 197 198// MouseOver 199bool 200TransformBox::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 215bool 216TransformBox::DoubleClicked(BPoint where) 217{ 218 return false; 219} 220 221 222// #pragma mark - 223 224 225// Bounds 226BRect 227TransformBox::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 252BRect 253TransformBox::TrackingBounds(BView* withinView) 254{ 255 return withinView->Bounds(); 256} 257 258 259// #pragma mark - 260 261 262// ModifiersChanged 263void 264TransformBox::ModifiersChanged(uint32 modifiers) 265{ 266 fModifiers = modifiers; 267 if (fDragging && fCurrentState) { 268 fCurrentState->DragTo(fMousePos, fModifiers); 269 } 270} 271 272 273// HandleKeyDown 274bool 275TransformBox::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 317bool 318TransformBox::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 329bool 330TransformBox::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 344void 345TransformBox::AttachedToView(BView* view) 346{ 347 view->Invalidate(Bounds().InsetByCopy(-INSET, -INSET)); 348} 349 350 351// DetachedFromView 352void 353TransformBox::DetachedFromView(BView* view) 354{ 355 view->Invalidate(Bounds().InsetByCopy(-INSET, -INSET)); 356} 357 358 359// pragma mark - 360 361 362// Update 363void 364TransformBox::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 388void 389TransformBox::OffsetCenter(BPoint offset) 390{ 391 if (offset != BPoint(0.0, 0.0)) { 392 fPivotOffset += offset; 393 Update(false); 394 } 395} 396 397 398// Center 399BPoint 400TransformBox::Center() const 401{ 402 return fPivot; 403} 404 405 406// SetBox 407void 408TransformBox::SetBox(BRect box) 409{ 410 if (fOriginalBox != box) { 411 fOriginalBox = box; 412 Update(false); 413 } 414} 415 416 417// FinishTransaction 418Command* 419TransformBox::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 432void 433TransformBox::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 446Command* 447TransformBox::FinishNudging() 448{ 449 fNudging = false; 450 return FinishTransaction(); 451} 452 453 454// TransformFromCanvas 455void 456TransformBox::TransformFromCanvas(BPoint& point) const 457{ 458} 459 460 461// TransformToCanvas 462void 463TransformBox::TransformToCanvas(BPoint& point) const 464{ 465} 466 467 468// ZoomLevel 469float 470TransformBox::ZoomLevel() const 471{ 472 return 1.0; 473} 474 475 476// ViewSpaceRotation 477double 478TransformBox::ViewSpaceRotation() const 479{ 480 // assume no inherited transformation 481 return LocalRotation(); 482} 483 484 485// #pragma mark - 486 487 488// AddListener 489bool 490TransformBox::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 499bool 500TransformBox::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 511float 512point_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 528DragState* 529TransformBox::_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 631void 632TransformBox::_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 663void 664TransformBox::_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 716void 717TransformBox::_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 733void 734TransformBox::_SetState(DragState* state) 735{ 736 if (state != fCurrentState) { 737 fCurrentState = state; 738 fCurrentState->UpdateViewCursor(fView, fMousePos); 739 } 740} 741 742