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 
16 CTextEditWindow::CTextEditWindow()
17     : m_hFont(NULL)
18     , m_hFontZoomed(NULL)
19 {
20     SetRectEmpty(&m_rc);
21 }
22 
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 
42 void CTextEditWindow::DrawGrip(HDC hDC, RECT& rc)
43 {
44     drawSizeBoxes(hDC, &rc, TRUE, NULL);
45 }
46 
47 void CTextEditWindow::FixEditPos(LPCTSTR pszOldText)
48 {
49     CString szText;
50     GetWindowText(szText);
51 
52     RECT rcParent;
53     ::GetWindowRect(m_hwndParent, &rcParent);
54 
55     RECT 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 += TEXT("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         DrawText(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     IntersectRect(&rc, &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 
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     CString szText;
95     GetWindowText(szText);
96 
97     LRESULT ret = DefWindowProc(nMsg, wParam, lParam);
98     FixEditPos(szText);
99 
100     return ret;
101 }
102 
103 LRESULT CTextEditWindow::OnKeyDown(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
104 {
105     if (wParam == VK_ESCAPE)
106     {
107         toolsModel.OnCancelDraw();
108         return 0;
109     }
110 
111     CString szText;
112     GetWindowText(szText);
113 
114     LRESULT ret = DefWindowProc(nMsg, wParam, lParam);
115     FixEditPos(szText);
116     return ret;
117 }
118 
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 
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 
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 
159 LRESULT CTextEditWindow::OnNCPaint(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
160 {
161     RECT rc;
162     GetWindowRect(&rc);
163 
164     HDC hDC = GetDCEx(NULL, DCX_WINDOW | DCX_PARENTCLIP);
165     if (hDC)
166     {
167         OffsetRect(&rc, -rc.left, -rc.top);
168         DrawGrip(hDC, rc);
169         ReleaseDC(hDC);
170     }
171 
172     return 0;
173 }
174 
175 LRESULT CTextEditWindow::OnNCCalcSize(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
176 {
177     return 0; // No frame.
178 }
179 
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 
188 LRESULT CTextEditWindow::OnSetCursor(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
189 {
190     UINT nHitTest = LOWORD(lParam);
191     if (nHitTest == HTCAPTION)
192     {
193         ::SetCursor(::LoadCursor(NULL, IDC_SIZEALL)); // Enable drag move
194         return FALSE;
195     }
196     return DefWindowProc(nMsg, wParam, lParam);
197 }
198 
199 LRESULT CTextEditWindow::OnMove(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
200 {
201     LRESULT ret = DefWindowProc(nMsg, wParam, lParam);
202     return ret;
203 }
204 
205 LRESULT CTextEditWindow::OnSize(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
206 {
207     LRESULT ret = DefWindowProc(nMsg, wParam, lParam);
208 
209     RECT rc;
210     GetClientRect(&rc);
211     SendMessage(EM_SETRECTNP, 0, (LPARAM)&rc);
212     SendMessage(EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELONG(0, 0));
213 
214     return ret;
215 }
216 
217 // Hack: Use DECLARE_WND_SUPERCLASS instead!
218 HWND CTextEditWindow::Create(HWND hwndParent)
219 {
220     m_hwndParent = hwndParent;
221 
222     const DWORD style = ES_LEFT | ES_MULTILINE | ES_WANTRETURN | ES_AUTOVSCROLL |
223                         WS_CHILD | WS_THICKFRAME;
224     HWND hwnd = ::CreateWindowEx(0, WC_EDIT, NULL, style, 0, 0, 0, 0,
225                                  hwndParent, NULL, g_hinstExe, NULL);
226     if (hwnd)
227     {
228 #undef SubclassWindow // Don't use this macro
229         SubclassWindow(hwnd);
230 
231         UpdateFont();
232 
233         PostMessage(WM_SIZE, 0, 0);
234     }
235 
236     return m_hWnd;
237 }
238 
239 void CTextEditWindow::DoFillBack(HWND hwnd, HDC hDC)
240 {
241     if (toolsModel.IsBackgroundTransparent())
242         return;
243 
244     RECT rc;
245     SendMessage(EM_GETRECT, 0, (LPARAM)&rc);
246     MapWindowPoints(hwnd, (LPPOINT)&rc, 2);
247 
248     HBRUSH hbr = CreateSolidBrush(paletteModel.GetBgColor());
249     FillRect(hDC, &rc, hbr);
250     DeleteObject(hbr);
251 }
252 
253 LRESULT CTextEditWindow::OnCreate(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
254 {
255     UpdateFont();
256     return 0;
257 }
258 
259 LRESULT CTextEditWindow::OnClose(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
260 {
261     ShowWindow(SW_HIDE);
262     if (m_hFont)
263     {
264         DeleteObject(m_hFont);
265         m_hFont = NULL;
266     }
267     if (m_hFontZoomed)
268     {
269         DeleteObject(m_hFontZoomed);
270         m_hFontZoomed = NULL;
271     }
272     return 0;
273 }
274 
275 void CTextEditWindow::InvalidateEditRect()
276 {
277     RECT rc;
278     GetWindowRect(&rc);
279     ::MapWindowPoints(NULL, m_hwndParent, (LPPOINT)&rc, 2);
280     ::InvalidateRect(m_hwndParent, &rc, TRUE);
281 
282     GetClientRect(&rc);
283     MapWindowPoints(canvasWindow, (LPPOINT)&rc, 2);
284     canvasWindow.CanvasToImage(rc);
285     m_rc = rc;
286 }
287 
288 LRESULT CTextEditWindow::OnPaletteModelColorChanged(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
289 {
290     UpdateFont();
291     return 0;
292 }
293 
294 LRESULT CTextEditWindow::OnToolsModelSettingsChanged(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
295 {
296     UpdateFont();
297     return 0;
298 }
299 
300 LRESULT CTextEditWindow::OnToolsModelZoomChanged(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
301 {
302     UpdateFont();
303     ValidateEditRect(NULL);
304     return 0;
305 }
306 
307 LRESULT CTextEditWindow::OnToolsModelToolChanged(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
308 {
309     if (wParam == TOOL_TEXT)
310     {
311         UpdateFont();
312     }
313     else
314     {
315         ShowWindow(SW_HIDE);
316     }
317     return 0;
318 }
319 
320 void CTextEditWindow::UpdateFont()
321 {
322     if (m_hFont)
323     {
324         DeleteObject(m_hFont);
325         m_hFont = NULL;
326     }
327     if (m_hFontZoomed)
328     {
329         DeleteObject(m_hFontZoomed);
330         m_hFontZoomed = NULL;
331     }
332 
333     LOGFONT lf;
334     ZeroMemory(&lf, sizeof(lf));
335     lf.lfCharSet = DEFAULT_CHARSET; // registrySettings.CharSet; // Ignore
336     lf.lfWeight = (registrySettings.Bold ? FW_BOLD : FW_NORMAL);
337     lf.lfItalic = registrySettings.Italic;
338     lf.lfUnderline = registrySettings.Underline;
339     lstrcpyn(lf.lfFaceName, registrySettings.strFontName, _countof(lf.lfFaceName));
340 
341     HDC hdc = GetDC();
342     if (hdc)
343     {
344         INT nFontSize = registrySettings.PointSize;
345         lf.lfHeight = -MulDiv(nFontSize, GetDeviceCaps(hdc, LOGPIXELSY), 72);
346         ReleaseDC(hdc);
347     }
348 
349     m_hFont = ::CreateFontIndirect(&lf);
350 
351     lf.lfHeight = Zoomed(lf.lfHeight);
352     m_hFontZoomed = ::CreateFontIndirect(&lf);
353 
354     SetWindowFont(m_hWnd, m_hFontZoomed, TRUE);
355     DefWindowProc(EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELONG(0, 0));
356 
357     FixEditPos(NULL);
358 
359     Invalidate();
360 }
361 
362 LRESULT CTextEditWindow::OnSetSel(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
363 {
364     LRESULT ret = DefWindowProc(nMsg, wParam, lParam);
365     DefWindowProc(WM_HSCROLL, SB_LEFT, 0);
366     DefWindowProc(WM_VSCROLL, SB_TOP, 0);
367     InvalidateEditRect();
368     return ret;
369 }
370 
371 BOOL CTextEditWindow::GetEditRect(LPRECT prc) const
372 {
373     *prc = m_rc;
374     return TRUE;
375 }
376 
377 void CTextEditWindow::ValidateEditRect(LPCRECT prc OPTIONAL)
378 {
379     if (prc)
380         m_rc = *prc;
381 
382     CRect rc = m_rc;
383     canvasWindow.ImageToCanvas(rc);
384 
385     MoveWindow(rc.left, rc.top, rc.Width(), rc.Height(), TRUE);
386 }
387 
388 LRESULT CTextEditWindow::OnMoving(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
389 {
390     // Restrict the window position to the image area
391     LPRECT prcMoving = (LPRECT)lParam;
392     CRect rcMoving = *prcMoving;
393 
394     CRect rcImage;
395     canvasWindow.GetImageRect(rcImage);
396     canvasWindow.ImageToCanvas(rcImage);
397     canvasWindow.MapWindowPoints(NULL, &rcImage);
398 
399     CRect rcWnd;
400     GetWindowRect(&rcWnd);
401     INT cx = rcWnd.Width(), cy = rcWnd.Height();
402 
403     if (rcMoving.left < rcImage.left)
404     {
405         rcMoving.left = rcImage.left;
406         rcMoving.right = rcImage.left + cx;
407     }
408     else if (rcMoving.right > rcImage.right)
409     {
410         rcMoving.right = rcImage.right;
411         rcMoving.left = rcImage.right - cx;
412     }
413 
414     if (rcMoving.top < rcImage.top)
415     {
416         rcMoving.top = rcImage.top;
417         rcMoving.bottom = rcImage.top + cy;
418     }
419     else if (rcMoving.bottom > rcImage.bottom)
420     {
421         rcMoving.bottom = rcImage.bottom;
422         rcMoving.top = rcImage.bottom - cy;
423     }
424 
425     *prcMoving = rcMoving;
426     Invalidate(TRUE);
427     return TRUE;
428 }
429 
430 LRESULT CTextEditWindow::OnSizing(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
431 {
432     // Restrict the window size to the image area
433     LPRECT prcSizing = (LPRECT)lParam;
434     CRect rcSizing = *prcSizing;
435 
436     CRect rcImage;
437     canvasWindow.GetImageRect(rcImage);
438     canvasWindow.ImageToCanvas(rcImage);
439     canvasWindow.MapWindowPoints(NULL, &rcImage);
440 
441     // Horizontally
442     switch (wParam)
443     {
444         case WMSZ_BOTTOMLEFT:
445         case WMSZ_LEFT:
446         case WMSZ_TOPLEFT:
447             if (rcSizing.left < rcImage.left)
448                 rcSizing.left = rcImage.left;
449             break;
450         case WMSZ_BOTTOMRIGHT:
451         case WMSZ_RIGHT:
452         case WMSZ_TOPRIGHT:
453             if (rcSizing.right > rcImage.right)
454                 rcSizing.right = rcImage.right;
455             break;
456         case WMSZ_TOP:
457         case WMSZ_BOTTOM:
458         default:
459             break;
460     }
461 
462     // Vertically
463     switch (wParam)
464     {
465         case WMSZ_BOTTOM:
466         case WMSZ_BOTTOMLEFT:
467         case WMSZ_BOTTOMRIGHT:
468             if (rcSizing.bottom > rcImage.bottom)
469                 rcSizing.bottom = rcImage.bottom;
470             break;
471         case WMSZ_TOP:
472         case WMSZ_TOPLEFT:
473         case WMSZ_TOPRIGHT:
474             if (rcSizing.top < rcImage.top)
475                 rcSizing.top = rcImage.top;
476             break;
477         case WMSZ_LEFT:
478         case WMSZ_RIGHT:
479         default:
480             break;
481     }
482 
483     *prcSizing = rcSizing;
484     Invalidate(TRUE);
485     return TRUE;
486 }
487 
488 LRESULT CTextEditWindow::OnMouseWheel(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
489 {
490     return ::SendMessage(GetParent(), nMsg, wParam, lParam);
491 }
492 
493 LRESULT CTextEditWindow::OnCut(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
494 {
495     LRESULT ret = DefWindowProc(nMsg, wParam, lParam);
496     Invalidate(TRUE); // Redraw
497     return ret;
498 }
499 
500 LRESULT CTextEditWindow::OnPaste(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
501 {
502     LRESULT ret = DefWindowProc(nMsg, wParam, lParam);
503     FixEditPos(NULL);
504     return ret;
505 }
506 
507 LRESULT CTextEditWindow::OnClear(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
508 {
509     LRESULT ret = DefWindowProc(nMsg, wParam, lParam);
510     Invalidate(TRUE); // Redraw
511     return ret;
512 }
513