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