1 /* 2 * Copyright 2006, 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 // #pragma mark - 303 304 // UpdateCursor 305 void 306 TransformBox::UpdateCursor() 307 { 308 if (fCurrentState) 309 fCurrentState->UpdateViewCursor(fView, fMousePos); 310 } 311 312 // AttachedToView 313 void 314 TransformBox::AttachedToView(BView* view) 315 { 316 view->Invalidate(Bounds().InsetByCopy(-INSET, -INSET)); 317 } 318 319 // DetachedFromView 320 void 321 TransformBox::DetachedFromView(BView* view) 322 { 323 view->Invalidate(Bounds().InsetByCopy(-INSET, -INSET)); 324 } 325 326 // pragma mark - 327 328 // Update 329 void 330 TransformBox::Update(bool deep) 331 { 332 // recalculate the points from the original box 333 fLeftTop = fOriginalBox.LeftTop(); 334 fRightTop = fOriginalBox.RightTop(); 335 fLeftBottom = fOriginalBox.LeftBottom(); 336 fRightBottom = fOriginalBox.RightBottom(); 337 338 fPivot.x = (fLeftTop.x + fRightBottom.x) / 2.0; 339 fPivot.y = (fLeftTop.y + fRightBottom.y) / 2.0; 340 341 fPivot += fPivotOffset; 342 343 // transform the points for display 344 Transform(&fLeftTop); 345 Transform(&fRightTop); 346 Transform(&fLeftBottom); 347 Transform(&fRightBottom); 348 349 Transform(&fPivot); 350 } 351 352 // OffsetCenter 353 void 354 TransformBox::OffsetCenter(BPoint offset) 355 { 356 if (offset != BPoint(0.0, 0.0)) { 357 fPivotOffset += offset; 358 Update(false); 359 } 360 } 361 362 // Center 363 BPoint 364 TransformBox::Center() const 365 { 366 return fPivot; 367 } 368 369 // SetBox 370 void 371 TransformBox::SetBox(BRect box) 372 { 373 if (fOriginalBox != box) { 374 fOriginalBox = box; 375 Update(false); 376 } 377 } 378 379 // FinishTransaction 380 Command* 381 TransformBox::FinishTransaction() 382 { 383 Command* command = fCurrentCommand; 384 if (fCurrentCommand) { 385 fCurrentCommand->SetNewTransformation(Pivot(), 386 Translation(), 387 LocalRotation(), 388 LocalXScale(), 389 LocalYScale()); 390 fCurrentCommand = NULL; 391 } 392 return command; 393 } 394 395 // NudgeBy 396 void 397 TransformBox::NudgeBy(BPoint offset) 398 { 399 if (!fNudging && !fCurrentCommand) { 400 fCurrentCommand = MakeCommand("Move", 0/*MOVE*/); 401 fNudging = true; 402 } 403 if (fNudging) { 404 TranslateBy(offset); 405 } 406 } 407 408 // FinishNudging 409 Command* 410 TransformBox::FinishNudging() 411 { 412 fNudging = false; 413 return FinishTransaction(); 414 } 415 416 // TransformFromCanvas 417 void 418 TransformBox::TransformFromCanvas(BPoint& point) const 419 { 420 } 421 422 // TransformToCanvas 423 void 424 TransformBox::TransformToCanvas(BPoint& point) const 425 { 426 } 427 428 // ZoomLevel 429 float 430 TransformBox::ZoomLevel() const 431 { 432 return 1.0; 433 } 434 435 // ViewSpaceRotation 436 double 437 TransformBox::ViewSpaceRotation() const 438 { 439 // assume no inherited transformation 440 return LocalRotation(); 441 } 442 443 // #pragma mark - 444 445 // AddListener 446 bool 447 TransformBox::AddListener(TransformBoxListener* listener) 448 { 449 if (listener && !fListeners.HasItem((void*)listener)) 450 return fListeners.AddItem((void*)listener); 451 return false; 452 } 453 454 // RemoveListener 455 bool 456 TransformBox::RemoveListener(TransformBoxListener* listener) 457 { 458 return fListeners.RemoveItem((void*)listener); 459 } 460 461 // #pragma mark - 462 463 // TODO: why another version? 464 // point_line_dist 465 float 466 point_line_dist(BPoint start, BPoint end, BPoint p, float radius) 467 { 468 BRect r(min_c(start.x, end.x), 469 min_c(start.y, end.y), 470 max_c(start.x, end.x), 471 max_c(start.y, end.y)); 472 r.InsetBy(-radius, -radius); 473 if (r.Contains(p)) { 474 return fabs(agg::calc_line_point_distance(start.x, start.y, 475 end.x, end.y, 476 p.x, p.y)); 477 } 478 return min_c(point_point_distance(start, p), 479 point_point_distance(end, p)); 480 } 481 482 // _DragStateFor 483 // 484 // where is expected in canvas view coordinates 485 DragState* 486 TransformBox::_DragStateFor(BPoint where, float canvasZoom) 487 { 488 DragState* state = NULL; 489 // convert to canvas zoom level 490 // 491 // the conversion is necessary, because the "hot regions" 492 // around a point should be the same size no matter what 493 // zoom level the canvas is displayed at 494 495 float inset = INSET / canvasZoom; 496 497 // priorities: 498 // transformation center point has highest priority ?!? 499 if (point_point_distance(where, fPivot) < inset) 500 state = fOffsetCenterState; 501 502 if (!state) { 503 // next, the inner area of the box 504 505 // for the following calculations 506 // we can apply the inverse transformation to all points 507 // this way we have to consider BRects only, not transformed polygons 508 BPoint lt = fLeftTop; 509 BPoint rb = fRightBottom; 510 BPoint w = where; 511 512 InverseTransform(&w); 513 InverseTransform(<); 514 InverseTransform(&rb); 515 516 // next priority has the inside of the box 517 BRect iR(lt, rb); 518 float hInset = min_c(inset, max_c(0, (iR.Width() - inset) / 2.0)); 519 float vInset = min_c(inset, max_c(0, (iR.Height() - inset) / 2.0)); 520 521 iR.InsetBy(hInset, vInset); 522 if (iR.Contains(w)) 523 state = fTranslateState; 524 } 525 526 if (!state) { 527 // next priority have the corners 528 float dLT = point_point_distance(fLeftTop, where); 529 float dRT = point_point_distance(fRightTop, where); 530 float dLB = point_point_distance(fLeftBottom, where); 531 float dRB = point_point_distance(fRightBottom, where); 532 float d = min4(dLT, dRT, dLB, dRB); 533 if (d < inset) { 534 if (d == dLT) 535 state = fDragLTState; 536 else if (d == dRT) 537 state = fDragRTState; 538 else if (d == dLB) 539 state = fDragLBState; 540 else if (d == dRB) 541 state = fDragRBState; 542 } 543 } 544 545 if (!state) { 546 // next priority have the sides 547 float dL = point_line_dist(fLeftTop, fLeftBottom, where, inset); 548 float dR = point_line_dist(fRightTop, fRightBottom, where, inset); 549 float dT = point_line_dist(fLeftTop, fRightTop, where, inset); 550 float dB = point_line_dist(fLeftBottom, fRightBottom, where, inset); 551 float d = min4(dL, dR, dT, dB); 552 if (d < inset) { 553 if (d == dL) 554 state = fDragLState; 555 else if (d == dR) 556 state = fDragRState; 557 else if (d == dT) 558 state = fDragTState; 559 else if (d == dB) 560 state = fDragBState; 561 } 562 } 563 564 if (!state) { 565 BPoint lt = fLeftTop; 566 BPoint rb = fRightBottom; 567 BPoint w = where; 568 569 InverseTransform(&w); 570 InverseTransform(<); 571 InverseTransform(&rb); 572 573 // check inside of the box again 574 BRect iR(lt, rb); 575 if (iR.Contains(w)) { 576 state = fTranslateState; 577 } else { 578 // last priority has the rotate state 579 state = fRotateState; 580 } 581 } 582 583 return state; 584 } 585 586 // _StrokeBWLine 587 void 588 TransformBox::_StrokeBWLine(BView* into, BPoint from, BPoint to) const 589 { 590 // find out how to offset the second line optimally 591 BPoint offset(0.0, 0.0); 592 // first, do we have a more horizontal line or a more vertical line? 593 float xDiff = to.x - from.x; 594 float yDiff = to.y - from.y; 595 if (fabs(xDiff) > fabs(yDiff)) { 596 // horizontal 597 if (xDiff > 0.0) { 598 offset.y = -1.0; 599 } else { 600 offset.y = 1.0; 601 } 602 } else { 603 // vertical 604 if (yDiff < 0.0) { 605 offset.x = -1.0; 606 } else { 607 offset.x = 1.0; 608 } 609 } 610 // stroke two lines in high and low color of the view 611 into->StrokeLine(from, to, B_SOLID_LOW); 612 from += offset; 613 to += offset; 614 into->StrokeLine(from, to, B_SOLID_HIGH); 615 } 616 617 // _StrokeBWPoint 618 void 619 TransformBox::_StrokeBWPoint(BView* into, BPoint point, double angle) const 620 { 621 double x = point.x; 622 double y = point.y; 623 624 double x1 = x; 625 double y1 = y - 5.0; 626 627 double x2 = x - 5.0; 628 double y2 = y - 5.0; 629 630 double x3 = x - 5.0; 631 double y3 = y; 632 633 agg::trans_affine m; 634 635 double xOffset = -x; 636 double yOffset = -y; 637 638 agg::trans_affine_rotation r(angle * PI / 180.0); 639 640 r.transform(&xOffset, &yOffset); 641 xOffset = x + xOffset; 642 yOffset = y + yOffset; 643 644 m.multiply(r); 645 m.multiply(agg::trans_affine_translation(xOffset, yOffset)); 646 647 m.transform(&x, &y); 648 m.transform(&x1, &y1); 649 m.transform(&x2, &y2); 650 m.transform(&x3, &y3); 651 652 BPoint p[4]; 653 p[0] = BPoint(x, y); 654 p[1] = BPoint(x1, y1); 655 p[2] = BPoint(x2, y2); 656 p[3] = BPoint(x3, y3); 657 658 into->FillPolygon(p, 4, B_SOLID_HIGH); 659 660 into->StrokeLine(p[0], p[1], B_SOLID_LOW); 661 into->StrokeLine(p[1], p[2], B_SOLID_LOW); 662 into->StrokeLine(p[2], p[3], B_SOLID_LOW); 663 into->StrokeLine(p[3], p[0], B_SOLID_LOW); 664 } 665 666 // #pragma mark - 667 668 // _NotifyDeleted 669 void 670 TransformBox::_NotifyDeleted() const 671 { 672 BList listeners(fListeners); 673 int32 count = listeners.CountItems(); 674 for (int32 i = 0; i < count; i++) { 675 TransformBoxListener* listener 676 = (TransformBoxListener*)listeners.ItemAtFast(i); 677 listener->TransformBoxDeleted(this); 678 } 679 } 680 681 // #pragma mark - 682 683 // _SetState 684 void 685 TransformBox::_SetState(DragState* state) 686 { 687 if (state != fCurrentState) { 688 fCurrentState = state; 689 fCurrentState->UpdateViewCursor(fView, fMousePos); 690 } 691 } 692 693