1 /* 2 * PROJECT: PAINT for ReactOS 3 * LICENSE: LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later) 4 * PURPOSE: Things which should not be in the mouse event handler itself 5 * COPYRIGHT: Copyright 2015 Benedikt Freisen <b.freisen@gmx.net> 6 * Copyright 2021-2023 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com> 7 */ 8 9 #include "precomp.h" 10 #include <atlalloc.h> 11 12 static SIZE_T s_cPoints = 0; 13 static CHeapPtr<POINT, CLocalAllocator> s_dynamicPoints; 14 static POINT s_staticPoints[512]; // 512 is enough 15 static SIZE_T s_maxPoints = _countof(s_staticPoints); 16 static LPPOINT s_pPoints = s_staticPoints; 17 static POINT g_ptStart, g_ptEnd; 18 19 /* FUNCTIONS ********************************************************/ 20 21 void 22 regularize(LONG x0, LONG y0, LONG& x1, LONG& y1) 23 { 24 if (labs(x1 - x0) >= labs(y1 - y0)) 25 y1 = y0 + (y1 > y0 ? labs(x1 - x0) : -labs(x1 - x0)); 26 else 27 x1 = x0 + (x1 > x0 ? labs(y1 - y0) : -labs(y1 - y0)); 28 } 29 30 void 31 roundTo8Directions(LONG x0, LONG y0, LONG& x1, LONG& y1) 32 { 33 if (labs(x1 - x0) >= labs(y1 - y0)) 34 { 35 if (labs(y1 - y0) * 5 < labs(x1 - x0) * 2) 36 y1 = y0; 37 else 38 y1 = y0 + (y1 > y0 ? labs(x1 - x0) : -labs(x1 - x0)); 39 } 40 else 41 { 42 if (labs(x1 - x0) * 5 < labs(y1 - y0) * 2) 43 x1 = x0; 44 else 45 x1 = x0 + (x1 > x0 ? labs(y1 - y0) : -labs(y1 - y0)); 46 } 47 } 48 49 BOOL nearlyEqualPoints(INT x0, INT y0, INT x1, INT y1) 50 { 51 INT cxThreshold = toolsModel.GetLineWidth() + UnZoomed(GetSystemMetrics(SM_CXDRAG)); 52 INT cyThreshold = toolsModel.GetLineWidth() + UnZoomed(GetSystemMetrics(SM_CYDRAG)); 53 return (abs(x1 - x0) <= cxThreshold) && (abs(y1 - y0) <= cyThreshold); 54 } 55 56 void getBoundaryOfPoints(RECT& rcBoundary, SIZE_T cPoints, const POINT *pPoints) 57 { 58 POINT ptMin = { MAXLONG, MAXLONG }, ptMax = { (LONG)MINLONG, (LONG)MINLONG }; 59 while (cPoints-- > 0) 60 { 61 LONG x = pPoints->x, y = pPoints->y; 62 ptMin = { min(x, ptMin.x), min(y, ptMin.y) }; 63 ptMax = { max(x, ptMax.x), max(y, ptMax.y) }; 64 ++pPoints; 65 } 66 67 ptMax.x += 1; 68 ptMax.y += 1; 69 70 CRect rc(ptMin, ptMax); 71 rcBoundary = rc; 72 } 73 74 void ShiftPoints(INT dx, INT dy) 75 { 76 for (SIZE_T i = 0; i < s_cPoints; ++i) 77 { 78 POINT& pt = s_pPoints[i]; 79 pt.x += dx; 80 pt.y += dy; 81 } 82 } 83 84 void BuildMaskFromPoints() 85 { 86 CRect rc; 87 getBoundaryOfPoints(rc, s_cPoints, s_pPoints); 88 89 ShiftPoints(-rc.left, -rc.top); 90 91 HDC hdcMem = ::CreateCompatibleDC(NULL); 92 HBITMAP hbmMask = ::CreateBitmap(rc.Width(), rc.Height(), 1, 1, NULL); 93 HGDIOBJ hbmOld = ::SelectObject(hdcMem, hbmMask); 94 ::FillRect(hdcMem, &rc, (HBRUSH)::GetStockObject(BLACK_BRUSH)); 95 HGDIOBJ hPenOld = ::SelectObject(hdcMem, GetStockObject(NULL_PEN)); 96 HGDIOBJ hbrOld = ::SelectObject(hdcMem, GetStockObject(WHITE_BRUSH)); 97 ::Polygon(hdcMem, s_pPoints, (INT)s_cPoints); 98 ::SelectObject(hdcMem, hbrOld); 99 ::SelectObject(hdcMem, hPenOld); 100 ::SelectObject(hdcMem, hbmOld); 101 ::DeleteDC(hdcMem); 102 103 selectionModel.setMask(rc, hbmMask); 104 } 105 106 void ToolBase::reset() 107 { 108 if (s_pPoints != s_staticPoints) 109 { 110 s_dynamicPoints.Free(); 111 s_pPoints = s_staticPoints; 112 s_maxPoints = _countof(s_staticPoints); 113 } 114 115 s_cPoints = 0; 116 g_ptEnd = g_ptStart = { -1, -1 }; 117 118 if (selectionModel.m_bShow) 119 { 120 selectionModel.Landing(); 121 selectionModel.HideSelection(); 122 } 123 } 124 125 void ToolBase::OnEndDraw(BOOL bCancel) 126 { 127 reset(); 128 imageModel.NotifyImageChanged(); 129 } 130 131 void ToolBase::beginEvent() 132 { 133 m_hdc = imageModel.GetDC(); 134 m_fg = paletteModel.GetFgColor(); 135 m_bg = paletteModel.GetBgColor(); 136 } 137 138 void ToolBase::endEvent() 139 { 140 m_hdc = NULL; 141 } 142 143 static void pushToPoints(LONG x, LONG y) 144 { 145 if (s_cPoints + 1 >= s_maxPoints) 146 { 147 SIZE_T newMax = s_maxPoints + 512; 148 SIZE_T cbNew = newMax * sizeof(POINT); 149 if (!s_dynamicPoints.ReallocateBytes(cbNew)) 150 { 151 ATLTRACE("%d, %d, %d\n", (INT)s_cPoints, (INT)s_maxPoints, (INT)cbNew); 152 return; 153 } 154 155 if (s_pPoints == s_staticPoints) 156 CopyMemory(s_dynamicPoints, s_staticPoints, s_cPoints * sizeof(POINT)); 157 158 s_pPoints = s_dynamicPoints; 159 s_maxPoints = newMax; 160 } 161 162 s_pPoints[s_cPoints++] = { x, y }; 163 } 164 165 /* TOOLS ********************************************************/ 166 167 struct TwoPointDrawTool : ToolBase 168 { 169 BOOL m_bLeftButton = FALSE; 170 BOOL m_bDrawing = FALSE; 171 172 void OnButtonDown(BOOL bLeftButton, LONG x, LONG y, BOOL bDoubleClick) override 173 { 174 m_bLeftButton = bLeftButton; 175 m_bDrawing = TRUE; 176 imageModel.NotifyImageChanged(); 177 } 178 179 BOOL OnMouseMove(BOOL bLeftButton, LONG& x, LONG& y) override 180 { 181 imageModel.NotifyImageChanged(); 182 return TRUE; 183 } 184 185 BOOL OnButtonUp(BOOL bLeftButton, LONG& x, LONG& y) override 186 { 187 CRect rcPartial(g_ptStart, g_ptEnd); 188 rcPartial.NormalizeRect(); 189 SIZE size = toolsModel.GetToolSize(); 190 rcPartial.InflateRect((size.cx + 1) / 2, (size.cy + 1) / 2); 191 imageModel.PushImageForUndo(rcPartial); 192 193 OnDrawOverlayOnImage(m_hdc); 194 m_bDrawing = FALSE; 195 imageModel.NotifyImageChanged(); 196 return TRUE; 197 } 198 199 void OnEndDraw(BOOL bCancel) override 200 { 201 m_bDrawing = FALSE; 202 ToolBase::OnEndDraw(bCancel); 203 } 204 205 void OnSpecialTweak(BOOL bMinus) override 206 { 207 toolsModel.MakeLineThickerOrThinner(bMinus); 208 } 209 }; 210 211 typedef enum DIRECTION 212 { 213 NO_DIRECTION = -1, 214 DIRECTION_HORIZONTAL, 215 DIRECTION_VERTICAL, 216 DIRECTION_DIAGONAL_RIGHT_DOWN, 217 DIRECTION_DIAGONAL_RIGHT_UP, 218 } DIRECTION; 219 220 #define THRESHOULD_DEG 15 221 222 static DIRECTION 223 GetDirection(LONG x0, LONG y0, LONG x1, LONG y1) 224 { 225 LONG dx = x1 - x0, dy = y1 - y0; 226 227 if (labs(dx) <= 8 && labs(dy) <= 8) 228 return NO_DIRECTION; 229 230 double radian = atan2((double)dy, (double)dx); 231 if (radian < DEG2RAD(-180 + THRESHOULD_DEG)) 232 { 233 ATLTRACE("DIRECTION_HORIZONTAL: %ld\n", RAD2DEG(radian)); 234 return DIRECTION_HORIZONTAL; 235 } 236 if (radian < DEG2RAD(-90 - THRESHOULD_DEG)) 237 { 238 ATLTRACE("DIRECTION_DIAGONAL_RIGHT_DOWN: %ld\n", RAD2DEG(radian)); 239 return DIRECTION_DIAGONAL_RIGHT_DOWN; 240 } 241 if (radian < DEG2RAD(-90 + THRESHOULD_DEG)) 242 { 243 ATLTRACE("DIRECTION_VERTICAL: %ld\n", RAD2DEG(radian)); 244 return DIRECTION_VERTICAL; 245 } 246 if (radian < DEG2RAD(-THRESHOULD_DEG)) 247 { 248 ATLTRACE("DIRECTION_DIAGONAL_RIGHT_UP: %ld\n", RAD2DEG(radian)); 249 return DIRECTION_DIAGONAL_RIGHT_UP; 250 } 251 if (radian < DEG2RAD(+THRESHOULD_DEG)) 252 { 253 ATLTRACE("DIRECTION_HORIZONTAL: %ld\n", RAD2DEG(radian)); 254 return DIRECTION_HORIZONTAL; 255 } 256 if (radian < DEG2RAD(+90 - THRESHOULD_DEG)) 257 { 258 ATLTRACE("DIRECTION_DIAGONAL_RIGHT_DOWN: %ld\n", RAD2DEG(radian)); 259 return DIRECTION_DIAGONAL_RIGHT_DOWN; 260 } 261 if (radian < DEG2RAD(+90 + THRESHOULD_DEG)) 262 { 263 ATLTRACE("DIRECTION_VERTICAL: %ld\n", RAD2DEG(radian)); 264 return DIRECTION_VERTICAL; 265 } 266 if (radian < DEG2RAD(+180 - THRESHOULD_DEG)) 267 { 268 ATLTRACE("DIRECTION_DIAGONAL_RIGHT_UP: %ld\n", RAD2DEG(radian)); 269 return DIRECTION_DIAGONAL_RIGHT_UP; 270 } 271 ATLTRACE("DIRECTION_HORIZONTAL: %ld\n", RAD2DEG(radian)); 272 return DIRECTION_HORIZONTAL; 273 } 274 275 static void 276 RestrictDrawDirection(DIRECTION dir, LONG x0, LONG y0, LONG& x1, LONG& y1) 277 { 278 switch (dir) 279 { 280 case NO_DIRECTION: 281 default: 282 return; 283 284 case DIRECTION_HORIZONTAL: 285 y1 = y0; 286 break; 287 288 case DIRECTION_VERTICAL: 289 x1 = x0; 290 break; 291 292 case DIRECTION_DIAGONAL_RIGHT_DOWN: 293 y1 = y0 + (x1 - x0); 294 break; 295 296 case DIRECTION_DIAGONAL_RIGHT_UP: 297 x1 = x0 - (y1 - y0); 298 break; 299 } 300 } 301 302 struct SmoothDrawTool : ToolBase 303 { 304 DIRECTION m_direction = NO_DIRECTION; 305 BOOL m_bShiftDown = FALSE; 306 BOOL m_bLeftButton = FALSE; 307 308 virtual void OnDraw(HDC hdc, BOOL bLeftButton, POINT pt0, POINT pt1) = 0; 309 310 void OnButtonDown(BOOL bLeftButton, LONG x, LONG y, BOOL bDoubleClick) override 311 { 312 m_direction = NO_DIRECTION; 313 m_bShiftDown = (::GetKeyState(VK_SHIFT) & 0x8000); // Is Shift key pressed? 314 m_bLeftButton = bLeftButton; 315 s_cPoints = 0; 316 pushToPoints(x, y); 317 pushToPoints(x, y); // We have to draw the first point 318 imageModel.NotifyImageChanged(); 319 } 320 321 BOOL OnMouseMove(BOOL bLeftButton, LONG& x, LONG& y) override 322 { 323 if (!m_bShiftDown) 324 { 325 pushToPoints(x, y); 326 imageModel.NotifyImageChanged(); 327 return TRUE; 328 } 329 330 if (m_direction == NO_DIRECTION) 331 { 332 m_direction = GetDirection(g_ptStart.x, g_ptStart.y, x, y); 333 if (m_direction == NO_DIRECTION) 334 return FALSE; 335 } 336 337 RestrictDrawDirection(m_direction, g_ptStart.x, g_ptStart.y, x, y); 338 pushToPoints(x, y); 339 imageModel.NotifyImageChanged(); 340 return TRUE; 341 } 342 343 BOOL OnButtonUp(BOOL bLeftButton, LONG& x, LONG& y) override 344 { 345 if (m_bShiftDown && m_direction != NO_DIRECTION) 346 RestrictDrawDirection(m_direction, g_ptStart.x, g_ptStart.y, x, y); 347 348 pushToPoints(x, y); 349 350 CRect rcPartial; 351 getBoundaryOfPoints(rcPartial, s_cPoints, s_pPoints); 352 353 SIZE size = toolsModel.GetToolSize(); 354 rcPartial.InflateRect((size.cx + 1) / 2, (size.cy + 1) / 2); 355 356 imageModel.PushImageForUndo(rcPartial); 357 358 OnDrawOverlayOnImage(m_hdc); 359 imageModel.NotifyImageChanged(); 360 OnEndDraw(FALSE); 361 return TRUE; 362 } 363 364 void OnDrawOverlayOnImage(HDC hdc) override 365 { 366 for (SIZE_T i = 1; i < s_cPoints; ++i) 367 { 368 OnDraw(hdc, m_bLeftButton, s_pPoints[i - 1], s_pPoints[i]); 369 } 370 } 371 }; 372 373 struct SelectionBaseTool : ToolBase 374 { 375 BOOL m_bLeftButton = FALSE; 376 BOOL m_bCtrlKey = FALSE; 377 BOOL m_bShiftKey = FALSE; 378 BOOL m_bDrawing = FALSE; 379 BOOL m_bNoDrawBack = FALSE; 380 HITTEST m_hitSelection = HIT_NONE; 381 382 BOOL isRectSelect() const 383 { 384 return (toolsModel.GetActiveTool() == TOOL_RECTSEL); 385 } 386 387 void OnDrawOverlayOnImage(HDC hdc) override 388 { 389 if (selectionModel.IsLanded() || !selectionModel.m_bShow) 390 return; 391 392 if (!m_bNoDrawBack) 393 selectionModel.DrawBackground(hdc, selectionModel.m_rgbBack); 394 395 selectionModel.DrawSelection(hdc, paletteModel.GetBgColor(), toolsModel.IsBackgroundTransparent()); 396 } 397 398 void OnDrawOverlayOnCanvas(HDC hdc) override 399 { 400 if (m_bDrawing || selectionModel.m_bShow) 401 selectionModel.drawFrameOnCanvas(hdc); 402 } 403 404 void OnButtonDown(BOOL bLeftButton, LONG x, LONG y, BOOL bDoubleClick) override 405 { 406 m_bLeftButton = bLeftButton; 407 m_bCtrlKey = (::GetKeyState(VK_CONTROL) < 0); 408 m_bShiftKey = (::GetKeyState(VK_SHIFT) < 0); 409 m_bDrawing = FALSE; 410 m_hitSelection = HIT_NONE; 411 412 POINT pt = { x, y }; 413 if (!m_bLeftButton) // Show context menu on Right-click 414 { 415 canvasWindow.ImageToCanvas(pt); 416 canvasWindow.ClientToScreen(&pt); 417 mainWindow.TrackPopupMenu(pt, 0); 418 return; 419 } 420 421 POINT ptCanvas = pt; 422 canvasWindow.ImageToCanvas(ptCanvas); 423 HITTEST hit = selectionModel.hitTest(ptCanvas); 424 if (hit != HIT_NONE) // Dragging of selection started? 425 { 426 if (m_bCtrlKey || m_bShiftKey) 427 { 428 imageModel.PushImageForUndo(); 429 toolsModel.OnDrawOverlayOnImage(imageModel.GetDC()); 430 } 431 m_hitSelection = hit; 432 selectionModel.m_ptHit = pt; 433 selectionModel.TakeOff(); 434 m_bNoDrawBack |= (m_bCtrlKey || m_bShiftKey); 435 imageModel.NotifyImageChanged(); 436 return; 437 } 438 439 selectionModel.Landing(); 440 m_bDrawing = TRUE; 441 442 imageModel.Clamp(pt); 443 if (isRectSelect()) 444 { 445 selectionModel.SetRectFromPoints(g_ptStart, pt); 446 } 447 else 448 { 449 s_cPoints = 0; 450 pushToPoints(pt.x, pt.y); 451 } 452 453 imageModel.NotifyImageChanged(); 454 } 455 456 BOOL OnMouseMove(BOOL bLeftButton, LONG& x, LONG& y) override 457 { 458 POINT pt = { x, y }; 459 460 if (!m_bLeftButton) 461 return TRUE; 462 463 if (m_hitSelection != HIT_NONE) // Now dragging selection? 464 { 465 if (m_bShiftKey) 466 toolsModel.OnDrawOverlayOnImage(imageModel.GetDC()); 467 468 selectionModel.Dragging(m_hitSelection, pt); 469 imageModel.NotifyImageChanged(); 470 return TRUE; 471 } 472 473 if (isRectSelect() && ::GetKeyState(VK_SHIFT) < 0) 474 regularize(g_ptStart.x, g_ptStart.y, pt.x, pt.y); 475 476 imageModel.Clamp(pt); 477 478 if (isRectSelect()) 479 selectionModel.SetRectFromPoints(g_ptStart, pt); 480 else 481 pushToPoints(pt.x, pt.y); 482 483 imageModel.NotifyImageChanged(); 484 return TRUE; 485 } 486 487 BOOL OnButtonUp(BOOL bLeftButton, LONG& x, LONG& y) override 488 { 489 POINT pt = { x, y }; 490 m_bDrawing = FALSE; 491 492 if (!m_bLeftButton) 493 return TRUE; 494 495 if (m_hitSelection != HIT_NONE) // Dragging of selection ended? 496 { 497 if (m_bShiftKey) 498 toolsModel.OnDrawOverlayOnImage(imageModel.GetDC()); 499 500 selectionModel.Dragging(m_hitSelection, pt); 501 m_hitSelection = HIT_NONE; 502 imageModel.NotifyImageChanged(); 503 return TRUE; 504 } 505 506 if (isRectSelect() && ::GetKeyState(VK_SHIFT) < 0) 507 regularize(g_ptStart.x, g_ptStart.y, pt.x, pt.y); 508 509 imageModel.Clamp(pt); 510 511 if (isRectSelect()) 512 { 513 selectionModel.SetRectFromPoints(g_ptStart, pt); 514 selectionModel.m_bShow = !selectionModel.m_rc.IsRectEmpty(); 515 } 516 else 517 { 518 if (s_cPoints > 2) 519 { 520 BuildMaskFromPoints(); 521 selectionModel.m_bShow = TRUE; 522 } 523 else 524 { 525 s_cPoints = 0; 526 selectionModel.m_bShow = FALSE; 527 } 528 } 529 530 m_bNoDrawBack = FALSE; 531 imageModel.NotifyImageChanged(); 532 return TRUE; 533 } 534 535 void OnEndDraw(BOOL bCancel) override 536 { 537 if (bCancel) 538 selectionModel.HideSelection(); 539 else 540 selectionModel.Landing(); 541 542 m_bDrawing = FALSE; 543 m_hitSelection = HIT_NONE; 544 ToolBase::OnEndDraw(bCancel); 545 } 546 547 void OnSpecialTweak(BOOL bMinus) override 548 { 549 selectionModel.StretchSelection(bMinus); 550 } 551 }; 552 553 // TOOL_FREESEL 554 struct FreeSelTool : SelectionBaseTool 555 { 556 void OnDrawOverlayOnImage(HDC hdc) override 557 { 558 SelectionBaseTool::OnDrawOverlayOnImage(hdc); 559 560 if (!selectionModel.m_bShow && m_bDrawing) 561 { 562 /* Draw the freehand selection inverted/xored */ 563 Poly(hdc, s_pPoints, (INT)s_cPoints, 0, 0, 2, 0, FALSE, TRUE); 564 } 565 } 566 }; 567 568 // TOOL_RECTSEL 569 struct RectSelTool : SelectionBaseTool 570 { 571 void OnDrawOverlayOnImage(HDC hdc) override 572 { 573 SelectionBaseTool::OnDrawOverlayOnImage(hdc); 574 575 if (!selectionModel.m_bShow && m_bDrawing) 576 { 577 CRect& rc = selectionModel.m_rc; 578 if (!rc.IsRectEmpty()) 579 RectSel(hdc, rc.left, rc.top, rc.right, rc.bottom); 580 } 581 } 582 }; 583 584 // TOOL_RUBBER 585 struct RubberTool : SmoothDrawTool 586 { 587 void OnDraw(HDC hdc, BOOL bLeftButton, POINT pt0, POINT pt1) override 588 { 589 if (bLeftButton) 590 Erase(hdc, pt0.x, pt0.y, pt1.x, pt1.y, m_bg, toolsModel.GetRubberRadius()); 591 else 592 Replace(hdc, pt0.x, pt0.y, pt1.x, pt1.y, m_fg, m_bg, toolsModel.GetRubberRadius()); 593 } 594 595 void OnSpecialTweak(BOOL bMinus) override 596 { 597 toolsModel.MakeRubberThickerOrThinner(bMinus); 598 } 599 }; 600 601 // TOOL_FILL 602 struct FillTool : ToolBase 603 { 604 void OnButtonDown(BOOL bLeftButton, LONG x, LONG y, BOOL bDoubleClick) override 605 { 606 imageModel.PushImageForUndo(); 607 Fill(m_hdc, x, y, bLeftButton ? m_fg : m_bg); 608 } 609 }; 610 611 // TOOL_COLOR 612 struct ColorTool : ToolBase 613 { 614 void fetchColor(BOOL bLeftButton, LONG x, LONG y) 615 { 616 COLORREF rgbColor; 617 618 if (0 <= x && x < imageModel.GetWidth() && 0 <= y && y < imageModel.GetHeight()) 619 rgbColor = GetPixel(m_hdc, x, y); 620 else 621 rgbColor = RGB(255, 255, 255); // Outside is white 622 623 if (bLeftButton) 624 paletteModel.SetFgColor(rgbColor); 625 else 626 paletteModel.SetBgColor(rgbColor); 627 } 628 629 BOOL OnMouseMove(BOOL bLeftButton, LONG& x, LONG& y) override 630 { 631 fetchColor(bLeftButton, x, y); 632 return TRUE; 633 } 634 635 BOOL OnButtonUp(BOOL bLeftButton, LONG& x, LONG& y) override 636 { 637 fetchColor(bLeftButton, x, y); 638 toolsModel.SetActiveTool(toolsModel.GetOldActiveTool()); 639 return TRUE; 640 } 641 }; 642 643 // TOOL_ZOOM 644 struct ZoomTool : ToolBase 645 { 646 BOOL m_bZoomed = FALSE; 647 648 BOOL getNewZoomRect(CRect& rcView, INT newZoom); 649 650 void OnDrawOverlayOnCanvas(HDC hdc) override 651 { 652 CRect rcView; 653 INT oldZoom = toolsModel.GetZoom(); 654 if (oldZoom < MAX_ZOOM && getNewZoomRect(rcView, oldZoom * 2)) 655 DrawXorRect(hdc, &rcView); 656 } 657 658 void OnButtonDown(BOOL bLeftButton, LONG x, LONG y, BOOL bDoubleClick) override 659 { 660 INT newZoom, oldZoom = toolsModel.GetZoom(); 661 if (bLeftButton) 662 newZoom = (oldZoom < MAX_ZOOM) ? (oldZoom * 2) : MIN_ZOOM; 663 else 664 newZoom = (oldZoom > MIN_ZOOM) ? (oldZoom / 2) : MAX_ZOOM; 665 666 m_bZoomed = FALSE; 667 668 if (oldZoom != newZoom) 669 { 670 CRect rcView; 671 if (getNewZoomRect(rcView, newZoom)) 672 { 673 canvasWindow.zoomTo(newZoom, rcView.left, rcView.top); 674 m_bZoomed = TRUE; 675 } 676 } 677 } 678 679 BOOL OnButtonUp(BOOL bLeftButton, LONG& x, LONG& y) override 680 { 681 if (m_bZoomed) 682 toolsModel.SetActiveTool(toolsModel.GetOldActiveTool()); 683 684 return TRUE; 685 } 686 }; 687 688 BOOL ZoomTool::getNewZoomRect(CRect& rcView, INT newZoom) 689 { 690 CPoint pt; 691 ::GetCursorPos(&pt); 692 canvasWindow.ScreenToClient(&pt); 693 694 canvasWindow.getNewZoomRect(rcView, newZoom, pt); 695 696 CRect rc; 697 canvasWindow.GetImageRect(rc); 698 canvasWindow.ImageToCanvas(rc); 699 700 return rc.PtInRect(pt); 701 } 702 703 // TOOL_PEN 704 struct PenTool : SmoothDrawTool 705 { 706 void OnDraw(HDC hdc, BOOL bLeftButton, POINT pt0, POINT pt1) override 707 { 708 COLORREF rgb = bLeftButton ? m_fg : m_bg; 709 Line(hdc, pt0.x, pt0.y, pt1.x, pt1.y, rgb, toolsModel.GetPenWidth()); 710 } 711 712 void OnSpecialTweak(BOOL bMinus) override 713 { 714 toolsModel.MakePenThickerOrThinner(bMinus); 715 } 716 }; 717 718 // TOOL_BRUSH 719 struct BrushTool : SmoothDrawTool 720 { 721 void OnDraw(HDC hdc, BOOL bLeftButton, POINT pt0, POINT pt1) override 722 { 723 COLORREF rgb = bLeftButton ? m_fg : m_bg; 724 Brush(hdc, pt0.x, pt0.y, pt1.x, pt1.y, rgb, toolsModel.GetBrushStyle(), 725 toolsModel.GetBrushWidth()); 726 } 727 728 void OnSpecialTweak(BOOL bMinus) override 729 { 730 toolsModel.MakeBrushThickerOrThinner(bMinus); 731 } 732 }; 733 734 // TOOL_AIRBRUSH 735 struct AirBrushTool : SmoothDrawTool 736 { 737 DWORD m_dwTick = 0; 738 739 void OnButtonDown(BOOL bLeftButton, LONG x, LONG y, BOOL bDoubleClick) override 740 { 741 m_dwTick = GetTickCount(); 742 SmoothDrawTool::OnButtonDown(bLeftButton, x, y, bDoubleClick); 743 } 744 745 void OnDrawOverlayOnImage(HDC hdc) override 746 { 747 srand(m_dwTick); 748 SmoothDrawTool::OnDrawOverlayOnImage(hdc); 749 } 750 751 void OnDraw(HDC hdc, BOOL bLeftButton, POINT pt0, POINT pt1) override 752 { 753 COLORREF rgb = bLeftButton ? m_fg : m_bg; 754 Airbrush(hdc, pt1.x, pt1.y, rgb, toolsModel.GetAirBrushRadius()); 755 } 756 757 void OnSpecialTweak(BOOL bMinus) override 758 { 759 toolsModel.MakeAirBrushThickerOrThinner(bMinus); 760 } 761 }; 762 763 // TOOL_TEXT 764 struct TextTool : ToolBase 765 { 766 void OnDrawOverlayOnImage(HDC hdc) override 767 { 768 if (canvasWindow.m_drawing) 769 { 770 CRect& rc = selectionModel.m_rc; 771 if (!rc.IsRectEmpty()) 772 RectSel(hdc, rc.left, rc.top, rc.right, rc.bottom); 773 } 774 } 775 776 void UpdatePoint(LONG x, LONG y) 777 { 778 POINT pt = { x, y }; 779 imageModel.Clamp(pt); 780 selectionModel.SetRectFromPoints(g_ptStart, pt); 781 imageModel.NotifyImageChanged(); 782 } 783 784 void OnButtonDown(BOOL bLeftButton, LONG x, LONG y, BOOL bDoubleClick) override 785 { 786 if (!textEditWindow.IsWindow()) 787 textEditWindow.Create(canvasWindow); 788 789 UpdatePoint(x, y); 790 } 791 792 BOOL OnMouseMove(BOOL bLeftButton, LONG& x, LONG& y) override 793 { 794 UpdatePoint(x, y); 795 return TRUE; 796 } 797 798 void draw(HDC hdc) 799 { 800 CStringW szText; 801 textEditWindow.GetWindowText(szText); 802 803 CRect rc; 804 textEditWindow.InvalidateEditRect(); 805 textEditWindow.GetEditRect(&rc); 806 rc.InflateRect(-GRIP_SIZE / 2, -GRIP_SIZE / 2); 807 808 // Draw the text 809 INT style = (toolsModel.IsBackgroundTransparent() ? 0 : 1); 810 Text(hdc, rc.left, rc.top, rc.right, rc.bottom, m_fg, m_bg, szText, 811 textEditWindow.GetFont(), style); 812 } 813 814 void quit() 815 { 816 if (textEditWindow.IsWindow()) 817 textEditWindow.ShowWindow(SW_HIDE); 818 selectionModel.HideSelection(); 819 } 820 821 BOOL OnButtonUp(BOOL bLeftButton, LONG& x, LONG& y) override 822 { 823 POINT pt = { x, y }; 824 imageModel.Clamp(pt); 825 selectionModel.SetRectFromPoints(g_ptStart, pt); 826 827 BOOL bTextBoxShown = ::IsWindowVisible(textEditWindow); 828 if (bTextBoxShown) 829 { 830 if (textEditWindow.GetWindowTextLength() > 0) 831 { 832 imageModel.PushImageForUndo(); 833 draw(m_hdc); 834 } 835 if (selectionModel.m_rc.IsRectEmpty()) 836 { 837 quit(); 838 return TRUE; 839 } 840 } 841 842 if (registrySettings.ShowTextTool) 843 { 844 if (!fontsDialog.IsWindow()) 845 fontsDialog.Create(mainWindow); 846 847 fontsDialog.ShowWindow(SW_SHOWNOACTIVATE); 848 } 849 850 CRect rc = selectionModel.m_rc; 851 852 // Enlarge if tool small 853 INT cxMin = CX_MINTEXTEDIT, cyMin = CY_MINTEXTEDIT; 854 if (selectionModel.m_rc.IsRectEmpty()) 855 { 856 rc.SetRect(x, y, x + cxMin, y + cyMin); 857 } 858 else 859 { 860 if (rc.right - rc.left < cxMin) 861 rc.right = rc.left + cxMin; 862 if (rc.bottom - rc.top < cyMin) 863 rc.bottom = rc.top + cyMin; 864 } 865 866 if (!textEditWindow.IsWindow()) 867 textEditWindow.Create(canvasWindow); 868 869 textEditWindow.SetWindowText(NULL); 870 textEditWindow.ValidateEditRect(&rc); 871 textEditWindow.ShowWindow(SW_SHOWNOACTIVATE); 872 textEditWindow.SetFocus(); 873 return TRUE; 874 } 875 876 void OnEndDraw(BOOL bCancel) override 877 { 878 if (!bCancel) 879 { 880 if (::IsWindowVisible(textEditWindow) && 881 textEditWindow.GetWindowTextLength() > 0) 882 { 883 imageModel.PushImageForUndo(); 884 draw(m_hdc); 885 } 886 } 887 quit(); 888 ToolBase::OnEndDraw(bCancel); 889 } 890 }; 891 892 // TOOL_LINE 893 struct LineTool : TwoPointDrawTool 894 { 895 void OnDrawOverlayOnImage(HDC hdc) override 896 { 897 if (!m_bDrawing) 898 return; 899 if (GetAsyncKeyState(VK_SHIFT) < 0) 900 roundTo8Directions(g_ptStart.x, g_ptStart.y, g_ptEnd.x, g_ptEnd.y); 901 COLORREF rgb = m_bLeftButton ? m_fg : m_bg; 902 Line(hdc, g_ptStart.x, g_ptStart.y, g_ptEnd.x, g_ptEnd.y, rgb, toolsModel.GetLineWidth()); 903 } 904 }; 905 906 // TOOL_BEZIER 907 struct BezierTool : ToolBase 908 { 909 BOOL m_bLeftButton = FALSE; 910 911 void OnDrawOverlayOnImage(HDC hdc) 912 { 913 COLORREF rgb = (m_bLeftButton ? m_fg : m_bg); 914 switch (s_cPoints) 915 { 916 case 2: 917 Line(hdc, s_pPoints[0].x, s_pPoints[0].y, s_pPoints[1].x, s_pPoints[1].y, rgb, 918 toolsModel.GetLineWidth()); 919 break; 920 case 3: 921 Bezier(hdc, s_pPoints[0], s_pPoints[2], s_pPoints[2], s_pPoints[1], rgb, toolsModel.GetLineWidth()); 922 break; 923 case 4: 924 Bezier(hdc, s_pPoints[0], s_pPoints[2], s_pPoints[3], s_pPoints[1], rgb, toolsModel.GetLineWidth()); 925 break; 926 } 927 } 928 929 void OnButtonDown(BOOL bLeftButton, LONG x, LONG y, BOOL bDoubleClick) override 930 { 931 m_bLeftButton = bLeftButton; 932 933 if (s_cPoints == 0) 934 { 935 pushToPoints(x, y); 936 pushToPoints(x, y); 937 } 938 else 939 { 940 s_pPoints[s_cPoints - 1] = { x, y }; 941 } 942 943 imageModel.NotifyImageChanged(); 944 } 945 946 BOOL OnMouseMove(BOOL bLeftButton, LONG& x, LONG& y) override 947 { 948 if (s_cPoints > 0) 949 s_pPoints[s_cPoints - 1] = { x, y }; 950 imageModel.NotifyImageChanged(); 951 return TRUE; 952 } 953 954 BOOL OnButtonUp(BOOL bLeftButton, LONG& x, LONG& y) override 955 { 956 if (s_cPoints >= 4) 957 { 958 OnEndDraw(FALSE); 959 return TRUE; 960 } 961 pushToPoints(x, y); 962 imageModel.NotifyImageChanged(); 963 return TRUE; 964 } 965 966 void OnEndDraw(BOOL bCancel) override 967 { 968 if (!bCancel && s_cPoints > 1) 969 { 970 // FIXME: I couldn't calculate boundary rectangle from Bezier curve 971 imageModel.PushImageForUndo(); 972 OnDrawOverlayOnImage(m_hdc); 973 } 974 ToolBase::OnEndDraw(bCancel); 975 } 976 977 void OnSpecialTweak(BOOL bMinus) override 978 { 979 toolsModel.MakeLineThickerOrThinner(bMinus); 980 } 981 }; 982 983 // TOOL_RECT 984 struct RectTool : TwoPointDrawTool 985 { 986 void OnDrawOverlayOnImage(HDC hdc) override 987 { 988 if (!m_bDrawing) 989 return; 990 if (GetAsyncKeyState(VK_SHIFT) < 0) 991 regularize(g_ptStart.x, g_ptStart.y, g_ptEnd.x, g_ptEnd.y); 992 if (m_bLeftButton) 993 Rect(hdc, g_ptStart.x, g_ptStart.y, g_ptEnd.x, g_ptEnd.y, m_fg, m_bg, toolsModel.GetLineWidth(), toolsModel.GetShapeStyle()); 994 else 995 Rect(hdc, g_ptStart.x, g_ptStart.y, g_ptEnd.x, g_ptEnd.y, m_bg, m_fg, toolsModel.GetLineWidth(), toolsModel.GetShapeStyle()); 996 } 997 }; 998 999 // TOOL_SHAPE 1000 struct ShapeTool : ToolBase 1001 { 1002 BOOL m_bLeftButton = FALSE; 1003 BOOL m_bClosed = FALSE; 1004 1005 void OnDrawOverlayOnImage(HDC hdc) 1006 { 1007 if (s_cPoints <= 0) 1008 return; 1009 1010 if (m_bLeftButton) 1011 Poly(hdc, s_pPoints, (INT)s_cPoints, m_fg, m_bg, toolsModel.GetLineWidth(), toolsModel.GetShapeStyle(), m_bClosed, FALSE); 1012 else 1013 Poly(hdc, s_pPoints, (INT)s_cPoints, m_bg, m_fg, toolsModel.GetLineWidth(), toolsModel.GetShapeStyle(), m_bClosed, FALSE); 1014 } 1015 1016 void OnButtonDown(BOOL bLeftButton, LONG x, LONG y, BOOL bDoubleClick) override 1017 { 1018 m_bLeftButton = bLeftButton; 1019 m_bClosed = FALSE; 1020 1021 if ((s_cPoints > 0) && (GetAsyncKeyState(VK_SHIFT) < 0)) 1022 roundTo8Directions(s_pPoints[s_cPoints - 1].x, s_pPoints[s_cPoints - 1].y, x, y); 1023 1024 pushToPoints(x, y); 1025 1026 if (s_cPoints > 1 && bDoubleClick) 1027 { 1028 OnEndDraw(FALSE); 1029 return; 1030 } 1031 1032 if (s_cPoints == 1) 1033 pushToPoints(x, y); // We have to draw the first point 1034 1035 imageModel.NotifyImageChanged(); 1036 } 1037 1038 BOOL OnMouseMove(BOOL bLeftButton, LONG& x, LONG& y) override 1039 { 1040 if (s_cPoints > 1) 1041 { 1042 if (GetAsyncKeyState(VK_SHIFT) < 0) 1043 roundTo8Directions(s_pPoints[s_cPoints - 2].x, s_pPoints[s_cPoints - 2].y, x, y); 1044 1045 s_pPoints[s_cPoints - 1] = { x, y }; 1046 } 1047 1048 imageModel.NotifyImageChanged(); 1049 return TRUE; 1050 } 1051 1052 BOOL OnButtonUp(BOOL bLeftButton, LONG& x, LONG& y) override 1053 { 1054 if ((s_cPoints > 1) && (GetAsyncKeyState(VK_SHIFT) < 0)) 1055 roundTo8Directions(s_pPoints[s_cPoints - 2].x, s_pPoints[s_cPoints - 2].y, x, y); 1056 1057 m_bClosed = FALSE; 1058 if (nearlyEqualPoints(x, y, s_pPoints[0].x, s_pPoints[0].y)) 1059 { 1060 OnEndDraw(FALSE); 1061 return TRUE; 1062 } 1063 1064 pushToPoints(x, y); 1065 imageModel.NotifyImageChanged(); 1066 return TRUE; 1067 } 1068 1069 void OnEndDraw(BOOL bCancel) override 1070 { 1071 if (!bCancel && s_cPoints > 1) 1072 { 1073 CRect rcPartial; 1074 getBoundaryOfPoints(rcPartial, s_cPoints, s_pPoints); 1075 1076 SIZE size = toolsModel.GetToolSize(); 1077 rcPartial.InflateRect((size.cx + 1) / 2, (size.cy + 1) / 2); 1078 1079 imageModel.PushImageForUndo(rcPartial); 1080 1081 m_bClosed = TRUE; 1082 OnDrawOverlayOnImage(m_hdc); 1083 } 1084 m_bClosed = FALSE; 1085 ToolBase::OnEndDraw(bCancel); 1086 } 1087 1088 void OnSpecialTweak(BOOL bMinus) override 1089 { 1090 toolsModel.MakeLineThickerOrThinner(bMinus); 1091 } 1092 }; 1093 1094 // TOOL_ELLIPSE 1095 struct EllipseTool : TwoPointDrawTool 1096 { 1097 void OnDrawOverlayOnImage(HDC hdc) override 1098 { 1099 if (!m_bDrawing) 1100 return; 1101 if (GetAsyncKeyState(VK_SHIFT) < 0) 1102 regularize(g_ptStart.x, g_ptStart.y, g_ptEnd.x, g_ptEnd.y); 1103 if (m_bLeftButton) 1104 Ellp(hdc, g_ptStart.x, g_ptStart.y, g_ptEnd.x, g_ptEnd.y, m_fg, m_bg, toolsModel.GetLineWidth(), toolsModel.GetShapeStyle()); 1105 else 1106 Ellp(hdc, g_ptStart.x, g_ptStart.y, g_ptEnd.x, g_ptEnd.y, m_bg, m_fg, toolsModel.GetLineWidth(), toolsModel.GetShapeStyle()); 1107 } 1108 }; 1109 1110 // TOOL_RRECT 1111 struct RRectTool : TwoPointDrawTool 1112 { 1113 void OnDrawOverlayOnImage(HDC hdc) override 1114 { 1115 if (!m_bDrawing) 1116 return; 1117 if (GetAsyncKeyState(VK_SHIFT) < 0) 1118 regularize(g_ptStart.x, g_ptStart.y, g_ptEnd.x, g_ptEnd.y); 1119 if (m_bLeftButton) 1120 RRect(hdc, g_ptStart.x, g_ptStart.y, g_ptEnd.x, g_ptEnd.y, m_fg, m_bg, toolsModel.GetLineWidth(), toolsModel.GetShapeStyle()); 1121 else 1122 RRect(hdc, g_ptStart.x, g_ptStart.y, g_ptEnd.x, g_ptEnd.y, m_bg, m_fg, toolsModel.GetLineWidth(), toolsModel.GetShapeStyle()); 1123 } 1124 }; 1125 1126 /*static*/ ToolBase* 1127 ToolBase::createToolObject(TOOLTYPE type) 1128 { 1129 switch (type) 1130 { 1131 case TOOL_FREESEL: return new FreeSelTool(); 1132 case TOOL_RECTSEL: return new RectSelTool(); 1133 case TOOL_RUBBER: return new RubberTool(); 1134 case TOOL_FILL: return new FillTool(); 1135 case TOOL_COLOR: return new ColorTool(); 1136 case TOOL_ZOOM: return new ZoomTool(); 1137 case TOOL_PEN: return new PenTool(); 1138 case TOOL_BRUSH: return new BrushTool(); 1139 case TOOL_AIRBRUSH: return new AirBrushTool(); 1140 case TOOL_TEXT: return new TextTool(); 1141 case TOOL_LINE: return new LineTool(); 1142 case TOOL_BEZIER: return new BezierTool(); 1143 case TOOL_RECT: return new RectTool(); 1144 case TOOL_SHAPE: return new ShapeTool(); 1145 case TOOL_ELLIPSE: return new EllipseTool(); 1146 case TOOL_RRECT: return new RRectTool(); 1147 } 1148 UNREACHABLE; 1149 return NULL; 1150 } 1151 1152 void ToolsModel::OnButtonDown(BOOL bLeftButton, LONG x, LONG y, BOOL bDoubleClick) 1153 { 1154 m_pToolObject->beginEvent(); 1155 g_ptEnd = g_ptStart = { x, y }; 1156 m_pToolObject->OnButtonDown(bLeftButton, x, y, bDoubleClick); 1157 m_pToolObject->endEvent(); 1158 } 1159 1160 void ToolsModel::OnMouseMove(BOOL bLeftButton, LONG x, LONG y) 1161 { 1162 m_pToolObject->beginEvent(); 1163 if (m_pToolObject->OnMouseMove(bLeftButton, x, y)) 1164 g_ptEnd = { x, y }; 1165 1166 m_pToolObject->endEvent(); 1167 } 1168 1169 void ToolsModel::OnButtonUp(BOOL bLeftButton, LONG x, LONG y) 1170 { 1171 m_pToolObject->beginEvent(); 1172 if (m_pToolObject->OnButtonUp(bLeftButton, x, y)) 1173 g_ptEnd = { x, y }; 1174 1175 m_pToolObject->endEvent(); 1176 } 1177 1178 void ToolsModel::OnEndDraw(BOOL bCancel) 1179 { 1180 ATLTRACE("ToolsModel::OnEndDraw(%d)\n", bCancel); 1181 m_pToolObject->beginEvent(); 1182 m_pToolObject->OnEndDraw(bCancel); 1183 m_pToolObject->endEvent(); 1184 } 1185 1186 void ToolsModel::OnDrawOverlayOnImage(HDC hdc) 1187 { 1188 m_pToolObject->OnDrawOverlayOnImage(hdc); 1189 } 1190 1191 void ToolsModel::OnDrawOverlayOnCanvas(HDC hdc) 1192 { 1193 m_pToolObject->OnDrawOverlayOnCanvas(hdc); 1194 } 1195 1196 void ToolsModel::SpecialTweak(BOOL bMinus) 1197 { 1198 m_pToolObject->OnSpecialTweak(bMinus); 1199 } 1200 1201 void ToolsModel::DrawWithMouseTool(POINT pt, WPARAM wParam) 1202 { 1203 LONG xRel = pt.x - g_ptStart.x, yRel = pt.y - g_ptStart.y; 1204 1205 switch (m_activeTool) 1206 { 1207 // freesel, rectsel and text tools always show numbers limited to fit into image area 1208 case TOOL_FREESEL: 1209 case TOOL_RECTSEL: 1210 case TOOL_TEXT: 1211 if (xRel < 0) 1212 xRel = (pt.x < 0) ? -g_ptStart.x : xRel; 1213 else if (pt.x > imageModel.GetWidth()) 1214 xRel = imageModel.GetWidth() - g_ptStart.x; 1215 if (yRel < 0) 1216 yRel = (pt.y < 0) ? -g_ptStart.y : yRel; 1217 else if (pt.y > imageModel.GetHeight()) 1218 yRel = imageModel.GetHeight() - g_ptStart.y; 1219 break; 1220 1221 // while drawing, update cursor coordinates only for tools 3, 7, 8, 9, 14 1222 case TOOL_RUBBER: 1223 case TOOL_PEN: 1224 case TOOL_BRUSH: 1225 case TOOL_AIRBRUSH: 1226 case TOOL_SHAPE: 1227 { 1228 CStringW strCoord; 1229 strCoord.Format(L"%ld, %ld", pt.x, pt.y); 1230 ::SendMessageW(g_hStatusBar, SB_SETTEXT, 1, (LPARAM)(LPCWSTR)strCoord); 1231 break; 1232 } 1233 default: 1234 break; 1235 } 1236 1237 // rectsel and shape tools always show non-negative numbers when drawing 1238 if (m_activeTool == TOOL_RECTSEL || m_activeTool == TOOL_SHAPE) 1239 { 1240 xRel = labs(xRel); 1241 yRel = labs(yRel); 1242 } 1243 1244 if (wParam & MK_LBUTTON) 1245 { 1246 OnMouseMove(TRUE, pt.x, pt.y); 1247 canvasWindow.Invalidate(FALSE); 1248 if ((m_activeTool >= TOOL_TEXT) || IsSelection()) 1249 { 1250 CStringW strSize; 1251 if ((m_activeTool >= TOOL_LINE) && (GetAsyncKeyState(VK_SHIFT) < 0)) 1252 yRel = xRel; 1253 strSize.Format(L"%ld x %ld", xRel, yRel); 1254 ::SendMessageW(g_hStatusBar, SB_SETTEXT, 2, (LPARAM)(LPCWSTR)strSize); 1255 } 1256 } 1257 1258 if (wParam & MK_RBUTTON) 1259 { 1260 OnMouseMove(FALSE, pt.x, pt.y); 1261 canvasWindow.Invalidate(FALSE); 1262 if (m_activeTool >= TOOL_TEXT) 1263 { 1264 CStringW strSize; 1265 if ((m_activeTool >= TOOL_LINE) && (GetAsyncKeyState(VK_SHIFT) < 0)) 1266 yRel = xRel; 1267 strSize.Format(L"%ld x %ld", xRel, yRel); 1268 ::SendMessageW(g_hStatusBar, SB_SETTEXT, 2, (LPARAM)(LPCWSTR)strSize); 1269 } 1270 } 1271 } 1272