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