1 /*
2  * PROJECT:    PAINT for ReactOS
3  * LICENSE:    LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later)
4  * PURPOSE:    Window procedure of the tool settings window
5  * COPYRIGHT:  Copyright 2015 Benedikt Freisen <b.freisen@gmx.net>
6  *             Copyright 2018 Stanislav Motylkov <x86corez@gmail.com>
7  *             Copyright 2021-2023 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
8  */
9 
10 #include "precomp.h"
11 
12 #define X_TOOLSETTINGS  0
13 #define Y_TOOLSETTINGS  (CY_TOOLBAR + 3)
14 #define CX_TOOLSETTINGS CX_TOOLBAR
15 #define CY_TOOLSETTINGS 140
16 
17 #define CX_TRANS_ICON 40
18 #define CY_TRANS_ICON 30
19 #define MARGIN1 3
20 #define MARGIN2 2
21 
22 #define MAX_ZOOM_TRACK 6
23 #define MIN_ZOOM_TRACK 0
24 #define DEFAULT_ZOOM_TRACK 3
25 
26 static const BYTE s_AirRadius[4] = { 5, 8, 3, 12 };
27 
28 CToolSettingsWindow toolSettingsWindow;
29 
30 /* FUNCTIONS ********************************************************/
31 
DoCreate(HWND hwndParent)32 BOOL CToolSettingsWindow::DoCreate(HWND hwndParent)
33 {
34     RECT toolSettingsWindowPos =
35     {
36         X_TOOLSETTINGS, Y_TOOLSETTINGS,
37         X_TOOLSETTINGS + CX_TOOLSETTINGS, Y_TOOLSETTINGS + CY_TOOLSETTINGS
38     };
39     return !!Create(toolBoxContainer, toolSettingsWindowPos, NULL, WS_CHILD | WS_VISIBLE);
40 }
41 
42 static INT
getSplitRects(RECT * rects,INT cColumns,INT cRows,LPCRECT prc,LPPOINT ppt)43 getSplitRects(RECT *rects, INT cColumns, INT cRows, LPCRECT prc, LPPOINT ppt)
44 {
45     INT cx = prc->right - prc->left, cy = prc->bottom - prc->top;
46     for (INT i = 0, iRow = 0; iRow < cRows; ++iRow)
47     {
48         for (INT iColumn = 0; iColumn < cColumns; ++iColumn)
49         {
50             RECT& rc = rects[i];
51             rc.left = prc->left + (iColumn * cx / cColumns);
52             rc.top = prc->top + (iRow * cy / cRows);
53             rc.right = prc->left + ((iColumn + 1) * cx / cColumns);
54             rc.bottom = prc->top + ((iRow + 1) * cy / cRows);
55             if (ppt && ::PtInRect(&rc, *ppt))
56                 return i;
57             ++i;
58         }
59     }
60     return -1;
61 }
62 
getTransRects(RECT rects[2],LPCRECT prc,LPPOINT ppt=NULL)63 static inline INT getTransRects(RECT rects[2], LPCRECT prc, LPPOINT ppt = NULL)
64 {
65     return getSplitRects(rects, 1, 2, prc, ppt);
66 }
67 
drawTrans(HDC hdc,LPCRECT prc)68 VOID CToolSettingsWindow::drawTrans(HDC hdc, LPCRECT prc)
69 {
70     RECT rc[2];
71     getTransRects(rc, prc);
72 
73     ::FillRect(hdc, &rc[toolsModel.IsBackgroundTransparent()], (HBRUSH)(COLOR_HIGHLIGHT + 1));
74     ::DrawIconEx(hdc, rc[0].left, rc[0].top, m_hNontranspIcon,
75                  CX_TRANS_ICON, CY_TRANS_ICON, 0, NULL, DI_NORMAL);
76     ::DrawIconEx(hdc, rc[1].left, rc[1].top, m_hTranspIcon,
77                  CX_TRANS_ICON, CY_TRANS_ICON, 0, NULL, DI_NORMAL);
78 }
79 
getRubberRects(RECT rects[4],LPCRECT prc,LPPOINT ppt=NULL)80 static inline INT getRubberRects(RECT rects[4], LPCRECT prc, LPPOINT ppt = NULL)
81 {
82     return getSplitRects(rects, 1, 4, prc, ppt);
83 }
84 
drawRubber(HDC hdc,LPCRECT prc)85 VOID CToolSettingsWindow::drawRubber(HDC hdc, LPCRECT prc)
86 {
87     RECT rects[4], rcRubber;
88     getRubberRects(rects, prc);
89     INT xCenter = (prc->left + prc->right) / 2;
90     for (INT i = 0; i < 4; i++)
91     {
92         INT iColor, radius = i + 2;
93         if (toolsModel.GetRubberRadius() == radius)
94         {
95             ::FillRect(hdc, &rects[i], ::GetSysColorBrush(COLOR_HIGHLIGHT));
96             iColor = COLOR_HIGHLIGHTTEXT;
97         }
98         else
99         {
100             iColor = COLOR_WINDOWTEXT;
101         }
102 
103         INT yCenter = (rects[i].top + rects[i].bottom) / 2;
104         rcRubber.left = xCenter - radius;
105         rcRubber.top = yCenter - radius;
106         rcRubber.right = rcRubber.left + radius * 2;
107         rcRubber.bottom = rcRubber.top + radius * 2;
108         ::FillRect(hdc, &rcRubber, GetSysColorBrush(iColor));
109     }
110 }
111 
getBrushRects(RECT rects[12],LPCRECT prc,LPPOINT ppt=NULL)112 static inline INT getBrushRects(RECT rects[12], LPCRECT prc, LPPOINT ppt = NULL)
113 {
114     return getSplitRects(rects, 3, 4, prc, ppt);
115 }
116 
117 struct BrushStyleAndWidth
118 {
119     BrushStyle style;
120     INT width;
121 };
122 
123 static const BrushStyleAndWidth c_BrushPresets[] =
124 {
125     { BrushStyleRound,     7 }, { BrushStyleRound,     4 }, { BrushStyleRound,     1 },
126     { BrushStyleSquare,    8 }, { BrushStyleSquare,    5 }, { BrushStyleSquare,    2 },
127     { BrushStyleForeSlash, 8 }, { BrushStyleForeSlash, 5 }, { BrushStyleForeSlash, 2 },
128     { BrushStyleBackSlash, 8 }, { BrushStyleBackSlash, 5 }, { BrushStyleBackSlash, 2 },
129 };
130 
drawBrush(HDC hdc,LPCRECT prc)131 VOID CToolSettingsWindow::drawBrush(HDC hdc, LPCRECT prc)
132 {
133     RECT rects[12];
134     getBrushRects(rects, prc);
135 
136     for (INT i = 0; i < 12; i++)
137     {
138         RECT rcItem = rects[i];
139         INT x = (rcItem.left + rcItem.right) / 2, y = (rcItem.top + rcItem.bottom) / 2;
140 
141         INT iColor;
142         const BrushStyleAndWidth& data = c_BrushPresets[i];
143         if (data.width == toolsModel.GetBrushWidth() && data.style == toolsModel.GetBrushStyle())
144         {
145             iColor = COLOR_HIGHLIGHTTEXT;
146             ::FillRect(hdc, &rcItem, (HBRUSH)(COLOR_HIGHLIGHT + 1));
147         }
148         else
149         {
150             iColor = COLOR_WINDOWTEXT;
151         }
152 
153         Brush(hdc, x, y, x, y, ::GetSysColor(iColor), data.style, data.width);
154     }
155 }
156 
getLineRects(RECT rects[5],LPCRECT prc,LPPOINT ppt=NULL)157 static inline INT getLineRects(RECT rects[5], LPCRECT prc, LPPOINT ppt = NULL)
158 {
159     return getSplitRects(rects, 1, 5, prc, ppt);
160 }
161 
drawLine(HDC hdc,LPCRECT prc)162 VOID CToolSettingsWindow::drawLine(HDC hdc, LPCRECT prc)
163 {
164     RECT rects[5];
165     getLineRects(rects, prc);
166 
167     for (INT i = 0; i < 5; i++)
168     {
169         INT penWidth = i + 1;
170         CRect rcLine = rects[i];
171         rcLine.InflateRect(-2, 0);
172         rcLine.top = (rcLine.top + rcLine.bottom - penWidth) / 2;
173         rcLine.bottom = rcLine.top + penWidth;
174         if (toolsModel.GetLineWidth() == penWidth)
175         {
176             ::FillRect(hdc, &rects[i], ::GetSysColorBrush(COLOR_HIGHLIGHT));
177             ::FillRect(hdc, &rcLine, ::GetSysColorBrush(COLOR_HIGHLIGHTTEXT));
178         }
179         else
180         {
181             ::FillRect(hdc, &rcLine, ::GetSysColorBrush(COLOR_WINDOWTEXT));
182         }
183     }
184 }
185 
getAirBrushRects(RECT rects[4],LPCRECT prc,LPPOINT ppt=NULL)186 static INT getAirBrushRects(RECT rects[4], LPCRECT prc, LPPOINT ppt = NULL)
187 {
188     INT cx = (prc->right - prc->left), cy = (prc->bottom - prc->top);
189 
190     rects[0] = rects[1] = rects[2] = rects[3] = *prc;
191 
192     rects[0].right = rects[1].left = prc->left + cx * 3 / 8;
193     rects[0].bottom = rects[1].bottom = prc->top + cy / 2;
194 
195     rects[2].top = rects[3].top = prc->top + cy / 2;
196     rects[2].right = rects[3].left = prc->left + cx * 2 / 8;
197 
198     if (ppt)
199     {
200         for (INT i = 0; i < 4; ++i)
201         {
202             if (::PtInRect(&rects[i], *ppt))
203                 return i;
204         }
205     }
206     return -1;
207 }
208 
drawAirBrush(HDC hdc,LPCRECT prc)209 VOID CToolSettingsWindow::drawAirBrush(HDC hdc, LPCRECT prc)
210 {
211     RECT rects[4];
212     getAirBrushRects(rects, prc);
213 
214     srand(0);
215     for (size_t i = 0; i < 4; ++i)
216     {
217         RECT& rc = rects[i];
218         INT x = (rc.left + rc.right) / 2;
219         INT y = (rc.top + rc.bottom) / 2;
220         BOOL bHigh = (s_AirRadius[i] == toolsModel.GetAirBrushRadius());
221         if (bHigh)
222         {
223             ::FillRect(hdc, &rc, ::GetSysColorBrush(COLOR_HIGHLIGHT));
224 
225             for (int k = 0; k < 3; ++k)
226                 Airbrush(hdc, x, y, ::GetSysColor(COLOR_HIGHLIGHTTEXT), s_AirRadius[i]);
227         }
228         else
229         {
230             for (int k = 0; k < 3; ++k)
231                 Airbrush(hdc, x, y, ::GetSysColor(COLOR_WINDOWTEXT), s_AirRadius[i]);
232         }
233     }
234 }
235 
getBoxRects(RECT rects[3],LPCRECT prc,LPPOINT ppt=NULL)236 static inline INT getBoxRects(RECT rects[3], LPCRECT prc, LPPOINT ppt = NULL)
237 {
238     return getSplitRects(rects, 1, 3, prc, ppt);
239 }
240 
drawBox(HDC hdc,LPCRECT prc)241 VOID CToolSettingsWindow::drawBox(HDC hdc, LPCRECT prc)
242 {
243     CRect rects[3];
244     getBoxRects(rects, prc);
245 
246     for (INT iItem = 0; iItem < 3; ++iItem)
247     {
248         CRect& rcItem = rects[iItem];
249 
250         if (toolsModel.GetShapeStyle() == iItem)
251             ::FillRect(hdc, &rcItem, ::GetSysColorBrush(COLOR_HIGHLIGHT));
252 
253         rcItem.InflateRect(-5, -5);
254 
255         if (iItem <= 1)
256         {
257             COLORREF rgbPen;
258             if (toolsModel.GetShapeStyle() == iItem)
259                 rgbPen = ::GetSysColor(COLOR_HIGHLIGHTTEXT);
260             else
261                 rgbPen = ::GetSysColor(COLOR_WINDOWTEXT);
262             HGDIOBJ hOldBrush;
263             if (iItem == 0)
264                 hOldBrush = ::SelectObject(hdc, ::GetStockObject(NULL_BRUSH));
265             else
266                 hOldBrush = ::SelectObject(hdc, ::GetSysColorBrush(COLOR_APPWORKSPACE));
267             HGDIOBJ hOldPen = ::SelectObject(hdc, ::CreatePen(PS_SOLID, 1, rgbPen));
268             ::Rectangle(hdc, rcItem.left, rcItem.top, rcItem.right, rcItem.bottom);
269             ::DeleteObject(::SelectObject(hdc, hOldPen));
270             ::SelectObject(hdc, hOldBrush);
271         }
272         else
273         {
274             if (toolsModel.GetShapeStyle() == iItem)
275                 ::FillRect(hdc, &rcItem, ::GetSysColorBrush(COLOR_HIGHLIGHTTEXT));
276             else
277                 ::FillRect(hdc, &rcItem, ::GetSysColorBrush(COLOR_WINDOWTEXT));
278         }
279     }
280 }
281 
OnCreate(UINT nMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)282 LRESULT CToolSettingsWindow::OnCreate(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
283 {
284     /* preloading the draw transparent/nontransparent icons for later use */
285     m_hNontranspIcon = (HICON)LoadImageW(g_hinstExe, MAKEINTRESOURCEW(IDI_NONTRANSPARENT),
286                                          IMAGE_ICON, CX_TRANS_ICON, CY_TRANS_ICON, LR_DEFAULTCOLOR);
287     m_hTranspIcon = (HICON)LoadImageW(g_hinstExe, MAKEINTRESOURCEW(IDI_TRANSPARENT),
288                                       IMAGE_ICON, CX_TRANS_ICON, CY_TRANS_ICON, LR_DEFAULTCOLOR);
289 
290     CRect trackbarZoomPos, rect2;
291     calculateTwoBoxes(trackbarZoomPos, rect2);
292     trackbarZoomPos.InflateRect(-1, -1);
293 
294     trackbarZoom.Create(TRACKBAR_CLASS, m_hWnd, trackbarZoomPos, NULL, WS_CHILD | TBS_VERT | TBS_AUTOTICKS);
295     trackbarZoom.SendMessage(TBM_SETRANGE, TRUE, MAKELPARAM(MIN_ZOOM_TRACK, MAX_ZOOM_TRACK));
296     trackbarZoom.SendMessage(TBM_SETPOS, TRUE, DEFAULT_ZOOM_TRACK);
297     return 0;
298 }
299 
OnDestroy(UINT nMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)300 LRESULT CToolSettingsWindow::OnDestroy(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
301 {
302     ::DestroyIcon(m_hNontranspIcon);
303     ::DestroyIcon(m_hTranspIcon);
304     return 0;
305 }
306 
OnVScroll(UINT nMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)307 LRESULT CToolSettingsWindow::OnVScroll(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
308 {
309     INT trackPos = MAX_ZOOM_TRACK - (INT)trackbarZoom.SendMessage(TBM_GETPOS, 0, 0);
310     canvasWindow.zoomTo(MIN_ZOOM << trackPos);
311 
312     INT zoomRate = toolsModel.GetZoom();
313 
314     CStringW strZoom;
315     if (zoomRate % 10 == 0)
316         strZoom.Format(L"%d%%", zoomRate / 10);
317     else
318         strZoom.Format(L"%d.%d%%", zoomRate / 10, zoomRate % 10);
319 
320     ::SendMessageW(g_hStatusBar, SB_SETTEXT, 1, (LPARAM)(LPCWSTR)strZoom);
321 
322     OnToolsModelZoomChanged(nMsg, wParam, lParam, bHandled);
323     return 0;
324 }
325 
OnNotify(UINT nMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)326 LRESULT CToolSettingsWindow::OnNotify(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
327 {
328     NMHDR *pnmhdr = (NMHDR*)lParam;
329     if (pnmhdr->code == NM_CUSTOMDRAW)
330     {
331         NMCUSTOMDRAW *pCustomDraw = (NMCUSTOMDRAW*)pnmhdr;
332         pCustomDraw->uItemState &= ~CDIS_FOCUS; // Do not draw the focus
333     }
334     return 0;
335 }
336 
calculateTwoBoxes(CRect & rect1,CRect & rect2)337 VOID CToolSettingsWindow::calculateTwoBoxes(CRect& rect1, CRect& rect2)
338 {
339     CRect rcClient;
340     GetClientRect(&rcClient);
341     rcClient.InflateRect(-MARGIN1, -MARGIN1);
342 
343     INT yCenter = (rcClient.top + rcClient.bottom) / 2;
344     rect1.SetRect(rcClient.left, rcClient.top, rcClient.right, yCenter);
345     rect2.SetRect(rcClient.left, yCenter, rcClient.right, rcClient.bottom);
346 
347     rect1.InflateRect(-MARGIN2, -MARGIN2);
348     rect2.InflateRect(-MARGIN2, -MARGIN2);
349 }
350 
OnPaint(UINT nMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)351 LRESULT CToolSettingsWindow::OnPaint(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
352 {
353     CRect rect1, rect2;
354     calculateTwoBoxes(rect1, rect2);
355 
356     PAINTSTRUCT ps;
357     HDC hdc = BeginPaint(&ps);
358 
359     if (toolsModel.GetActiveTool() != TOOL_ZOOM)
360         ::DrawEdge(hdc, &rect1, BDR_SUNKENOUTER, BF_RECT | BF_MIDDLE);
361 
362     if (toolsModel.GetActiveTool() >= TOOL_RECT)
363         ::DrawEdge(hdc, &rect2, BDR_SUNKENOUTER, BF_RECT | BF_MIDDLE);
364 
365     rect1.InflateRect(-MARGIN2, -MARGIN2);
366     rect2.InflateRect(-MARGIN2, -MARGIN2);
367     switch (toolsModel.GetActiveTool())
368     {
369         case TOOL_FREESEL:
370         case TOOL_RECTSEL:
371         case TOOL_TEXT:
372             drawTrans(hdc, &rect1);
373             break;
374         case TOOL_RUBBER:
375             drawRubber(hdc, &rect1);
376             break;
377         case TOOL_BRUSH:
378             drawBrush(hdc, &rect1);
379             break;
380         case TOOL_AIRBRUSH:
381             drawAirBrush(hdc, &rect1);
382             break;
383         case TOOL_LINE:
384         case TOOL_BEZIER:
385             drawLine(hdc, &rect1);
386             break;
387         case TOOL_RECT:
388         case TOOL_SHAPE:
389         case TOOL_ELLIPSE:
390         case TOOL_RRECT:
391             drawBox(hdc, &rect1);
392             drawLine(hdc, &rect2);
393             break;
394         case TOOL_FILL:
395         case TOOL_COLOR:
396         case TOOL_ZOOM:
397         case TOOL_PEN:
398             break;
399     }
400     EndPaint(&ps);
401     return 0;
402 }
403 
OnLButtonDown(UINT nMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)404 LRESULT CToolSettingsWindow::OnLButtonDown(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
405 {
406     POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
407 
408     CRect rect1, rect2;
409     calculateTwoBoxes(rect1, rect2);
410     RECT rects[12];
411 
412     INT iItem;
413     switch (toolsModel.GetActiveTool())
414     {
415         case TOOL_FREESEL:
416         case TOOL_RECTSEL:
417         case TOOL_TEXT:
418             iItem = getTransRects(rects, &rect1, &pt);
419             if (iItem != -1)
420                 toolsModel.SetBackgroundTransparent(iItem);
421             break;
422         case TOOL_RUBBER:
423             iItem = getRubberRects(rects, &rect1, &pt);
424             if (iItem != -1)
425                 toolsModel.SetRubberRadius(iItem + 2);
426             break;
427         case TOOL_BRUSH:
428             iItem = getBrushRects(rects, &rect1, &pt);
429             if (iItem != -1)
430             {
431                 const BrushStyleAndWidth& data = c_BrushPresets[iItem];
432                 toolsModel.SetBrushStyle(data.style);
433                 toolsModel.SetBrushWidth(data.width);
434             }
435             break;
436         case TOOL_AIRBRUSH:
437             iItem = getAirBrushRects(rects, &rect1, &pt);
438             if (iItem != -1)
439                 toolsModel.SetAirBrushRadius(s_AirRadius[iItem]);
440             break;
441         case TOOL_LINE:
442         case TOOL_BEZIER:
443             iItem = getLineRects(rects, &rect1, &pt);
444             if (iItem != -1)
445                 toolsModel.SetLineWidth(iItem + 1);
446             break;
447         case TOOL_RECT:
448         case TOOL_SHAPE:
449         case TOOL_ELLIPSE:
450         case TOOL_RRECT:
451             iItem = getBoxRects(rects, &rect1, &pt);
452             if (iItem != -1)
453                 toolsModel.SetShapeStyle(iItem);
454 
455             iItem = getLineRects(rects, &rect2, &pt);
456             if (iItem != -1)
457                 toolsModel.SetLineWidth(iItem + 1);
458             break;
459         case TOOL_FILL:
460         case TOOL_COLOR:
461         case TOOL_ZOOM:
462         case TOOL_PEN:
463             break;
464     }
465 
466     ::SetCapture(::GetParent(m_hWnd));
467     return 0;
468 }
469 
OnToolsModelToolChanged(UINT nMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)470 LRESULT CToolSettingsWindow::OnToolsModelToolChanged(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
471 {
472     Invalidate();
473     trackbarZoom.ShowWindow((wParam == TOOL_ZOOM) ? SW_SHOW : SW_HIDE);
474     return 0;
475 }
476 
OnToolsModelSettingsChanged(UINT nMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)477 LRESULT CToolSettingsWindow::OnToolsModelSettingsChanged(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
478 {
479     Invalidate();
480     return 0;
481 }
482 
OnToolsModelZoomChanged(UINT nMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)483 LRESULT CToolSettingsWindow::OnToolsModelZoomChanged(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
484 {
485     int tbPos = MIN_ZOOM_TRACK;
486     int tempZoom = toolsModel.GetZoom();
487 
488     while (tempZoom > MIN_ZOOM)
489     {
490         tbPos++;
491         tempZoom = tempZoom >> 1;
492     }
493 
494     trackbarZoom.SendMessage(TBM_SETPOS, TRUE, MAX_ZOOM_TRACK - tbPos);
495     return 0;
496 }
497