1 /*
2  * PROJECT:    PAINT for ReactOS
3  * LICENSE:    LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later)
4  * PURPOSE:    Text editor and font chooser for the text tool
5  * COPYRIGHT:  Copyright 2015 Benedikt Freisen <b.freisen@gmx.net>
6  */
7 
8 #include "precomp.h"
9 
10 #define CXY_GRIP 3
11 
12 CTextEditWindow textEditWindow;
13 
14 /* FUNCTIONS ********************************************************/
15 
CTextEditWindow()16 CTextEditWindow::CTextEditWindow()
17     : m_hFont(NULL)
18     , m_hFontZoomed(NULL)
19 {
20     SetRectEmpty(&m_rc);
21 }
22 
DoHitTest(RECT & rc,POINT pt)23 INT CTextEditWindow::DoHitTest(RECT& rc, POINT pt)
24 {
25     switch (getSizeBoxHitTest(pt, &rc))
26     {
27         case HIT_NONE:          return HTNOWHERE;
28         case HIT_UPPER_LEFT:    return HTTOPLEFT;
29         case HIT_UPPER_CENTER:  return HTTOP;
30         case HIT_UPPER_RIGHT:   return HTTOPRIGHT;
31         case HIT_MIDDLE_LEFT:   return HTLEFT;
32         case HIT_MIDDLE_RIGHT:  return HTRIGHT;
33         case HIT_LOWER_LEFT:    return HTBOTTOMLEFT;
34         case HIT_LOWER_CENTER:  return HTBOTTOM;
35         case HIT_LOWER_RIGHT:   return HTBOTTOMRIGHT;
36         case HIT_BORDER:        return HTCAPTION; // Enable drag move
37         case HIT_INNER:         return HTCLIENT;
38     }
39     return HTNOWHERE;
40 }
41 
DrawGrip(HDC hDC,RECT & rc)42 void CTextEditWindow::DrawGrip(HDC hDC, RECT& rc)
43 {
44     drawSizeBoxes(hDC, &rc, TRUE, NULL);
45 }
46 
FixEditPos(LPCWSTR pszOldText)47 void CTextEditWindow::FixEditPos(LPCWSTR pszOldText)
48 {
49     CStringW szText;
50     GetWindowText(szText);
51 
52     RECT rcParent;
53     ::GetWindowRect(m_hwndParent, &rcParent);
54 
55     CRect rc, rcWnd, rcText;
56     GetWindowRect(&rcWnd);
57     rcText = rcWnd;
58 
59     HDC hDC = GetDC();
60     if (hDC)
61     {
62         SelectObject(hDC, m_hFontZoomed);
63         TEXTMETRIC tm;
64         GetTextMetrics(hDC, &tm);
65         szText += L"x"; // This is a trick to enable the g_ptEnd newlines
66         const UINT uFormat = DT_LEFT | DT_TOP | DT_EDITCONTROL | DT_NOPREFIX | DT_NOCLIP |
67                              DT_EXPANDTABS | DT_WORDBREAK;
68         DrawTextW(hDC, szText, -1, &rcText, uFormat | DT_CALCRECT);
69         if (tm.tmDescent > 0)
70             rcText.bottom += tm.tmDescent;
71         ReleaseDC(hDC);
72     }
73 
74     UnionRect(&rc, &rcText, &rcWnd);
75     ::MapWindowPoints(NULL, m_hwndParent, (LPPOINT)&rc, 2);
76 
77     rcWnd = rc;
78     ::GetClientRect(m_hwndParent, &rcParent);
79     rc.IntersectRect(&rcParent, &rcWnd);
80 
81     MoveWindow(rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, FALSE);
82 
83     DefWindowProc(WM_HSCROLL, SB_LEFT, 0);
84     DefWindowProc(WM_VSCROLL, SB_TOP, 0);
85 
86     ::InvalidateRect(m_hwndParent, &rc, TRUE);
87 }
88 
OnChar(UINT nMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)89 LRESULT CTextEditWindow::OnChar(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
90 {
91     if (wParam == VK_TAB)
92         return 0; // FIXME: Tabs
93 
94     CStringW szText;
95     GetWindowText(szText);
96 
97     LRESULT ret = DefWindowProc(nMsg, wParam, lParam);
98     FixEditPos(szText);
99 
100     return ret;
101 }
102 
OnKeyDown(UINT nMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)103 LRESULT CTextEditWindow::OnKeyDown(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
104 {
105     if (wParam == VK_ESCAPE)
106     {
107         toolsModel.OnEndDraw(TRUE);
108         return 0;
109     }
110 
111     CStringW szText;
112     GetWindowText(szText);
113 
114     LRESULT ret = DefWindowProc(nMsg, wParam, lParam);
115     FixEditPos(szText);
116     return ret;
117 }
118 
OnLButtonDown(UINT nMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)119 LRESULT CTextEditWindow::OnLButtonDown(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
120 {
121     LRESULT ret = DefWindowProc(nMsg, wParam, lParam);
122     DefWindowProc(WM_HSCROLL, SB_LEFT, 0);
123     DefWindowProc(WM_VSCROLL, SB_TOP, 0);
124     return ret;
125 }
126 
OnEraseBkGnd(UINT nMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)127 LRESULT CTextEditWindow::OnEraseBkGnd(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
128 {
129     HDC hDC = (HDC)wParam;
130     if (!toolsModel.IsBackgroundTransparent())
131     {
132         RECT rc;
133         GetClientRect(&rc);
134         HBRUSH hbr = CreateSolidBrush(paletteModel.GetBgColor());
135         FillRect(hDC, &rc, hbr);
136         DeleteObject(hbr);
137     }
138     ::SetTextColor(hDC, paletteModel.GetFgColor());
139     return TRUE;
140 }
141 
OnPaint(UINT nMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)142 LRESULT CTextEditWindow::OnPaint(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
143 {
144     RECT rc;
145     GetClientRect(&rc);
146 
147     DefWindowProc(nMsg, wParam, lParam);
148 
149     HDC hDC = GetDC();
150     if (hDC)
151     {
152         DrawGrip(hDC, rc);
153         ReleaseDC(hDC);
154     }
155 
156     return 0;
157 }
158 
OnNCPaint(UINT nMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)159 LRESULT CTextEditWindow::OnNCPaint(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
160 {
161     CRect rc;
162     GetWindowRect(&rc);
163 
164     HDC hDC = GetDCEx(NULL, DCX_WINDOW | DCX_PARENTCLIP);
165     if (hDC)
166     {
167         rc.OffsetRect(-rc.left, -rc.top);
168         DrawGrip(hDC, rc);
169         ReleaseDC(hDC);
170     }
171 
172     return 0;
173 }
174 
OnNCCalcSize(UINT nMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)175 LRESULT CTextEditWindow::OnNCCalcSize(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
176 {
177     return 0; // No frame.
178 }
179 
OnNCHitTest(UINT nMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)180 LRESULT CTextEditWindow::OnNCHitTest(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
181 {
182     POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
183     RECT rc;
184     GetWindowRect(&rc);
185     return DoHitTest(rc, pt);
186 }
187 
OnSetCursor(UINT nMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)188 LRESULT CTextEditWindow::OnSetCursor(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
189 {
190     if (CWaitCursor::IsWaiting())
191     {
192         bHandled = FALSE;
193         return 0;
194     }
195 
196     UINT nHitTest = LOWORD(lParam);
197     if (nHitTest == HTCAPTION)
198     {
199         ::SetCursor(::LoadCursorW(NULL, (LPCWSTR)IDC_SIZEALL)); // Enable drag move
200         return FALSE;
201     }
202     return DefWindowProc(nMsg, wParam, lParam);
203 }
204 
OnMove(UINT nMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)205 LRESULT CTextEditWindow::OnMove(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
206 {
207     LRESULT ret = DefWindowProc(nMsg, wParam, lParam);
208     return ret;
209 }
210 
OnSize(UINT nMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)211 LRESULT CTextEditWindow::OnSize(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
212 {
213     LRESULT ret = DefWindowProc(nMsg, wParam, lParam);
214 
215     RECT rc;
216     GetClientRect(&rc);
217     SendMessage(EM_SETRECTNP, 0, (LPARAM)&rc);
218     SendMessage(EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELONG(0, 0));
219 
220     return ret;
221 }
222 
223 // Hack: Use DECLARE_WND_SUPERCLASS instead!
Create(HWND hwndParent)224 HWND CTextEditWindow::Create(HWND hwndParent)
225 {
226     m_hwndParent = hwndParent;
227 
228     const DWORD style = ES_LEFT | ES_MULTILINE | ES_WANTRETURN | ES_AUTOVSCROLL |
229                         WS_CHILD | WS_THICKFRAME;
230     HWND hwnd = ::CreateWindowEx(0, WC_EDIT, NULL, style, 0, 0, 0, 0,
231                                  hwndParent, NULL, g_hinstExe, NULL);
232     if (hwnd)
233     {
234 #undef SubclassWindow // Don't use this macro
235         SubclassWindow(hwnd);
236 
237         UpdateFont();
238 
239         PostMessage(WM_SIZE, 0, 0);
240     }
241 
242     return m_hWnd;
243 }
244 
DoFillBack(HWND hwnd,HDC hDC)245 void CTextEditWindow::DoFillBack(HWND hwnd, HDC hDC)
246 {
247     if (toolsModel.IsBackgroundTransparent())
248         return;
249 
250     RECT rc;
251     SendMessage(EM_GETRECT, 0, (LPARAM)&rc);
252     MapWindowPoints(hwnd, (LPPOINT)&rc, 2);
253 
254     HBRUSH hbr = CreateSolidBrush(paletteModel.GetBgColor());
255     FillRect(hDC, &rc, hbr);
256     DeleteObject(hbr);
257 }
258 
OnCreate(UINT nMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)259 LRESULT CTextEditWindow::OnCreate(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
260 {
261     UpdateFont();
262     return 0;
263 }
264 
OnClose(UINT nMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)265 LRESULT CTextEditWindow::OnClose(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
266 {
267     ShowWindow(SW_HIDE);
268     if (m_hFont)
269     {
270         DeleteObject(m_hFont);
271         m_hFont = NULL;
272     }
273     if (m_hFontZoomed)
274     {
275         DeleteObject(m_hFontZoomed);
276         m_hFontZoomed = NULL;
277     }
278     return 0;
279 }
280 
InvalidateEditRect()281 void CTextEditWindow::InvalidateEditRect()
282 {
283     RECT rc;
284     GetWindowRect(&rc);
285     ::MapWindowPoints(NULL, m_hwndParent, (LPPOINT)&rc, 2);
286     ::InvalidateRect(m_hwndParent, &rc, TRUE);
287 
288     GetClientRect(&rc);
289     MapWindowPoints(canvasWindow, (LPPOINT)&rc, 2);
290     canvasWindow.CanvasToImage(rc);
291     m_rc = rc;
292 }
293 
OnPaletteModelColorChanged(UINT nMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)294 LRESULT CTextEditWindow::OnPaletteModelColorChanged(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
295 {
296     UpdateFont();
297     return 0;
298 }
299 
OnToolsModelSettingsChanged(UINT nMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)300 LRESULT CTextEditWindow::OnToolsModelSettingsChanged(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
301 {
302     UpdateFont();
303     return 0;
304 }
305 
OnToolsModelZoomChanged(UINT nMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)306 LRESULT CTextEditWindow::OnToolsModelZoomChanged(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
307 {
308     UpdateFont();
309     ValidateEditRect(NULL);
310     return 0;
311 }
312 
OnToolsModelToolChanged(UINT nMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)313 LRESULT CTextEditWindow::OnToolsModelToolChanged(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
314 {
315     if (wParam == TOOL_TEXT)
316     {
317         UpdateFont();
318     }
319     else
320     {
321         ShowWindow(SW_HIDE);
322     }
323     return 0;
324 }
325 
UpdateFont()326 void CTextEditWindow::UpdateFont()
327 {
328     if (m_hFont)
329     {
330         DeleteObject(m_hFont);
331         m_hFont = NULL;
332     }
333     if (m_hFontZoomed)
334     {
335         DeleteObject(m_hFontZoomed);
336         m_hFontZoomed = NULL;
337     }
338 
339     LOGFONTW lf;
340     ZeroMemory(&lf, sizeof(lf));
341     lf.lfCharSet = DEFAULT_CHARSET; // registrySettings.CharSet; // Ignore
342     lf.lfWeight = (registrySettings.Bold ? FW_BOLD : FW_NORMAL);
343     lf.lfItalic = (BYTE)registrySettings.Italic;
344     lf.lfUnderline = (BYTE)registrySettings.Underline;
345     StringCchCopyW(lf.lfFaceName, _countof(lf.lfFaceName), registrySettings.strFontName);
346 
347     HDC hdc = GetDC();
348     if (hdc)
349     {
350         INT nFontSize = registrySettings.PointSize;
351         lf.lfHeight = -MulDiv(nFontSize, GetDeviceCaps(hdc, LOGPIXELSY), 72);
352         ReleaseDC(hdc);
353     }
354 
355     m_hFont = ::CreateFontIndirect(&lf);
356 
357     lf.lfHeight = Zoomed(lf.lfHeight);
358     m_hFontZoomed = ::CreateFontIndirect(&lf);
359 
360     SetWindowFont(m_hWnd, m_hFontZoomed, TRUE);
361     DefWindowProc(EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELONG(0, 0));
362 
363     FixEditPos(NULL);
364 
365     Invalidate();
366 }
367 
OnSetSel(UINT nMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)368 LRESULT CTextEditWindow::OnSetSel(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
369 {
370     LRESULT ret = DefWindowProc(nMsg, wParam, lParam);
371     DefWindowProc(WM_HSCROLL, SB_LEFT, 0);
372     DefWindowProc(WM_VSCROLL, SB_TOP, 0);
373     InvalidateEditRect();
374     return ret;
375 }
376 
GetEditRect(LPRECT prc) const377 BOOL CTextEditWindow::GetEditRect(LPRECT prc) const
378 {
379     *prc = m_rc;
380     return TRUE;
381 }
382 
ValidateEditRect(LPCRECT prc OPTIONAL)383 void CTextEditWindow::ValidateEditRect(LPCRECT prc OPTIONAL)
384 {
385     if (prc)
386         m_rc = *prc;
387 
388     CRect rc = m_rc;
389     canvasWindow.ImageToCanvas(rc);
390 
391     MoveWindow(rc.left, rc.top, rc.Width(), rc.Height(), TRUE);
392 }
393 
OnMoving(UINT nMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)394 LRESULT CTextEditWindow::OnMoving(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
395 {
396     // Restrict the window position to the image area
397     LPRECT prcMoving = (LPRECT)lParam;
398     CRect rcMoving = *prcMoving;
399 
400     CRect rcImage;
401     canvasWindow.GetImageRect(rcImage);
402     canvasWindow.ImageToCanvas(rcImage);
403     canvasWindow.MapWindowPoints(NULL, &rcImage);
404 
405     CRect rcWnd;
406     GetWindowRect(&rcWnd);
407     INT cx = rcWnd.Width(), cy = rcWnd.Height();
408 
409     if (rcMoving.left < rcImage.left)
410     {
411         rcMoving.left = rcImage.left;
412         rcMoving.right = rcImage.left + cx;
413     }
414     else if (rcMoving.right > rcImage.right)
415     {
416         rcMoving.right = rcImage.right;
417         rcMoving.left = rcImage.right - cx;
418     }
419 
420     if (rcMoving.top < rcImage.top)
421     {
422         rcMoving.top = rcImage.top;
423         rcMoving.bottom = rcImage.top + cy;
424     }
425     else if (rcMoving.bottom > rcImage.bottom)
426     {
427         rcMoving.bottom = rcImage.bottom;
428         rcMoving.top = rcImage.bottom - cy;
429     }
430 
431     *prcMoving = rcMoving;
432     Invalidate(TRUE);
433     return TRUE;
434 }
435 
OnSizing(UINT nMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)436 LRESULT CTextEditWindow::OnSizing(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
437 {
438     // Restrict the window size to the image area
439     LPRECT prcSizing = (LPRECT)lParam;
440     CRect rcSizing = *prcSizing;
441 
442     CRect rcImage;
443     canvasWindow.GetImageRect(rcImage);
444     canvasWindow.ImageToCanvas(rcImage);
445     canvasWindow.MapWindowPoints(NULL, &rcImage);
446 
447     // Horizontally
448     switch (wParam)
449     {
450         case WMSZ_BOTTOMLEFT:
451         case WMSZ_LEFT:
452         case WMSZ_TOPLEFT:
453             if (rcSizing.left < rcImage.left)
454                 rcSizing.left = rcImage.left;
455             break;
456         case WMSZ_BOTTOMRIGHT:
457         case WMSZ_RIGHT:
458         case WMSZ_TOPRIGHT:
459             if (rcSizing.right > rcImage.right)
460                 rcSizing.right = rcImage.right;
461             break;
462         case WMSZ_TOP:
463         case WMSZ_BOTTOM:
464         default:
465             break;
466     }
467 
468     // Vertically
469     switch (wParam)
470     {
471         case WMSZ_BOTTOM:
472         case WMSZ_BOTTOMLEFT:
473         case WMSZ_BOTTOMRIGHT:
474             if (rcSizing.bottom > rcImage.bottom)
475                 rcSizing.bottom = rcImage.bottom;
476             break;
477         case WMSZ_TOP:
478         case WMSZ_TOPLEFT:
479         case WMSZ_TOPRIGHT:
480             if (rcSizing.top < rcImage.top)
481                 rcSizing.top = rcImage.top;
482             break;
483         case WMSZ_LEFT:
484         case WMSZ_RIGHT:
485         default:
486             break;
487     }
488 
489     *prcSizing = rcSizing;
490     Invalidate(TRUE);
491     return TRUE;
492 }
493 
OnMouseWheel(UINT nMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)494 LRESULT CTextEditWindow::OnMouseWheel(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
495 {
496     return ::SendMessageW(GetParent(), nMsg, wParam, lParam);
497 }
498 
OnCut(UINT nMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)499 LRESULT CTextEditWindow::OnCut(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
500 {
501     LRESULT ret = DefWindowProc(nMsg, wParam, lParam);
502     Invalidate(TRUE); // Redraw
503     return ret;
504 }
505 
OnPaste(UINT nMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)506 LRESULT CTextEditWindow::OnPaste(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
507 {
508     LRESULT ret = DefWindowProc(nMsg, wParam, lParam);
509     FixEditPos(NULL);
510     return ret;
511 }
512 
OnClear(UINT nMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)513 LRESULT CTextEditWindow::OnClear(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
514 {
515     LRESULT ret = DefWindowProc(nMsg, wParam, lParam);
516     Invalidate(TRUE); // Redraw
517     return ret;
518 }
519