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