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