ReactOS 0.4.15-dev-7889-g76290a6
edit.c
Go to the documentation of this file.
1/*
2 * Edit control
3 *
4 * Copyright David W. Metcalfe, 1994
5 * Copyright William Magro, 1995, 1996
6 * Copyright Frans van Dorsselaer, 1996, 1997
7 * Copyright Frank Richter, 2005
8 *
9 *
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
14 *
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
19 *
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 *
24 * TODO:
25 * - EDITBALLOONTIP structure
26 * - EM_GETCUEBANNER/Edit_GetCueBannerText
27 * - EM_HIDEBALLOONTIP/Edit_HideBalloonTip
28 * - EM_SETCUEBANNER/Edit_SetCueBannerText
29 * - EM_SHOWBALLOONTIP/Edit_ShowBalloonTip
30 * - EM_GETIMESTATUS, EM_SETIMESTATUS
31 * - EN_ALIGN_LTR_EC
32 * - EN_ALIGN_RTL_EC
33 * - ES_OEMCONVERT
34 *
35 */
36
37#include "config.h"
38
39#include <stdarg.h>
40#include <string.h>
41#include <stdlib.h>
42
43#include "windef.h"
44#include "winbase.h"
45#include "wingdi.h"
46#include "winuser.h"
47#include "imm.h"
48#ifdef __REACTOS__
49 #include <immdev.h>
50 #include <imm32_undoc.h>
51#endif
52#include "usp10.h"
53#include "commctrl.h"
54#include "uxtheme.h"
55#include "vsstyle.h"
56#include "wine/unicode.h"
57#include "wine/debug.h"
58#include "wine/heap.h"
59
61
62#define BUFLIMIT_INITIAL 30000 /* initial buffer size */
63#define GROWLENGTH 32 /* buffers granularity in bytes: must be power of 2 */
64#define ROUND_TO_GROW(size) (((size) + (GROWLENGTH - 1)) & ~(GROWLENGTH - 1))
65#define HSCROLL_FRACTION 3 /* scroll window by 1/3 width */
66
67/*
68 * extra flags for EDITSTATE.flags field
69 */
70#define EF_MODIFIED 0x0001 /* text has been modified */
71#define EF_FOCUSED 0x0002 /* we have input focus */
72#define EF_UPDATE 0x0004 /* notify parent of changed state */
73#define EF_VSCROLL_TRACK 0x0008 /* don't SetScrollPos() since we are tracking the thumb */
74#define EF_HSCROLL_TRACK 0x0010 /* don't SetScrollPos() since we are tracking the thumb */
75#define EF_AFTER_WRAP 0x0080 /* the caret is displayed after the last character of a
76 wrapped line, instead of in front of the next character */
77#define EF_USE_SOFTBRK 0x0100 /* Enable soft breaks in text. */
78#define EF_DIALOGMODE 0x0200 /* Indicates that we are inside a dialog window */
80#define ID_CB_LISTBOX 1000
82typedef enum
84 END_0 = 0, /* line ends with terminating '\0' character */
85 END_WRAP, /* line is wrapped */
86 END_HARD, /* line ends with a hard return '\r\n' */
87 END_SOFT, /* line ends with a soft return '\r\r\n' */
88 END_RICH /* line ends with a single '\n' */
89} LINE_END;
91typedef struct tagLINEDEF {
92 INT length; /* bruto length of a line in bytes */
93 INT net_length; /* netto length of a line in visible characters */
95 INT width; /* width of the line in pixels */
96 INT index; /* line index into the buffer */
97 SCRIPT_STRING_ANALYSIS ssa; /* Uniscribe Data */
99} LINEDEF;
101typedef struct
103 LPWSTR text; /* the actual contents of the control */
104 UINT text_length; /* cached length of text buffer (in WCHARs) - use get_text_length() to retrieve */
105 UINT buffer_size; /* the size of the buffer in characters */
106 UINT buffer_limit; /* the maximum size to which the buffer may grow in characters */
107 HFONT font; /* NULL means standard system font */
108 INT x_offset; /* scroll offset for multi lines this is in pixels
109 for single lines it's in characters */
110#ifdef __REACTOS__
111 DWORD dwCaretWidth;
112#endif
113 INT line_height; /* height of a screen line in pixels */
114 INT char_width; /* average character width in pixels */
115 DWORD style; /* sane version of wnd->dwStyle */
116 WORD flags; /* flags that are not in es->style or wnd->flags (EF_XXX) */
117 INT undo_insert_count; /* number of characters inserted in sequence */
118 UINT undo_position; /* character index of the insertion and deletion */
119 LPWSTR undo_text; /* deleted text */
120 UINT undo_buffer_size; /* size of the deleted text buffer */
121 INT selection_start; /* == selection_end if no selection */
122 INT selection_end; /* == current caret position */
123 WCHAR password_char; /* == 0 if no password char, and for multi line controls */
124 INT left_margin; /* in pixels */
125 INT right_margin; /* in pixels */
126 RECT format_rect;
127 INT text_width; /* width of the widest line in pixels for multi line controls
128 and just line width for single line controls */
129 INT region_posx; /* Position of cursor relative to region: */
130 INT region_posy; /* -1: to left, 0: within, 1: to right */
131 EDITWORDBREAKPROCW word_break_proc;
132 INT line_count; /* number of lines */
133 INT y_offset; /* scroll offset in number of lines */
134 BOOL bCaptureState; /* flag indicating whether mouse was captured */
135 BOOL bEnableState; /* flag keeping the enable state */
136 HWND hwndSelf; /* the our window handle */
137 HWND hwndParent; /* Handle of parent for sending EN_* messages.
138 Even if parent will change, EN_* messages
139 should be sent to the first parent. */
140 HWND hwndListBox; /* handle of ComboBox's listbox or NULL */
141 INT wheelDeltaRemainder; /* scroll wheel delta left over after scrolling whole lines */
142 /*
143 * only for multi line controls
144 */
145 INT lock_count; /* amount of re-entries in the EditWndProc */
146 INT tabs_count;
147 LPINT tabs;
148 LINEDEF *first_line_def; /* linked list of (soft) linebreaks */
149 HLOCAL hloc32W; /* our unicode local memory block */
150 HLOCAL hlocapp; /* The text buffer handle belongs to the app */
151 /*
152 * IME Data
153 */
154#ifndef __REACTOS__ /* Rely on the composition window */
155 UINT composition_len; /* length of composition, 0 == no composition */
156 int composition_start; /* the character position for the composition */
157#endif
158 /*
159 * Uniscribe Data
160 */
162 SCRIPT_STRING_ANALYSIS ssa; /* Uniscribe Data for single line controls */
163} EDITSTATE;
164
166#define SWAP_UINT32(x,y) do { UINT temp = (UINT)(x); (x) = (UINT)(y); (y) = temp; } while(0)
167#define ORDER_UINT(x,y) do { if ((UINT)(y) < (UINT)(x)) SWAP_UINT32((x),(y)); } while(0)
169static inline BOOL notify_parent(const EDITSTATE *es, INT code)
170{
171 HWND hwnd = es->hwndSelf;
172 TRACE("notification %d sent to %p.\n", code, es->hwndParent);
173 SendMessageW(es->hwndParent, WM_COMMAND, MAKEWPARAM(GetWindowLongPtrW(es->hwndSelf, GWLP_ID), code), (LPARAM)es->hwndSelf);
174 return IsWindow(hwnd);
175}
176
177static LRESULT EDIT_EM_PosFromChar(EDITSTATE *es, INT index, BOOL after_wrap);
178
179/*********************************************************************
180 *
181 * EM_CANUNDO
182 *
183 */
184static inline BOOL EDIT_EM_CanUndo(const EDITSTATE *es)
185{
186 return (es->undo_insert_count || strlenW(es->undo_text));
187}
188
189
190/*********************************************************************
191 *
192 * EM_EMPTYUNDOBUFFER
193 *
194 */
195static inline void EDIT_EM_EmptyUndoBuffer(EDITSTATE *es)
196{
197 es->undo_insert_count = 0;
198 *es->undo_text = '\0';
199}
201static HBRUSH EDIT_NotifyCtlColor(EDITSTATE *es, HDC hdc)
202{
203 HBRUSH hbrush;
204 UINT msg;
205
206 if ((!es->bEnableState || (es->style & ES_READONLY)))
208 else
210
211 /* Why do we notify to es->hwndParent, and we send this one to GetParent()? */
212 hbrush = (HBRUSH)SendMessageW(GetParent(es->hwndSelf), msg, (WPARAM)hdc, (LPARAM)es->hwndSelf);
213 if (!hbrush)
214 hbrush = (HBRUSH)DefWindowProcW(GetParent(es->hwndSelf), msg, (WPARAM)hdc, (LPARAM)es->hwndSelf);
215 return hbrush;
216}
217
219static inline UINT get_text_length(EDITSTATE *es)
220{
221 if(es->text_length == (UINT)-1)
222 es->text_length = strlenW(es->text);
223 return es->text_length;
224}
225
226
227/*********************************************************************
228 *
229 * EDIT_WordBreakProc
230 *
231 * Find the beginning of words.
232 * Note: unlike the specs for a WordBreakProc, this function can
233 * only be called without linebreaks between s[0] up to
234 * s[count - 1]. Remember it is only called
235 * internally, so we can decide this for ourselves.
236 * Additionally we will always be breaking the full string.
237 *
238 */
240{
241 INT ret = 0;
242
243 TRACE("s=%p, index=%d, count=%d, action=%d\n", s, index, count, action);
244
245 if(!s) return 0;
246
247 if (!es->logAttr)
248 {
250
251 memset(&psa,0,sizeof(SCRIPT_ANALYSIS));
252 psa.eScript = SCRIPT_UNDEFINED;
253
254 es->logAttr = heap_alloc(sizeof(SCRIPT_LOGATTR) * get_text_length(es));
255 ScriptBreak(es->text, get_text_length(es), &psa, es->logAttr);
256 }
257
258 switch (action) {
259 case WB_LEFT:
260 if (index)
261 index--;
262 while (index && !es->logAttr[index].fSoftBreak)
263 index--;
264 ret = index;
265 break;
266 case WB_RIGHT:
267 if (!count)
268 break;
269 while (index < count && s[index] && !es->logAttr[index].fSoftBreak)
270 index++;
271 ret = index;
272 break;
273 case WB_ISDELIMITER:
274 ret = es->logAttr[index].fWhiteSpace;
275 break;
276 default:
277 ERR("unknown action code, please report !\n");
278 break;
279 }
280 return ret;
281}
282
283
284/*********************************************************************
285 *
286 * EDIT_CallWordBreakProc
287 *
288 * Call appropriate WordBreakProc (internal or external).
289 *
290 * Note: The "start" argument should always be an index referring
291 * to es->text. The actual wordbreak proc might be
292 * 16 bit, so we can't always pass any 32 bit LPSTR.
293 * Hence we assume that es->text is the buffer that holds
294 * the string under examination (we can decide this for ourselves).
295 *
296 */
298{
299 INT ret;
300
301 if (es->word_break_proc)
302 ret = es->word_break_proc(es->text + start, index, count, action);
303 else
305
306 return ret;
307}
309static inline void EDIT_InvalidateUniscribeData_linedef(LINEDEF *line_def)
310{
311 if (line_def->ssa)
312 {
313 ScriptStringFree(&line_def->ssa);
314 line_def->ssa = NULL;
315 }
316}
318static inline void EDIT_InvalidateUniscribeData(EDITSTATE *es)
319{
320 LINEDEF *line_def = es->first_line_def;
321 while (line_def)
322 {
324 line_def = line_def->next;
325 }
326 if (es->ssa)
327 {
328 ScriptStringFree(&es->ssa);
329 es->ssa = NULL;
330 }
331}
334{
335 if (!line_def)
336 return NULL;
337
338 if (line_def->net_length && !line_def->ssa)
339 {
340 int index = line_def->index;
341 HFONT old_font = NULL;
342 HDC udc = dc;
343 SCRIPT_TABDEF tabdef;
344 HRESULT hr;
345
346 if (!udc)
347 udc = GetDC(es->hwndSelf);
348 if (es->font)
349 old_font = SelectObject(udc, es->font);
350
351 tabdef.cTabStops = es->tabs_count;
352 tabdef.iScale = GdiGetCharDimensions(udc, NULL, NULL);
353 tabdef.pTabStops = es->tabs;
354 tabdef.iTabOrigin = 0;
355
356 hr = ScriptStringAnalyse(udc, &es->text[index], line_def->net_length,
357 (1.5*line_def->net_length+16), -1,
359 NULL, NULL, NULL, &tabdef, NULL, &line_def->ssa);
360 if (FAILED(hr))
361 {
362 WARN("ScriptStringAnalyse failed (%x)\n",hr);
363 line_def->ssa = NULL;
364 }
365
366 if (es->font)
367 SelectObject(udc, old_font);
368 if (udc != dc)
369 ReleaseDC(es->hwndSelf, udc);
370 }
371
372 return line_def->ssa;
373}
376{
377 LINEDEF *line_def;
378
379 if (!(es->style & ES_MULTILINE))
380 {
381 if (!es->ssa)
382 {
384 HFONT old_font = NULL;
385 HDC udc = dc;
386
387 if (!udc)
388 udc = GetDC(es->hwndSelf);
389 if (es->font)
390 old_font = SelectObject(udc, es->font);
391
392 if (es->style & ES_PASSWORD)
393 ScriptStringAnalyse(udc, &es->password_char, length, (1.5*length+16), -1, SSA_LINK|SSA_FALLBACK|SSA_GLYPHS|SSA_PASSWORD, -1, NULL, NULL, NULL, NULL, NULL, &es->ssa);
394 else
395 ScriptStringAnalyse(udc, es->text, length, (1.5*length+16), -1, SSA_LINK|SSA_FALLBACK|SSA_GLYPHS, -1, NULL, NULL, NULL, NULL, NULL, &es->ssa);
396
397 if (es->font)
398 SelectObject(udc, old_font);
399 if (udc != dc)
400 ReleaseDC(es->hwndSelf, udc);
401 }
402 return es->ssa;
403 }
404 else
405 {
406 line_def = es->first_line_def;
407 while (line_def && line)
408 {
409 line_def = line_def->next;
410 line--;
411 }
412
413 return EDIT_UpdateUniscribeData_linedef(es,dc,line_def);
414 }
415}
418{
419 INT vlc = (es->format_rect.bottom - es->format_rect.top) / es->line_height;
420 return max(1,vlc);
421}
422
423/*********************************************************************
424 *
425 * EDIT_BuildLineDefs_ML
426 *
427 * Build linked list of text lines.
428 * Lines can end with '\0' (last line), a character (if it is wrapped),
429 * a soft return '\r\r\n' or a hard return '\r\n'
430 *
431 */
432static void EDIT_BuildLineDefs_ML(EDITSTATE *es, INT istart, INT iend, INT delta, HRGN hrgn)
433{
434 LPWSTR current_position, cp;
435 INT fw;
436 LINEDEF *current_line;
437 LINEDEF *previous_line;
438 LINEDEF *start_line;
439 INT line_index = 0, nstart_line, nstart_index;
440 INT line_count = es->line_count;
441 INT orig_net_length;
442 RECT rc;
443 INT vlc;
444
445 if (istart == iend && delta == 0)
446 return;
447
448 previous_line = NULL;
449 current_line = es->first_line_def;
450
451 /* Find starting line. istart must lie inside an existing line or
452 * at the end of buffer */
453 do {
454 if (istart < current_line->index + current_line->length ||
455 current_line->ending == END_0)
456 break;
457
458 previous_line = current_line;
459 current_line = current_line->next;
460 line_index++;
461 } while (current_line);
462
463 if (!current_line) /* Error occurred start is not inside previous buffer */
464 {
465 FIXME(" modification occurred outside buffer\n");
466 return;
467 }
468
469 /* Remember start of modifications in order to calculate update region */
470 nstart_line = line_index;
471 nstart_index = current_line->index;
472
473 /* We must start to reformat from the previous line since the modifications
474 * may have caused the line to wrap upwards. */
475 if (!(es->style & ES_AUTOHSCROLL) && line_index > 0)
476 {
477 line_index--;
478 current_line = previous_line;
479 }
480 start_line = current_line;
481
482 fw = es->format_rect.right - es->format_rect.left;
483 current_position = es->text + current_line->index;
485 do {
486 if (current_line != start_line)
487 {
488 if (!current_line || current_line->index + delta > current_position - es->text)
489 {
490 /* The buffer has been expanded, create a new line and
491 insert it into the link list */
492 LINEDEF *new_line = heap_alloc_zero(sizeof(*new_line));
493 new_line->next = previous_line->next;
494 previous_line->next = new_line;
495 current_line = new_line;
496 es->line_count++;
497 }
498 else if (current_line->index + delta < current_position - es->text)
499 {
500 /* The previous line merged with this line so we delete this extra entry */
501 previous_line->next = current_line->next;
502 heap_free(current_line);
503 current_line = previous_line->next;
504 es->line_count--;
505 continue;
506 }
507 else /* current_line->index + delta == current_position */
508 {
509 if (current_position - es->text > iend)
510 break; /* We reached end of line modifications */
511 /* else recalculate this line */
512 }
513 }
514
515 current_line->index = current_position - es->text;
516 orig_net_length = current_line->net_length;
517
518 /* Find end of line */
519 cp = current_position;
520 while (*cp) {
521 if (*cp == '\n') break;
522 if ((*cp == '\r') && (*(cp + 1) == '\n'))
523 break;
524 cp++;
525 }
526
527 /* Mark type of line termination */
528 if (!(*cp)) {
529 current_line->ending = END_0;
530 current_line->net_length = strlenW(current_position);
531 } else if ((cp > current_position) && (*(cp - 1) == '\r')) {
532 current_line->ending = END_SOFT;
533 current_line->net_length = cp - current_position - 1;
534 } else if (*cp == '\n') {
535 current_line->ending = END_RICH;
536 current_line->net_length = cp - current_position;
537 } else {
538 current_line->ending = END_HARD;
539 current_line->net_length = cp - current_position;
540 }
541
542 if (current_line->net_length)
543 {
544 const SIZE *sz;
547 if (current_line->ssa)
548 {
549 sz = ScriptString_pSize(current_line->ssa);
550 /* Calculate line width */
551 current_line->width = sz->cx;
552 }
553 else current_line->width = es->char_width * current_line->net_length;
554 }
555 else current_line->width = 0;
556
557 /* FIXME: check here for lines that are too wide even in AUTOHSCROLL (> 32767 ???) */
558
559/* Line breaks just look back from the end and find the next break and try that. */
560
561 if (!(es->style & ES_AUTOHSCROLL)) {
562 if (current_line->width > fw && fw > es->char_width) {
563
564 INT prev, next;
565 int w;
566 const SIZE *sz;
567 float d;
568
569 prev = current_line->net_length - 1;
570 w = current_line->net_length;
571 d = (float)current_line->width/(float)fw;
572 if (d > 1.2f) d -= 0.2f;
573 next = prev/d;
574 if (next >= prev) next = prev-1;
575 do {
576 prev = EDIT_CallWordBreakProc(es, current_position - es->text,
577 next, current_line->net_length, WB_LEFT);
578 current_line->net_length = prev;
581 if (current_line->ssa)
582 sz = ScriptString_pSize(current_line->ssa);
583 else sz = 0;
584 if (sz)
585 current_line->width = sz->cx;
586 else
587 prev = 0;
588 next = prev - 1;
589 } while (prev && current_line->width > fw);
590 current_line->net_length = w;
591
592 if (prev == 0) { /* Didn't find a line break so force a break */
593 INT *piDx;
594 const INT *count;
595
598
599 if (current_line->ssa)
600 {
601 count = ScriptString_pcOutChars(current_line->ssa);
602 piDx = heap_alloc(sizeof(INT) * (*count));
603 ScriptStringGetLogicalWidths(current_line->ssa,piDx);
604
605 prev = current_line->net_length-1;
606 do {
607 current_line->width -= piDx[prev];
608 prev--;
609 } while ( prev > 0 && current_line->width > fw);
610 if (prev<=0)
611 prev = 1;
612 heap_free(piDx);
613 }
614 else
615 prev = (fw / es->char_width);
616 }
617
618 /* If the first line we are calculating, wrapped before istart, we must
619 * adjust istart in order for this to be reflected in the update region. */
620 if (current_line->index == nstart_index && istart > current_line->index + prev)
621 istart = current_line->index + prev;
622 /* else if we are updating the previous line before the first line we
623 * are re-calculating and it expanded */
624 else if (current_line == start_line &&
625 current_line->index != nstart_index && orig_net_length < prev)
626 {
627 /* Line expanded due to an upwards line wrap so we must partially include
628 * previous line in update region */
629 nstart_line = line_index;
630 nstart_index = current_line->index;
631 istart = current_line->index + orig_net_length;
632 }
633
634 current_line->net_length = prev;
635 current_line->ending = END_WRAP;
636
637 if (current_line->net_length > 0)
638 {
640 if (current_line->ssa)
641 {
642 sz = ScriptString_pSize(current_line->ssa);
643 current_line->width = sz->cx;
644 }
645 else
646 current_line->width = 0;
647 }
648 else current_line->width = 0;
649 }
650 else if (current_line == start_line &&
651 current_line->index != nstart_index &&
652 orig_net_length < current_line->net_length) {
653 /* The previous line expanded but it's still not as wide as the client rect */
654 /* The expansion is due to an upwards line wrap so we must partially include
655 it in the update region */
656 nstart_line = line_index;
657 nstart_index = current_line->index;
658 istart = current_line->index + orig_net_length;
659 }
660 }
661
662
663 /* Adjust length to include line termination */
664 switch (current_line->ending) {
665 case END_SOFT:
666 current_line->length = current_line->net_length + 3;
667 break;
668 case END_RICH:
669 current_line->length = current_line->net_length + 1;
670 break;
671 case END_HARD:
672 current_line->length = current_line->net_length + 2;
673 break;
674 case END_WRAP:
675 case END_0:
676 current_line->length = current_line->net_length;
677 break;
678 }
679 es->text_width = max(es->text_width, current_line->width);
680 current_position += current_line->length;
681 previous_line = current_line;
682
683 /* Discard data for non-visible lines. It will be calculated as needed */
684 if ((line_index < es->y_offset) || (line_index > es->y_offset + vlc))
686
687 current_line = current_line->next;
688 line_index++;
689 } while (previous_line->ending != END_0);
690
691 /* Finish adjusting line indexes by delta or remove hanging lines */
692 if (previous_line->ending == END_0)
693 {
694 LINEDEF *pnext = NULL;
695
696 previous_line->next = NULL;
697 while (current_line)
698 {
699 pnext = current_line->next;
701 heap_free(current_line);
702 current_line = pnext;
703 es->line_count--;
704 }
705 }
706 else if (delta != 0)
707 {
708 while (current_line)
709 {
710 current_line->index += delta;
711 current_line = current_line->next;
712 }
713 }
714
715 /* Calculate rest of modification rectangle */
716 if (hrgn)
717 {
718 HRGN tmphrgn;
719 /*
720 * We calculate two rectangles. One for the first line which may have
721 * an indent with respect to the format rect. The other is a format-width
722 * rectangle that spans the rest of the lines that changed or moved.
723 */
724 rc.top = es->format_rect.top + nstart_line * es->line_height -
725 (es->y_offset * es->line_height); /* Adjust for vertical scrollbar */
726 rc.bottom = rc.top + es->line_height;
727 if ((es->style & ES_CENTER) || (es->style & ES_RIGHT))
728 rc.left = es->format_rect.left;
729 else
730#ifdef __REACTOS__ /* CORE-11475 */
731 rc.left = (short)LOWORD(EDIT_EM_PosFromChar(es, nstart_index, FALSE));
732#else
733 rc.left = LOWORD(EDIT_EM_PosFromChar(es, nstart_index, FALSE));
734#endif
735 rc.right = es->format_rect.right;
736 SetRectRgn(hrgn, rc.left, rc.top, rc.right, rc.bottom);
737
738 rc.top = rc.bottom;
739 rc.left = es->format_rect.left;
740 rc.right = es->format_rect.right;
741 /*
742 * If lines were added or removed we must re-paint the remainder of the
743 * lines since the remaining lines were either shifted up or down.
744 */
745 if (line_count < es->line_count) /* We added lines */
746 rc.bottom = es->line_count * es->line_height;
747 else if (line_count > es->line_count) /* We removed lines */
748 rc.bottom = line_count * es->line_height;
749 else
750 rc.bottom = line_index * es->line_height;
751 rc.bottom += es->format_rect.top;
752 rc.bottom -= (es->y_offset * es->line_height); /* Adjust for vertical scrollbar */
753 tmphrgn = CreateRectRgn(rc.left, rc.top, rc.right, rc.bottom);
754 CombineRgn(hrgn, hrgn, tmphrgn, RGN_OR);
755 DeleteObject(tmphrgn);
756 }
757}
758
759/*********************************************************************
760 *
761 * EDIT_CalcLineWidth_SL
762 *
763 */
765{
767 if (es->ssa)
768 {
769 const SIZE *size;
770 size = ScriptString_pSize(es->ssa);
771 es->text_width = size->cx;
772 }
773 else
774 es->text_width = 0;
775}
776
777/*********************************************************************
778 *
779 * EDIT_CharFromPos
780 *
781 * Beware: This is not the function called on EM_CHARFROMPOS
782 * The position _can_ be outside the formatting / client
783 * rectangle
784 * The return value is only the character index
785 *
786 */
787static INT EDIT_CharFromPos(EDITSTATE *es, INT x, INT y, LPBOOL after_wrap)
788{
789 INT index;
790
791 if (es->style & ES_MULTILINE) {
792 int trailing;
793 INT line = (y - es->format_rect.top) / es->line_height + es->y_offset;
794 INT line_index = 0;
795 LINEDEF *line_def = es->first_line_def;
797 while ((line > 0) && line_def->next) {
798 line_index += line_def->length;
799 line_def = line_def->next;
800 line--;
801 }
802
803 x += es->x_offset - es->format_rect.left;
804 if (es->style & ES_RIGHT)
805 x -= (es->format_rect.right - es->format_rect.left) - line_def->width;
806 else if (es->style & ES_CENTER)
807 x -= ((es->format_rect.right - es->format_rect.left) - line_def->width) / 2;
808 if (x >= line_def->width) {
809 if (after_wrap)
810 *after_wrap = (line_def->ending == END_WRAP);
811 return line_index + line_def->net_length;
812 }
813 if (x <= 0 || !line_def->ssa) {
814 if (after_wrap)
815 *after_wrap = FALSE;
816 return line_index;
817 }
818
819 ScriptStringXtoCP(line_def->ssa, x , &index, &trailing);
820 if (trailing) index++;
821 index += line_index;
822 if (after_wrap)
823 *after_wrap = ((index == line_index + line_def->net_length) &&
824 (line_def->ending == END_WRAP));
825 } else {
826 INT xoff = 0;
827 INT trailing;
828 if (after_wrap)
829 *after_wrap = FALSE;
830 x -= es->format_rect.left;
831 if (!x)
832 return es->x_offset;
833
834 if (!es->x_offset)
835 {
836 INT indent = (es->format_rect.right - es->format_rect.left) - es->text_width;
837 if (es->style & ES_RIGHT)
838 x -= indent;
839 else if (es->style & ES_CENTER)
840 x -= indent / 2;
841 }
842
844 if (es->x_offset)
845 {
846 if (es->ssa)
847 {
848 if (es->x_offset>= get_text_length(es))
849 {
850 const SIZE *size;
851 size = ScriptString_pSize(es->ssa);
852 xoff = size->cx;
853 }
854 ScriptStringCPtoX(es->ssa, es->x_offset, FALSE, &xoff);
855 }
856 else
857 xoff = 0;
858 }
859 if (x < 0)
860 {
861 if (x + xoff > 0 || !es->ssa)
862 {
863 ScriptStringXtoCP(es->ssa, x+xoff, &index, &trailing);
864 if (trailing) index++;
865 }
866 else
867 index = 0;
868 }
869 else
870 {
871 if (x)
872 {
873 const SIZE *size = NULL;
874 if (es->ssa)
875 size = ScriptString_pSize(es->ssa);
876 if (!size)
877 index = 0;
878 else if (x > size->cx)
880 else if (es->ssa)
881 {
882 ScriptStringXtoCP(es->ssa, x+xoff, &index, &trailing);
883 if (trailing) index++;
884 }
885 else
886 index = 0;
887 }
888 else
889 index = es->x_offset;
890 }
891 }
892 return index;
893}
894
895
896/*********************************************************************
897 *
898 * EDIT_ConfinePoint
899 *
900 * adjusts the point to be within the formatting rectangle
901 * (so CharFromPos returns the nearest _visible_ character)
902 *
903 */
904static void EDIT_ConfinePoint(const EDITSTATE *es, LPINT x, LPINT y)
905{
906 *x = min(max(*x, es->format_rect.left), es->format_rect.right - 1);
907 *y = min(max(*y, es->format_rect.top), es->format_rect.bottom - 1);
908}
909
910
911/*********************************************************************
912 *
913 * EM_LINEFROMCHAR
914 *
915 */
917{
918 INT line;
919 LINEDEF *line_def;
920
921 if (!(es->style & ES_MULTILINE))
922 return 0;
923 if (index > (INT)get_text_length(es))
924 return es->line_count - 1;
925 if (index == -1)
926 index = min(es->selection_start, es->selection_end);
927
928 line = 0;
929 line_def = es->first_line_def;
930 index -= line_def->length;
931 while ((index >= 0) && line_def->next) {
932 line++;
933 line_def = line_def->next;
934 index -= line_def->length;
935 }
936 return line;
937}
938
939
940/*********************************************************************
941 *
942 * EM_LINEINDEX
943 *
944 */
946{
947 INT line_index;
948 const LINEDEF *line_def;
949
950 if (!(es->style & ES_MULTILINE))
951 return 0;
952 if (line >= es->line_count)
953 return -1;
954
955 line_index = 0;
956 line_def = es->first_line_def;
957 if (line == -1) {
958 INT index = es->selection_end - line_def->length;
959 while ((index >= 0) && line_def->next) {
960 line_index += line_def->length;
961 line_def = line_def->next;
962 index -= line_def->length;
963 }
964 } else {
965 while (line > 0) {
966 line_index += line_def->length;
967 line_def = line_def->next;
968 line--;
969 }
970 }
971 return line_index;
972}
973
974
975/*********************************************************************
976 *
977 * EM_LINELENGTH
978 *
979 */
981{
982 LINEDEF *line_def;
983
984 if (!(es->style & ES_MULTILINE))
985 return get_text_length(es);
986
987 if (index == -1) {
988 /* get the number of remaining non-selected chars of selected lines */
989 INT32 l; /* line number */
990 INT32 li; /* index of first char in line */
991 INT32 count;
992 l = EDIT_EM_LineFromChar(es, es->selection_start);
993 /* # chars before start of selection area */
994 count = es->selection_start - EDIT_EM_LineIndex(es, l);
995 l = EDIT_EM_LineFromChar(es, es->selection_end);
996 /* # chars after end of selection */
998 count += li + EDIT_EM_LineLength(es, li) - es->selection_end;
999 return count;
1000 }
1001 line_def = es->first_line_def;
1002 index -= line_def->length;
1003 while ((index >= 0) && line_def->next) {
1004 line_def = line_def->next;
1005 index -= line_def->length;
1006 }
1007 return line_def->net_length;
1008}
1009
1010
1011/*********************************************************************
1012 *
1013 * EM_POSFROMCHAR
1014 *
1016static LRESULT EDIT_EM_PosFromChar(EDITSTATE *es, INT index, BOOL after_wrap)
1017{
1019 INT l;
1020 INT li;
1021 INT x = 0;
1022 INT y = 0;
1023 INT w;
1024 INT lw;
1025 LINEDEF *line_def;
1026
1027 index = min(index, len);
1028 if (es->style & ES_MULTILINE) {
1031
1032 y = (l - es->y_offset) * es->line_height;
1034 if (after_wrap && (li == index) && l) {
1035 INT l2 = l - 1;
1036 line_def = es->first_line_def;
1037 while (l2) {
1038 line_def = line_def->next;
1039 l2--;
1040 }
1041 if (line_def->ending == END_WRAP) {
1042 l--;
1043 y -= es->line_height;
1045 }
1046 }
1047
1048 line_def = es->first_line_def;
1049 while (line_def->index != li)
1050 line_def = line_def->next;
1051
1052 lw = line_def->width;
1053 w = es->format_rect.right - es->format_rect.left;
1054 if (line_def->ssa)
1055 {
1056 ScriptStringCPtoX(line_def->ssa, (index - 1) - li, TRUE, &x);
1057 x -= es->x_offset;
1058 }
1059 else
1060#ifdef __REACTOS__ /* CORE-15780 */
1061 x = (lw > 0 ? es->x_offset : x - es->x_offset);
1062#else
1063 x = es->x_offset;
1064#endif
1065
1066 if (es->style & ES_RIGHT)
1067 x = w - (lw - x);
1068 else if (es->style & ES_CENTER)
1069 x += (w - lw) / 2;
1070 } else {
1071 INT xoff = 0;
1072 INT xi = 0;
1074 if (es->x_offset)
1075 {
1076 if (es->ssa)
1077 {
1078 if (es->x_offset >= get_text_length(es))
1079 {
1080 int leftover = es->x_offset - get_text_length(es);
1081 if (es->ssa)
1082 {
1083 const SIZE *size;
1084 size = ScriptString_pSize(es->ssa);
1085 xoff = size->cx;
1086 }
1087 else
1088 xoff = 0;
1089 xoff += es->char_width * leftover;
1090 }
1091 else
1092 ScriptStringCPtoX(es->ssa, es->x_offset, FALSE, &xoff);
1093 }
1094 else
1095 xoff = 0;
1096 }
1097 if (index)
1098 {
1099 if (index >= get_text_length(es))
1100 {
1101 if (es->ssa)
1102 {
1103 const SIZE *size;
1104 size = ScriptString_pSize(es->ssa);
1105 xi = size->cx;
1106 }
1107 else
1108 xi = 0;
1109 }
1110 else if (es->ssa)
1111 ScriptStringCPtoX(es->ssa, index, FALSE, &xi);
1112 else
1113 xi = 0;
1114 }
1115 x = xi - xoff;
1116
1117 if (index >= es->x_offset) {
1118 if (!es->x_offset && (es->style & (ES_RIGHT | ES_CENTER)))
1119 {
1120 w = es->format_rect.right - es->format_rect.left;
1121 if (w > es->text_width)
1122 {
1123 if (es->style & ES_RIGHT)
1124 x += w - es->text_width;
1125 else if (es->style & ES_CENTER)
1126 x += (w - es->text_width) / 2;
1127 }
1128 }
1129 }
1130 y = 0;
1131 }
1132 x += es->format_rect.left;
1133 y += es->format_rect.top;
1134 return MAKELONG((INT16)x, (INT16)y);
1135}
1136
1137
1138/*********************************************************************
1139 *
1140 * EDIT_GetLineRect
1141 *
1142 * Calculates the bounding rectangle for a line from a starting
1143 * column to an ending column.
1144 *
1146static void EDIT_GetLineRect(EDITSTATE *es, INT line, INT scol, INT ecol, LPRECT rc)
1147{
1149 INT line_index = 0;
1150 INT pt1, pt2, pt3;
1151
1152 if (es->style & ES_MULTILINE)
1153 {
1154 const LINEDEF *line_def = NULL;
1155 rc->top = es->format_rect.top + (line - es->y_offset) * es->line_height;
1156 if (line >= es->line_count)
1157 return;
1158
1159 line_def = es->first_line_def;
1160 if (line == -1) {
1161 INT index = es->selection_end - line_def->length;
1162 while ((index >= 0) && line_def->next) {
1163 line_index += line_def->length;
1164 line_def = line_def->next;
1165 index -= line_def->length;
1166 }
1167 } else {
1168 while (line > 0) {
1169 line_index += line_def->length;
1170 line_def = line_def->next;
1171 line--;
1172 }
1173 }
1174 ssa = line_def->ssa;
1175 }
1176 else
1177 {
1178 line_index = 0;
1179 rc->top = es->format_rect.top;
1180 ssa = es->ssa;
1181 }
1182
1183 rc->bottom = rc->top + es->line_height;
1184 pt1 = (scol == 0) ? es->format_rect.left : (short)LOWORD(EDIT_EM_PosFromChar(es, line_index + scol, TRUE));
1185 pt2 = (ecol == -1) ? es->format_rect.right : (short)LOWORD(EDIT_EM_PosFromChar(es, line_index + ecol, TRUE));
1186 if (ssa)
1187 {
1188 ScriptStringCPtoX(ssa, scol, FALSE, &pt3);
1189 pt3+=es->format_rect.left;
1190 }
1191 else pt3 = pt1;
1192 rc->right = max(max(pt1 , pt2),pt3);
1193 rc->left = min(min(pt1, pt2),pt3);
1194}
1195
1197static inline void text_buffer_changed(EDITSTATE *es)
1198{
1199 es->text_length = (UINT)-1;
1200
1201 heap_free( es->logAttr );
1202 es->logAttr = NULL;
1204}
1205
1206/*********************************************************************
1207 * EDIT_LockBuffer
1208 *
1210static void EDIT_LockBuffer(EDITSTATE *es)
1211{
1212 if (!es->text)
1213 {
1214 if (!es->hloc32W)
1215 return;
1216
1217 es->text = LocalLock(es->hloc32W);
1218 }
1219
1220 es->lock_count++;
1221}
1222
1223
1224/*********************************************************************
1225 *
1226 * EDIT_UnlockBuffer
1227 *
1229static void EDIT_UnlockBuffer(EDITSTATE *es, BOOL force)
1230{
1231 /* Edit window might be already destroyed */
1232 if (!IsWindow(es->hwndSelf))
1233 {
1234 WARN("edit hwnd %p already destroyed\n", es->hwndSelf);
1235 return;
1236 }
1237
1238 if (!es->lock_count)
1239 {
1240 ERR("lock_count == 0 ... please report\n");
1241 return;
1242 }
1243
1244 if (!es->text)
1245 {
1246 ERR("es->text == 0 ... please report\n");
1247 return;
1248 }
1249
1250 if (force || (es->lock_count == 1))
1251 {
1252 if (es->hloc32W)
1253 {
1254 LocalUnlock(es->hloc32W);
1255 es->text = NULL;
1256 }
1257 else
1258 {
1259 ERR("no buffer ... please report\n");
1260 return;
1261 }
1262
1263 }
1264
1265 es->lock_count--;
1266}
1267
1268
1269/*********************************************************************
1270 *
1271 * EDIT_MakeFit
1272 *
1273 * Try to fit size + 1 characters in the buffer.
1276{
1277 HLOCAL hNew32W;
1278
1279 if (size <= es->buffer_size)
1280 return TRUE;
1281
1282 TRACE("trying to ReAlloc to %d+1 characters\n", size);
1283
1284 /* Force edit to unlock its buffer. es->text now NULL */
1286
1287 if (es->hloc32W) {
1288 UINT alloc_size = ROUND_TO_GROW((size + 1) * sizeof(WCHAR));
1289 if ((hNew32W = LocalReAlloc(es->hloc32W, alloc_size, LMEM_MOVEABLE | LMEM_ZEROINIT))) {
1290 TRACE("Old 32 bit handle %p, new handle %p\n", es->hloc32W, hNew32W);
1291 es->hloc32W = hNew32W;
1292 es->buffer_size = LocalSize(hNew32W)/sizeof(WCHAR) - 1;
1293 }
1294 }
1295
1297
1298 if (es->buffer_size < size) {
1299 WARN("FAILED ! We now have %d+1\n", es->buffer_size);
1301 return FALSE;
1302 } else {
1303 TRACE("We now have %d+1\n", es->buffer_size);
1304 return TRUE;
1305 }
1306}
1307
1308
1309/*********************************************************************
1310 *
1311 * EDIT_MakeUndoFit
1312 *
1313 * Try to fit size + 1 bytes in the undo buffer.
1314 *
1317{
1318 UINT alloc_size;
1319
1320 if (size <= es->undo_buffer_size)
1321 return TRUE;
1322
1323 TRACE("trying to ReAlloc to %d+1\n", size);
1324
1325 alloc_size = ROUND_TO_GROW((size + 1) * sizeof(WCHAR));
1326 if ((es->undo_text = HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, es->undo_text, alloc_size))) {
1327 es->undo_buffer_size = alloc_size/sizeof(WCHAR) - 1;
1328 return TRUE;
1329 }
1330 else
1331 {
1332 WARN("FAILED ! We now have %d+1\n", es->undo_buffer_size);
1333 return FALSE;
1334 }
1335}
1336
1337
1338/*********************************************************************
1339 *
1340 * EDIT_UpdateTextRegion
1341 *
1343static void EDIT_UpdateTextRegion(EDITSTATE *es, HRGN hrgn, BOOL bErase)
1344{
1345 if (es->flags & EF_UPDATE) {
1346 es->flags &= ~EF_UPDATE;
1347 if (!notify_parent(es, EN_UPDATE)) return;
1348 }
1349 InvalidateRgn(es->hwndSelf, hrgn, bErase);
1350}
1351
1352
1353/*********************************************************************
1354 *
1355 * EDIT_UpdateText
1356 *
1358static void EDIT_UpdateText(EDITSTATE *es, const RECT *rc, BOOL bErase)
1359{
1360 if (es->flags & EF_UPDATE) {
1361 es->flags &= ~EF_UPDATE;
1362 if (!notify_parent(es, EN_UPDATE)) return;
1363 }
1364 InvalidateRect(es->hwndSelf, rc, bErase);
1365}
1366
1367/*********************************************************************
1368 *
1369 * EDIT_SL_InvalidateText
1370 *
1371 * Called from EDIT_InvalidateText().
1372 * Does the job for single-line controls only.
1373 *
1376{
1377 RECT line_rect;
1378 RECT rc;
1379
1380 EDIT_GetLineRect(es, 0, start, end, &line_rect);
1381 if (IntersectRect(&rc, &line_rect, &es->format_rect))
1382 EDIT_UpdateText(es, &rc, TRUE);
1383}
1384
1385/*********************************************************************
1386 *
1387 * EDIT_ML_InvalidateText
1388 *
1389 * Called from EDIT_InvalidateText().
1390 * Does the job for multi-line controls only.
1391 *
1394{
1398 INT sc;
1399 INT ec;
1400 RECT rc1;
1401 RECT rcWnd;
1402 RECT rcLine;
1403 RECT rcUpdate;
1404 INT l;
1405
1406 if ((el < es->y_offset) || (sl > es->y_offset + vlc))
1407 return;
1408
1409 sc = start - EDIT_EM_LineIndex(es, sl);
1410 ec = end - EDIT_EM_LineIndex(es, el);
1411 if (sl < es->y_offset) {
1412 sl = es->y_offset;
1413 sc = 0;
1414 }
1415 if (el > es->y_offset + vlc) {
1416 el = es->y_offset + vlc;
1418 }
1419 GetClientRect(es->hwndSelf, &rc1);
1420 IntersectRect(&rcWnd, &rc1, &es->format_rect);
1421 if (sl == el) {
1422 EDIT_GetLineRect(es, sl, sc, ec, &rcLine);
1423 if (IntersectRect(&rcUpdate, &rcWnd, &rcLine))
1424 EDIT_UpdateText(es, &rcUpdate, TRUE);
1425 } else {
1426 EDIT_GetLineRect(es, sl, sc,
1428 EDIT_EM_LineIndex(es, sl)),
1429 &rcLine);
1430 if (IntersectRect(&rcUpdate, &rcWnd, &rcLine))
1431 EDIT_UpdateText(es, &rcUpdate, TRUE);
1432 for (l = sl + 1 ; l < el ; l++) {
1433 EDIT_GetLineRect(es, l, 0,
1436 &rcLine);
1437 if (IntersectRect(&rcUpdate, &rcWnd, &rcLine))
1438 EDIT_UpdateText(es, &rcUpdate, TRUE);
1439 }
1440 EDIT_GetLineRect(es, el, 0, ec, &rcLine);
1441 if (IntersectRect(&rcUpdate, &rcWnd, &rcLine))
1442 EDIT_UpdateText(es, &rcUpdate, TRUE);
1443 }
1444}
1445
1446
1447/*********************************************************************
1448 *
1449 * EDIT_InvalidateText
1450 *
1451 * Invalidate the text from offset start up to, but not including,
1452 * offset end. Useful for (re)painting the selection.
1453 * Regions outside the linewidth are not invalidated.
1454 * end == -1 means end == TextLength.
1455 * start and end need not be ordered.
1456 *
1459{
1460 if (end == start)
1461 return;
1462
1463 if (end == -1)
1465
1466 if (end < start) {
1467 INT tmp = start;
1468 start = end;
1469 end = tmp;
1470 }
1471
1472 if (es->style & ES_MULTILINE)
1474 else
1476}
1477
1478
1479/*********************************************************************
1480 *
1481 * EDIT_EM_SetSel
1482 *
1483 * note: unlike the specs say: the order of start and end
1484 * _is_ preserved in Windows. (i.e. start can be > end)
1485 * In other words: this handler is OK
1486 *
1488static BOOL EDIT_EM_SetSel(EDITSTATE *es, UINT start, UINT end, BOOL after_wrap)
1489{
1490 UINT old_start = es->selection_start;
1491 UINT old_end = es->selection_end;
1493
1494 if (start == old_start && end == old_end)
1495 return FALSE;
1496
1497 if (start == (UINT)-1) {
1498 start = es->selection_end;
1499 end = es->selection_end;
1500 } else {
1501 start = min(start, len);
1502 end = min(end, len);
1503 }
1504 es->selection_start = start;
1505 es->selection_end = end;
1506 if (after_wrap)
1507 es->flags |= EF_AFTER_WRAP;
1508 else
1509 es->flags &= ~EF_AFTER_WRAP;
1510 /* Compute the necessary invalidation region. */
1511 /* Note that we don't need to invalidate regions which have
1512 * "never" been selected, or those which are "still" selected.
1513 * In fact, every time we hit a selection boundary, we can
1514 * *toggle* whether we need to invalidate. Thus we can optimize by
1515 * *sorting* the interval endpoints. Let's assume that we sort them
1516 * in this order:
1517 * start <= end <= old_start <= old_end
1518 * Knuth 5.3.1 (p 183) assures us that this can be done optimally
1519 * in 5 comparisons; i.e. it is impossible to do better than the
1520 * following: */
1521 ORDER_UINT(end, old_end);
1522 ORDER_UINT(start, old_start);
1523 ORDER_UINT(old_start, old_end);
1525 /* Note that at this point 'end' and 'old_start' are not in order, but
1526 * start is definitely the min. and old_end is definitely the max. */
1527 if (end != old_start)
1528 {
1529/*
1530 * One can also do
1531 * ORDER_UINT32(end, old_start);
1532 * EDIT_InvalidateText(es, start, end);
1533 * EDIT_InvalidateText(es, old_start, old_end);
1534 * in place of the following if statement.
1535 * (That would complete the optimal five-comparison four-element sort.)
1536 */
1537 if (old_start > end )
1538 {
1540 EDIT_InvalidateText(es, old_start, old_end);
1541 }
1542 else
1543 {
1544 EDIT_InvalidateText(es, start, old_start);
1545 EDIT_InvalidateText(es, end, old_end);
1546 }
1547 }
1548 else EDIT_InvalidateText(es, start, old_end);
1549
1550 return TRUE;
1551}
1552
1553
1554/*********************************************************************
1555 *
1556 * EDIT_UpdateScrollInfo
1557 *
1560{
1561 if ((es->style & WS_VSCROLL) && !(es->flags & EF_VSCROLL_TRACK))
1562 {
1563 SCROLLINFO si;
1564 si.cbSize = sizeof(SCROLLINFO);
1566 si.nMin = 0;
1567 si.nMax = es->line_count - 1;
1568 si.nPage = (es->format_rect.bottom - es->format_rect.top) / es->line_height;
1569 si.nPos = es->y_offset;
1570 TRACE("SB_VERT, nMin=%d, nMax=%d, nPage=%d, nPos=%d\n",
1571 si.nMin, si.nMax, si.nPage, si.nPos);
1572 SetScrollInfo(es->hwndSelf, SB_VERT, &si, TRUE);
1573 }
1574
1575 if ((es->style & WS_HSCROLL) && !(es->flags & EF_HSCROLL_TRACK))
1576 {
1577 SCROLLINFO si;
1578 si.cbSize = sizeof(SCROLLINFO);
1580 si.nMin = 0;
1581 si.nMax = es->text_width - 1;
1582 si.nPage = es->format_rect.right - es->format_rect.left;
1583 si.nPos = es->x_offset;
1584 TRACE("SB_HORZ, nMin=%d, nMax=%d, nPage=%d, nPos=%d\n",
1585 si.nMin, si.nMax, si.nPage, si.nPos);
1586 SetScrollInfo(es->hwndSelf, SB_HORZ, &si, TRUE);
1587 }
1588}
1589
1590
1591/*********************************************************************
1592 *
1593 * EDIT_EM_LineScroll_internal
1594 *
1595 * Version of EDIT_EM_LineScroll for internal use.
1596 * It doesn't refuse if ES_MULTILINE is set and assumes that
1597 * dx is in pixels, dy - in lines.
1598 *
1601{
1602 INT nyoff;
1603 INT x_offset_in_pixels;
1604 INT lines_per_page = (es->format_rect.bottom - es->format_rect.top) /
1605 es->line_height;
1606
1607 if (es->style & ES_MULTILINE)
1608 {
1609 x_offset_in_pixels = es->x_offset;
1610 }
1611 else
1612 {
1613 dy = 0;
1614 x_offset_in_pixels = (short)LOWORD(EDIT_EM_PosFromChar(es, es->x_offset, FALSE));
1615 }
1616
1617 if (-dx > x_offset_in_pixels)
1618 dx = -x_offset_in_pixels;
1619 if (dx > es->text_width - x_offset_in_pixels)
1620 dx = es->text_width - x_offset_in_pixels;
1621 nyoff = max(0, es->y_offset + dy);
1622 if (nyoff >= es->line_count - lines_per_page)
1623 nyoff = max(0, es->line_count - lines_per_page);
1624 dy = (es->y_offset - nyoff) * es->line_height;
1625 if (dx || dy) {
1626 RECT rc1;
1627 RECT rc;
1628
1629 es->y_offset = nyoff;
1630 if(es->style & ES_MULTILINE)
1631 es->x_offset += dx;
1632 else
1633 es->x_offset += dx / es->char_width;
1634
1635 GetClientRect(es->hwndSelf, &rc1);
1636 IntersectRect(&rc, &rc1, &es->format_rect);
1637 ScrollWindowEx(es->hwndSelf, -dx, dy,
1638 NULL, &rc, NULL, NULL, SW_INVALIDATE);
1639 /* force scroll info update */
1641 }
1642 if (dx && !(es->flags & EF_HSCROLL_TRACK))
1644 if (dy && !(es->flags & EF_VSCROLL_TRACK))
1646 return TRUE;
1647}
1648
1649/*********************************************************************
1650 *
1651 * EM_LINESCROLL
1652 *
1653 * NOTE: dx is in average character widths, dy - in lines;
1654 *
1657{
1658 if (!(es->style & ES_MULTILINE))
1659 return FALSE;
1660
1661 dx *= es->char_width;
1663}
1664
1665
1666/*********************************************************************
1667 *
1668 * EM_SCROLL
1669 *
1672{
1673 INT dy;
1674
1675 if (!(es->style & ES_MULTILINE))
1676 return (LRESULT)FALSE;
1677
1678 dy = 0;
1679
1680 switch (action) {
1681 case SB_LINEUP:
1682 if (es->y_offset)
1683 dy = -1;
1684 break;
1685 case SB_LINEDOWN:
1686 if (es->y_offset < es->line_count - 1)
1687 dy = 1;
1688 break;
1689 case SB_PAGEUP:
1690 if (es->y_offset)
1691 dy = -(es->format_rect.bottom - es->format_rect.top) / es->line_height;
1692 break;
1693 case SB_PAGEDOWN:
1694 if (es->y_offset < es->line_count - 1)
1695 dy = (es->format_rect.bottom - es->format_rect.top) / es->line_height;
1696 break;
1697 default:
1699 }
1700 if (dy) {
1702 /* check if we are going to move too far */
1703 if(es->y_offset + dy > es->line_count - vlc)
1704 dy = max(es->line_count - vlc, 0) - es->y_offset;
1705
1706 /* Notification is done in EDIT_EM_LineScroll */
1707 if(dy) {
1709 return MAKELONG(dy, TRUE);
1710 }
1711
1712 }
1713 return (LRESULT)FALSE;
1714}
1715
1716
1717#ifdef __REACTOS__
1718static void EDIT_ImmSetCompositionWindow(EDITSTATE *es, POINT pt)
1719{
1720 COMPOSITIONFORM CompForm;
1721 HIMC hIMC = ImmGetContext(es->hwndSelf);
1722 if (!hIMC)
1723 {
1724 ERR("!hIMC\n");
1725 return;
1726 }
1727
1728 CompForm.ptCurrentPos = pt;
1729 if (es->style & ES_MULTILINE)
1730 {
1731 CompForm.dwStyle = CFS_RECT;
1732 CompForm.rcArea = es->format_rect;
1733 }
1734 else
1735 {
1736 CompForm.dwStyle = CFS_POINT;
1737 SetRectEmpty(&CompForm.rcArea);
1738 }
1739
1740 ImmSetCompositionWindow(hIMC, &CompForm);
1741 ImmReleaseContext(es->hwndSelf, hIMC);
1742}
1743#endif
1744/*********************************************************************
1745 *
1746 * EDIT_SetCaretPos
1747 *
1749static void EDIT_SetCaretPos(EDITSTATE *es, INT pos,
1750 BOOL after_wrap)
1751{
1752 LRESULT res = EDIT_EM_PosFromChar(es, pos, after_wrap);
1753#ifdef __REACTOS__
1754 HKL hKL = GetKeyboardLayout(0);
1755 POINT pt = { (short)LOWORD(res), (short)HIWORD(res) };
1756
1757 /* Don't set caret if not focused */
1758 if ((es->flags & EF_FOCUSED) == 0)
1759 return;
1760
1761 SetCaretPos(pt.x, pt.y);
1762
1763 if (!ImmIsIME(hKL))
1764 return;
1765
1766 EDIT_ImmSetCompositionWindow(es, pt);
1767#else
1768 TRACE("%d - %dx%d\n", pos, (short)LOWORD(res), (short)HIWORD(res));
1769 SetCaretPos((short)LOWORD(res), (short)HIWORD(res));
1770#endif
1771}
1772
1773
1774/*********************************************************************
1775 *
1776 * EM_SCROLLCARET
1777 *
1779static void EDIT_EM_ScrollCaret(EDITSTATE *es)
1780{
1781 if (es->style & ES_MULTILINE) {
1782 INT l;
1783 INT vlc;
1784 INT ww;
1785 INT cw = es->char_width;
1786 INT x;
1787 INT dy = 0;
1788 INT dx = 0;
1789
1790 l = EDIT_EM_LineFromChar(es, es->selection_end);
1791 x = (short)LOWORD(EDIT_EM_PosFromChar(es, es->selection_end, es->flags & EF_AFTER_WRAP));
1793 if (l >= es->y_offset + vlc)
1794 dy = l - vlc + 1 - es->y_offset;
1795 if (l < es->y_offset)
1796 dy = l - es->y_offset;
1797 ww = es->format_rect.right - es->format_rect.left;
1798 if (x < es->format_rect.left)
1799 dx = x - es->format_rect.left - ww / HSCROLL_FRACTION / cw * cw;
1800 if (x > es->format_rect.right)
1801 dx = x - es->format_rect.left - (HSCROLL_FRACTION - 1) * ww / HSCROLL_FRACTION / cw * cw;
1802 if (dy || dx || (es->y_offset && (es->line_count - es->y_offset < vlc)))
1803 {
1804 /* check if we are going to move too far */
1805 if(es->x_offset + dx + ww > es->text_width)
1806 dx = es->text_width - ww - es->x_offset;
1807 if(dx || dy || (es->y_offset && (es->line_count - es->y_offset < vlc)))
1809 }
1810 } else {
1811 INT x;
1812 INT goal;
1813 INT format_width;
1814
1815 x = (short)LOWORD(EDIT_EM_PosFromChar(es, es->selection_end, FALSE));
1816 format_width = es->format_rect.right - es->format_rect.left;
1817 if (x < es->format_rect.left) {
1818 goal = es->format_rect.left + format_width / HSCROLL_FRACTION;
1819 do {
1820 es->x_offset--;
1821 x = (short)LOWORD(EDIT_EM_PosFromChar(es, es->selection_end, FALSE));
1822 } while ((x < goal) && es->x_offset);
1823 /* FIXME: use ScrollWindow() somehow to improve performance */
1825 } else if (x > es->format_rect.right) {
1826 INT x_last;
1828 goal = es->format_rect.right - format_width / HSCROLL_FRACTION;
1829 do {
1830 es->x_offset++;
1831 x = (short)LOWORD(EDIT_EM_PosFromChar(es, es->selection_end, FALSE));
1833 } while ((x > goal) && (x_last > es->format_rect.right));
1834 /* FIXME: use ScrollWindow() somehow to improve performance */
1836 }
1837 }
1838
1839 if(es->flags & EF_FOCUSED)
1840 EDIT_SetCaretPos(es, es->selection_end, es->flags & EF_AFTER_WRAP);
1841}
1842
1843
1844/*********************************************************************
1845 *
1846 * EDIT_MoveBackward
1847 *
1849static void EDIT_MoveBackward(EDITSTATE *es, BOOL extend)
1850{
1851 INT e = es->selection_end;
1852
1853 if (e) {
1854 e--;
1855 if ((es->style & ES_MULTILINE) && e &&
1856 (es->text[e - 1] == '\r') && (es->text[e] == '\n')) {
1857 e--;
1858 if (e && (es->text[e - 1] == '\r'))
1859 e--;
1860 }
1861 }
1862 EDIT_EM_SetSel(es, extend ? es->selection_start : e, e, FALSE);
1864}
1865
1866
1867/*********************************************************************
1868 *
1869 * EDIT_MoveDown_ML
1870 *
1871 * Only for multi line controls
1872 * Move the caret one line down, on a column with the nearest
1873 * x coordinate on the screen (might be a different column).
1874 *
1876static void EDIT_MoveDown_ML(EDITSTATE *es, BOOL extend)
1877{
1878 INT s = es->selection_start;
1879 INT e = es->selection_end;
1880 BOOL after_wrap = (es->flags & EF_AFTER_WRAP);
1881 LRESULT pos = EDIT_EM_PosFromChar(es, e, after_wrap);
1882 INT x = (short)LOWORD(pos);
1883 INT y = (short)HIWORD(pos);
1884
1885 e = EDIT_CharFromPos(es, x, y + es->line_height, &after_wrap);
1886 if (!extend)
1887 s = e;
1888 EDIT_EM_SetSel(es, s, e, after_wrap);
1890}
1891
1892
1893/*********************************************************************
1894 *
1895 * EDIT_MoveEnd
1896 *
1898static void EDIT_MoveEnd(EDITSTATE *es, BOOL extend, BOOL ctrl)
1899{
1900 BOOL after_wrap = FALSE;
1901 INT e;
1902
1903 /* Pass a high value in x to make sure of receiving the end of the line */
1904 if (!ctrl && (es->style & ES_MULTILINE))
1905 e = EDIT_CharFromPos(es, 0x3fffffff,
1906 HIWORD(EDIT_EM_PosFromChar(es, es->selection_end, es->flags & EF_AFTER_WRAP)), &after_wrap);
1907 else
1908 e = get_text_length(es);
1909 EDIT_EM_SetSel(es, extend ? es->selection_start : e, e, after_wrap);
1911}
1912
1913
1914/*********************************************************************
1915 *
1916 * EDIT_MoveForward
1917 *
1919static void EDIT_MoveForward(EDITSTATE *es, BOOL extend)
1920{
1921 INT e = es->selection_end;
1922
1923 if (es->text[e]) {
1924 e++;
1925 if ((es->style & ES_MULTILINE) && (es->text[e - 1] == '\r')) {
1926 if (es->text[e] == '\n')
1927 e++;
1928 else if ((es->text[e] == '\r') && (es->text[e + 1] == '\n'))
1929 e += 2;
1930 }
1931 }
1932 EDIT_EM_SetSel(es, extend ? es->selection_start : e, e, FALSE);
1934}
1935
1936
1937/*********************************************************************
1938 *
1939 * EDIT_MoveHome
1940 *
1941 * Home key: move to beginning of line.
1942 *
1944static void EDIT_MoveHome(EDITSTATE *es, BOOL extend, BOOL ctrl)
1945{
1946 INT e;
1947
1948 /* Pass the x_offset in x to make sure of receiving the first position of the line */
1949 if (!ctrl && (es->style & ES_MULTILINE))
1950 e = EDIT_CharFromPos(es, -es->x_offset,
1951 HIWORD(EDIT_EM_PosFromChar(es, es->selection_end, es->flags & EF_AFTER_WRAP)), NULL);
1952 else
1953 e = 0;
1954 EDIT_EM_SetSel(es, extend ? es->selection_start : e, e, FALSE);
1956}
1957
1958
1959/*********************************************************************
1960 *
1961 * EDIT_MovePageDown_ML
1962 *
1963 * Only for multi line controls
1964 * Move the caret one page down, on a column with the nearest
1965 * x coordinate on the screen (might be a different column).
1966 *
1968static void EDIT_MovePageDown_ML(EDITSTATE *es, BOOL extend)
1969{
1970 INT s = es->selection_start;
1971 INT e = es->selection_end;
1972 BOOL after_wrap = (es->flags & EF_AFTER_WRAP);
1973 LRESULT pos = EDIT_EM_PosFromChar(es, e, after_wrap);
1974 INT x = (short)LOWORD(pos);
1975 INT y = (short)HIWORD(pos);
1976
1978 y + (es->format_rect.bottom - es->format_rect.top),
1979 &after_wrap);
1980 if (!extend)
1981 s = e;
1982 EDIT_EM_SetSel(es, s, e, after_wrap);
1984}
1985
1986
1987/*********************************************************************
1988 *
1989 * EDIT_MovePageUp_ML
1990 *
1991 * Only for multi line controls
1992 * Move the caret one page up, on a column with the nearest
1993 * x coordinate on the screen (might be a different column).
1994 *
1996static void EDIT_MovePageUp_ML(EDITSTATE *es, BOOL extend)
1997{
1998 INT s = es->selection_start;
1999 INT e = es->selection_end;
2000 BOOL after_wrap = (es->flags & EF_AFTER_WRAP);
2001 LRESULT pos = EDIT_EM_PosFromChar(es, e, after_wrap);
2002 INT x = (short)LOWORD(pos);
2003 INT y = (short)HIWORD(pos);
2004
2006 y - (es->format_rect.bottom - es->format_rect.top),
2007 &after_wrap);
2008 if (!extend)
2009 s = e;
2010 EDIT_EM_SetSel(es, s, e, after_wrap);
2012}
2013
2014
2015/*********************************************************************
2016 *
2017 * EDIT_MoveUp_ML
2018 *
2019 * Only for multi line controls
2020 * Move the caret one line up, on a column with the nearest
2021 * x coordinate on the screen (might be a different column).
2022 *
2024static void EDIT_MoveUp_ML(EDITSTATE *es, BOOL extend)
2025{
2026 INT s = es->selection_start;
2027 INT e = es->selection_end;
2028 BOOL after_wrap = (es->flags & EF_AFTER_WRAP);
2029 LRESULT pos = EDIT_EM_PosFromChar(es, e, after_wrap);
2030 INT x = (short)LOWORD(pos);
2031 INT y = (short)HIWORD(pos);
2032
2033 e = EDIT_CharFromPos(es, x, y - es->line_height, &after_wrap);
2034 if (!extend)
2035 s = e;
2036 EDIT_EM_SetSel(es, s, e, after_wrap);
2038}
2039
2040
2041/*********************************************************************
2042 *
2043 * EDIT_MoveWordBackward
2044 *
2046static void EDIT_MoveWordBackward(EDITSTATE *es, BOOL extend)
2047{
2048 INT s = es->selection_start;
2049 INT e = es->selection_end;
2050 INT l;
2051 INT ll;
2052 INT li;
2053
2057 if (e - li == 0) {
2058 if (l) {
2059 li = EDIT_EM_LineIndex(es, l - 1);
2060 e = li + EDIT_EM_LineLength(es, li);
2061 }
2062 } else {
2064 }
2065 if (!extend)
2066 s = e;
2069}
2070
2071
2072/*********************************************************************
2073 *
2074 * EDIT_MoveWordForward
2075 *
2077static void EDIT_MoveWordForward(EDITSTATE *es, BOOL extend)
2078{
2079 INT s = es->selection_start;
2080 INT e = es->selection_end;
2081 INT l;
2082 INT ll;
2083 INT li;
2084
2088 if (e - li == ll) {
2089 if ((es->style & ES_MULTILINE) && (l != es->line_count - 1))
2090 e = EDIT_EM_LineIndex(es, l + 1);
2091 } else {
2093 li, e - li + 1, ll, WB_RIGHT);
2094 }
2095 if (!extend)
2096 s = e;
2099}
2100
2101
2102/*********************************************************************
2103 *
2104 * EDIT_PaintText
2105 *
2108{
2109 COLORREF BkColor;
2110 COLORREF TextColor;
2111 LOGFONTW underline_font;
2112 HFONT hUnderline = 0;
2113 HFONT old_font = 0;
2114 INT ret;
2115 INT li;
2116 INT BkMode;
2117 SIZE size;
2118
2119 if (!count)
2120 return 0;
2121 BkMode = GetBkMode(dc);
2122 BkColor = GetBkColor(dc);
2123 TextColor = GetTextColor(dc);
2124 if (rev) {
2125#ifdef __REACTOS__
2126 if (TRUE)
2127#else
2128 if (es->composition_len == 0)
2129#endif
2130 {
2133 SetBkMode( dc, OPAQUE);
2134 }
2135 else
2136 {
2138 GetObjectW(current,sizeof(LOGFONTW),&underline_font);
2139 underline_font.lfUnderline = TRUE;
2140 hUnderline = CreateFontIndirectW(&underline_font);
2141 old_font = SelectObject(dc,hUnderline);
2142 }
2143 }
2145 if (es->style & ES_MULTILINE) {
2146 ret = (INT)LOWORD(TabbedTextOutW(dc, x, y, es->text + li + col, count,
2147 es->tabs_count, es->tabs, es->format_rect.left - es->x_offset));
2148 } else {
2149 TextOutW(dc, x, y, es->text + li + col, count);
2150 GetTextExtentPoint32W(dc, es->text + li + col, count, &size);
2151 ret = size.cx;
2152 }
2153 if (rev) {
2154#ifdef __REACTOS__
2155 if (TRUE)
2156#else
2157 if (es->composition_len == 0)
2158#endif
2159 {
2160 SetBkColor(dc, BkColor);
2161 SetTextColor(dc, TextColor);
2162 SetBkMode( dc, BkMode);
2163 }
2164 else
2165 {
2166 if (old_font)
2167 SelectObject(dc,old_font);
2168 if (hUnderline)
2169 DeleteObject(hUnderline);
2170 }
2171 }
2172 return ret;
2173}
2174
2175
2176/*********************************************************************
2177 *
2178 * EDIT_PaintLine
2179 *
2181static void EDIT_PaintLine(EDITSTATE *es, HDC dc, INT line, BOOL rev)
2182{
2183 INT s = 0;
2184 INT e = 0;
2185 INT li = 0;
2186 INT ll = 0;
2187 INT x;
2188 INT y;
2189 LRESULT pos;
2191
2192 if (es->style & ES_MULTILINE) {
2194
2195 if ((line < es->y_offset) || (line > es->y_offset + vlc) || (line >= es->line_count))
2196 return;
2197 } else if (line)
2198 return;
2199
2200 TRACE("line=%d\n", line);
2201
2204 x = (short)LOWORD(pos);
2205 y = (short)HIWORD(pos);
2206
2207 if (es->style & ES_MULTILINE)
2208 {
2209 int line_idx = line;
2210 x = -es->x_offset;
2211 if (es->style & ES_RIGHT || es->style & ES_CENTER)
2212 {
2213 LINEDEF *line_def = es->first_line_def;
2214 int w, lw;
2215
2216 while (line_def && line_idx)
2217 {
2218 line_def = line_def->next;
2219 line_idx--;
2220 }
2221 w = es->format_rect.right - es->format_rect.left;
2222 lw = line_def->width;
2223
2224 if (es->style & ES_RIGHT)
2225 x = w - (lw - x);
2226 else if (es->style & ES_CENTER)
2227 x += (w - lw) / 2;
2228 }
2229 x += es->format_rect.left;
2230 }
2231
2232 if (rev)
2233 {
2236 s = min(es->selection_start, es->selection_end);
2237 e = max(es->selection_start, es->selection_end);
2238 s = min(li + ll, max(li, s));
2239 e = min(li + ll, max(li, e));
2240 }
2241
2242 if (ssa)
2243 ScriptStringOut(ssa, x, y, 0, &es->format_rect, s - li, e - li, FALSE);
2244 else if (rev && (s != e) &&
2245 ((es->flags & EF_FOCUSED) || (es->style & ES_NOHIDESEL))) {
2246 x += EDIT_PaintText(es, dc, x, y, line, 0, s - li, FALSE);
2247 x += EDIT_PaintText(es, dc, x, y, line, s - li, e - s, TRUE);
2248 x += EDIT_PaintText(es, dc, x, y, line, e - li, li + ll - e, FALSE);
2249 } else
2250 x += EDIT_PaintText(es, dc, x, y, line, 0, ll, FALSE);
2251}
2252
2253
2254/*********************************************************************
2255 *
2256 * EDIT_AdjustFormatRect
2257 *
2258 * Adjusts the format rectangle for the current font and the
2259 * current client rectangle.
2260 *
2263{
2264 RECT ClientRect;
2265
2266 es->format_rect.right = max(es->format_rect.right, es->format_rect.left + es->char_width);
2267 if (es->style & ES_MULTILINE)
2268 {
2269 INT fw, vlc, max_x_offset, max_y_offset;
2270
2272 es->format_rect.bottom = es->format_rect.top + vlc * es->line_height;
2273
2274 /* correct es->x_offset */
2275 fw = es->format_rect.right - es->format_rect.left;
2276 max_x_offset = es->text_width - fw;
2277 if(max_x_offset < 0) max_x_offset = 0;
2278 if(es->x_offset > max_x_offset)
2279 es->x_offset = max_x_offset;
2280
2281 /* correct es->y_offset */
2282 max_y_offset = es->line_count - vlc;
2283 if(max_y_offset < 0) max_y_offset = 0;
2284 if(es->y_offset > max_y_offset)
2285 es->y_offset = max_y_offset;
2286
2287 /* force scroll info update */
2289 }
2290 else
2291 /* Windows doesn't care to fix text placement for SL controls */
2292 es->format_rect.bottom = es->format_rect.top + es->line_height;
2293
2294 /* Always stay within the client area */
2295 GetClientRect(es->hwndSelf, &ClientRect);
2296 es->format_rect.bottom = min(es->format_rect.bottom, ClientRect.bottom);
2297
2298 if ((es->style & ES_MULTILINE) && !(es->style & ES_AUTOHSCROLL))
2300
2301 EDIT_SetCaretPos(es, es->selection_end, es->flags & EF_AFTER_WRAP);
2302}
2303
2304
2305/*********************************************************************
2306 *
2307 * EDIT_SetRectNP
2308 *
2309 * note: this is not (exactly) the handler called on EM_SETRECTNP
2310 * it is also used to set the rect of a single line control
2311 *
2313static void EDIT_SetRectNP(EDITSTATE *es, const RECT *rc)
2314{
2316 INT bw, bh;
2318
2319 CopyRect(&es->format_rect, rc);
2320
2321 if (ExStyle & WS_EX_CLIENTEDGE) {
2322 es->format_rect.left++;
2323 es->format_rect.right--;
2324
2325 if (es->format_rect.bottom - es->format_rect.top
2326 >= es->line_height + 2)
2327 {
2328 es->format_rect.top++;
2329 es->format_rect.bottom--;
2330 }
2331 }
2332 else if (es->style & WS_BORDER) {
2334 bh = GetSystemMetrics(SM_CYBORDER) + 1;
2335 InflateRect(&es->format_rect, -bw, 0);
2336 if (es->format_rect.bottom - es->format_rect.top >= es->line_height + 2 * bh)
2337 InflateRect(&es->format_rect, 0, -bh);
2338 }
2339
2340 es->format_rect.left += es->left_margin;
2341 es->format_rect.right -= es->right_margin;
2343}
2344
2345
2346/*********************************************************************
2347 *
2348 * EM_CHARFROMPOS
2349 *
2350 * returns line number (not index) in high-order word of result.
2351 * NB : Q137805 is unclear about this. POINT * pointer in lParam apply
2352 * to Richedit, not to the edit control. Original documentation is valid.
2353 * FIXME: do the specs mean to return -1 if outside client area or
2354 * if outside formatting rectangle ???
2355 *
2358{
2359 POINT pt;
2360 RECT rc;
2361 INT index;
2362
2363 pt.x = x;
2364 pt.y = y;
2365 GetClientRect(es->hwndSelf, &rc);
2366 if (!PtInRect(&rc, pt))
2367 return -1;
2368
2371}
2372
2373
2374/*********************************************************************
2375 *
2376 * EM_FMTLINES
2377 *
2378 * Enable or disable soft breaks.
2379 *
2380 * This means: insert or remove the soft linebreak character (\r\r\n).
2381 * Take care to check if the text still fits the buffer after insertion.
2382 * If not, notify with EN_ERRSPACE.
2383 *
2385static BOOL EDIT_EM_FmtLines(EDITSTATE *es, BOOL add_eol)
2386{
2387 es->flags &= ~EF_USE_SOFTBRK;
2388 if (add_eol) {
2389 es->flags |= EF_USE_SOFTBRK;
2390 FIXME("soft break enabled, not implemented\n");
2391 }
2392 return add_eol;
2393}
2394
2395
2396/*********************************************************************
2397 *
2398 * EM_GETHANDLE
2399 *
2400 * Hopefully this won't fire back at us.
2401 * We always start with a fixed buffer in the local heap.
2402 * Despite of the documentation says that the local heap is used
2403 * only if DS_LOCALEDIT flag is set, NT and 2000 always allocate
2404 * buffer on the local heap.
2405 *
2408{
2409 if (!(es->style & ES_MULTILINE))
2410 return 0;
2411
2413
2414 /* The text buffer handle belongs to the app */
2415 es->hlocapp = es->hloc32W;
2416
2417 TRACE("Returning %p, LocalSize() = %ld\n", es->hlocapp, LocalSize(es->hlocapp));
2418 return es->hlocapp;
2419}
2420
2421
2422/*********************************************************************
2423 *
2424 * EM_GETLINE
2425 *
2428{
2429 INT line_len, dst_len;
2430 LPWSTR src;
2431 INT i;
2432
2433 if (es->style & ES_MULTILINE)
2434 {
2435 if (line >= es->line_count)
2436 return 0;
2437 }
2438 else
2439 line = 0;
2440
2442 src = es->text + i;
2443 line_len = EDIT_EM_LineLength(es, i);
2444 dst_len = *(WORD *)dst;
2445
2446 if (dst_len <= line_len)
2447 {
2448 memcpy(dst, src, dst_len * sizeof(WCHAR));
2449 return dst_len;
2450 }
2451 else /* Append 0 if enough space */
2452 {
2453 memcpy(dst, src, line_len * sizeof(WCHAR));
2454 dst[line_len] = 0;
2455 return line_len;
2456 }
2457}
2458
2459
2460/*********************************************************************
2461 *
2462 * EM_GETSEL
2463 *
2466{
2467 UINT s = es->selection_start;
2468 UINT e = es->selection_end;
2469
2470 ORDER_UINT(s, e);
2471 if (start)
2472 *start = s;
2473 if (end)
2474 *end = e;
2475 return MAKELONG(s, e);
2476}
2477
2478
2479/*********************************************************************
2480 *
2481 * EM_REPLACESEL
2482 *
2483 * FIXME: handle ES_NUMBER and ES_OEMCONVERT here
2484 *
2486static void EDIT_EM_ReplaceSel(EDITSTATE *es, BOOL can_undo, const WCHAR *lpsz_replace, UINT strl,
2487 BOOL send_update, BOOL honor_limit)
2488{
2489 UINT tl = get_text_length(es);
2490 UINT utl;
2491 UINT s;
2492 UINT e;
2493 UINT i;
2494 UINT size;
2495 LPWSTR p;
2496 HRGN hrgn = 0;
2497 LPWSTR buf = NULL;
2498 UINT bufl;
2499
2500 TRACE("%s, can_undo %d, send_update %d\n",
2501 debugstr_wn(lpsz_replace, strl), can_undo, send_update);
2502
2503 s = es->selection_start;
2504 e = es->selection_end;
2505
2507 if ((s == e) && !strl)
2508 return;
2509
2510 ORDER_UINT(s, e);
2511
2512 size = tl - (e - s) + strl;
2513 if (!size)
2514 es->text_width = 0;
2515
2516 /* Issue the EN_MAXTEXT notification and continue with replacing text
2517 * so that buffer limit is honored. */
2518 if ((honor_limit) && (size > es->buffer_limit))
2519 {
2520 if (!notify_parent(es, EN_MAXTEXT)) return;
2521 /* Buffer limit can be smaller than the actual length of text in combobox */
2522 if (es->buffer_limit < (tl - (e-s)))
2523 strl = 0;
2524 else
2525 strl = min(strl, es->buffer_limit - (tl - (e-s)));
2526 }
2527
2528 if (!EDIT_MakeFit(es, tl - (e - s) + strl))
2529 return;
2530
2531 if (e != s) {
2532 /* there is something to be deleted */
2533 TRACE("deleting stuff.\n");
2534 bufl = e - s;
2535 buf = heap_alloc((bufl + 1) * sizeof(WCHAR));
2536 if (!buf) return;
2537 memcpy(buf, es->text + s, bufl * sizeof(WCHAR));
2538 buf[bufl] = 0; /* ensure 0 termination */
2539 /* now delete */
2540 strcpyW(es->text + s, es->text + e);
2542 }
2543 if (strl) {
2544 /* there is an insertion */
2545 tl = get_text_length(es);
2546 TRACE("inserting stuff (tl %d, strl %d, selstart %d (%s), text %s)\n", tl, strl, s, debugstr_w(es->text + s), debugstr_w(es->text));
2547 for (p = es->text + tl ; p >= es->text + s ; p--)
2548 p[strl] = p[0];
2549 for (i = 0 , p = es->text + s ; i < strl ; i++)
2550 p[i] = lpsz_replace[i];
2551 if(es->style & ES_UPPERCASE)
2552 CharUpperBuffW(p, strl);
2553 else if(es->style & ES_LOWERCASE)
2554 CharLowerBuffW(p, strl);
2556 }
2557 if (es->style & ES_MULTILINE)
2558 {
2559 INT st = min(es->selection_start, es->selection_end);
2561
2562 hrgn = CreateRectRgn(0, 0, 0, 0);
2563 EDIT_BuildLineDefs_ML(es, st, st + strl,
2564 strl - abs(es->selection_end - es->selection_start), hrgn);
2565 /* if text is too long undo all changes */
2566 if (honor_limit && !(es->style & ES_AUTOVSCROLL) && (es->line_count > vlc)) {
2567 if (strl)
2568 strcpyW(es->text + e, es->text + e + strl);
2569 if (e != s)
2570 for (i = 0 , p = es->text ; i < e - s ; i++)
2571 p[i + s] = buf[i];
2574 abs(es->selection_end - es->selection_start) - strl, hrgn);
2575 strl = 0;
2576 e = s;
2577 SetRectRgn(hrgn, 0, 0, 0, 0);
2578 if (!notify_parent(es, EN_MAXTEXT)) return;
2579 }
2580 }
2581 else {
2582 INT fw = es->format_rect.right - es->format_rect.left;
2585 /* remove chars that don't fit */
2586 if (honor_limit && !(es->style & ES_AUTOHSCROLL) && (es->text_width > fw)) {
2587 while ((es->text_width > fw) && s + strl >= s) {
2588 strcpyW(es->text + s + strl - 1, es->text + s + strl);
2589 strl--;
2590 es->text_length = -1;
2593 }
2595 if (!notify_parent(es, EN_MAXTEXT)) return;
2596 }
2597 }
2598
2599 if (e != s) {
2600 if (can_undo) {
2601 utl = strlenW(es->undo_text);
2602 if (!es->undo_insert_count && (*es->undo_text && (s == es->undo_position))) {
2603 /* undo-buffer is extended to the right */
2604 EDIT_MakeUndoFit(es, utl + e - s);
2605 memcpy(es->undo_text + utl, buf, (e - s)*sizeof(WCHAR));
2606 (es->undo_text + utl)[e - s] = 0; /* ensure 0 termination */
2607 } else if (!es->undo_insert_count && (*es->undo_text && (e == es->undo_position))) {
2608 /* undo-buffer is extended to the left */
2609 EDIT_MakeUndoFit(es, utl + e - s);
2610 for (p = es->undo_text + utl ; p >= es->undo_text ; p--)
2611 p[e - s] = p[0];
2612 for (i = 0 , p = es->undo_text ; i < e - s ; i++)
2613 p[i] = buf[i];
2614 es->undo_position = s;
2615 } else {
2616 /* new undo-buffer */
2617 EDIT_MakeUndoFit(es, e - s);
2618 memcpy(es->undo_text, buf, (e - s)*sizeof(WCHAR));
2619 es->undo_text[e - s] = 0; /* ensure 0 termination */
2620 es->undo_position = s;
2621 }
2622 /* any deletion makes the old insertion-undo invalid */
2623 es->undo_insert_count = 0;
2624 } else
2626 }
2627 if (strl) {
2628 if (can_undo) {
2629 if ((s == es->undo_position) ||
2630 ((es->undo_insert_count) &&
2631 (s == es->undo_position + es->undo_insert_count)))
2632 /*
2633 * insertion is new and at delete position or
2634 * an extension to either left or right
2635 */
2636 es->undo_insert_count += strl;
2637 else {
2638 /* new insertion undo */
2639 es->undo_position = s;
2640 es->undo_insert_count = strl;
2641 /* new insertion makes old delete-buffer invalid */
2642 *es->undo_text = '\0';
2643 }
2644 } else
2646 }
2647
2648 heap_free(buf);
2649
2650 s += strl;
2651
2652 /* If text has been deleted and we're right or center aligned then scroll rightward */
2653 if (es->style & (ES_RIGHT | ES_CENTER))
2654 {
2655 INT delta = strl - abs(es->selection_end - es->selection_start);
2656
2657 if (delta < 0 && es->x_offset)
2658 {
2659 if (abs(delta) > es->x_offset)
2660 es->x_offset = 0;
2661 else
2662 es->x_offset += delta;
2663 }
2664 }
2665
2667 es->flags |= EF_MODIFIED;
2668 if (send_update) es->flags |= EF_UPDATE;
2669 if (hrgn)
2670 {
2673 }
2674 else
2676
2678
2679 /* force scroll info update */
2681
2682
2683 if(send_update || (es->flags & EF_UPDATE))
2684 {
2685 es->flags &= ~EF_UPDATE;
2686 if (!notify_parent(es, EN_CHANGE)) return;
2687 }
2689}
2690
2691
2692/*********************************************************************
2693 *
2694 * EM_SETHANDLE
2695 *
2696 * FIXME: ES_LOWERCASE, ES_UPPERCASE, ES_OEMCONVERT, ES_NUMBER ???
2697 *
2699static void EDIT_EM_SetHandle(EDITSTATE *es, HLOCAL hloc)
2700{
2701 if (!(es->style & ES_MULTILINE))
2702 return;
2703
2704 if (!hloc)
2705 return;
2706
2708
2709 es->hloc32W = hloc;
2710 es->buffer_size = LocalSize(es->hloc32W)/sizeof(WCHAR) - 1;
2711
2712 /* The text buffer handle belongs to the control */
2713 es->hlocapp = NULL;
2714
2717
2718 es->x_offset = es->y_offset = 0;
2719 es->selection_start = es->selection_end = 0;
2721 es->flags &= ~EF_MODIFIED;
2722 es->flags &= ~EF_UPDATE;
2726 /* force scroll info update */
2728}
2729
2730
2731/*********************************************************************
2732 *
2733 * EM_SETLIMITTEXT
2734 *
2735 * NOTE: this version currently implements WinNT limits
2736 *
2739{
2740 if (!limit) limit = ~0u;
2741 if (!(es->style & ES_MULTILINE)) limit = min(limit, 0x7ffffffe);
2742 es->buffer_limit = limit;
2743}
2744
2745
2746/*********************************************************************
2747 *
2748 * EM_SETMARGINS
2749 *
2750 * EC_USEFONTINFO is used as a left or right value i.e. lParam and not as an
2751 * action wParam despite what the docs say. EC_USEFONTINFO calculates the
2752 * margin according to the textmetrics of the current font.
2753 *
2754 * When EC_USEFONTINFO is used in the non_cjk case the margins only
2755 * change if the edit control is equal to or larger than a certain
2756 * size. Though there is an exception for the empty client rect case
2757 * with small font sizes.
2759static BOOL is_cjk(UINT charset)
2760{
2761 switch(charset)
2762 {
2763 case SHIFTJIS_CHARSET:
2764 case HANGUL_CHARSET:
2765 case GB2312_CHARSET:
2767 return TRUE;
2768 }
2769 /* HANGUL_CHARSET is strange, though treated as CJK by Win 8, it is
2770 * not by other versions including Win 10. */
2771 return FALSE;
2772}
2775 WORD left, WORD right, BOOL repaint)
2776{
2778 INT default_left_margin = 0; /* in pixels */
2779 INT default_right_margin = 0; /* in pixels */
2780
2781 /* Set the default margins depending on the font */
2782 if (es->font && (left == EC_USEFONTINFO || right == EC_USEFONTINFO)) {
2783 HDC dc = GetDC(es->hwndSelf);
2784 HFONT old_font = SelectObject(dc, es->font);
2786 RECT rc;
2787
2788 /* The default margins are only non zero for TrueType or Vector fonts */
2789 if (tm.tmPitchAndFamily & ( TMPF_VECTOR | TMPF_TRUETYPE )) {
2790 if (!is_cjk(tm.tmCharSet)) {
2791 default_left_margin = width / 2;
2792 default_right_margin = width / 2;
2793
2794 GetClientRect(es->hwndSelf, &rc);
2795 if (rc.right - rc.left < (width / 2 + width) * 2 &&
2796 (width >= 28 || !IsRectEmpty(&rc)) ) {
2797 default_left_margin = es->left_margin;
2798 default_right_margin = es->right_margin;
2799 }
2800 } else {
2801 /* FIXME: figure out the CJK values. They are not affected by the client rect. */
2802 default_left_margin = width / 2;
2803 default_right_margin = width / 2;
2804 }
2805 }
2806 SelectObject(dc, old_font);
2807 ReleaseDC(es->hwndSelf, dc);
2808 }
2809
2810 if (action & EC_LEFTMARGIN) {
2811 es->format_rect.left -= es->left_margin;
2812 if (left != EC_USEFONTINFO)
2813 es->left_margin = left;
2814 else
2815 es->left_margin = default_left_margin;
2816 es->format_rect.left += es->left_margin;
2817 }
2818
2819 if (action & EC_RIGHTMARGIN) {
2820 es->format_rect.right += es->right_margin;
2821 if (right != EC_USEFONTINFO)
2822 es->right_margin = right;
2823 else
2824 es->right_margin = default_right_margin;
2825 es->format_rect.right -= es->right_margin;
2826 }
2827
2830 if (repaint) EDIT_UpdateText(es, NULL, TRUE);
2831 }
2832
2833 TRACE("left=%d, right=%d\n", es->left_margin, es->right_margin);
2834}
2835
2836
2837/*********************************************************************
2838 *
2839 * EM_SETPASSWORDCHAR
2840 *
2843{
2844 LONG style;
2845
2846 if (es->style & ES_MULTILINE)
2847 return;
2848
2849 if (es->password_char == c)
2850 return;
2851
2852 style = GetWindowLongW( es->hwndSelf, GWL_STYLE );
2853 es->password_char = c;
2854 if (c) {
2855 SetWindowLongW( es->hwndSelf, GWL_STYLE, style | ES_PASSWORD );
2856 es->style |= ES_PASSWORD;
2857 } else {
2858 SetWindowLongW( es->hwndSelf, GWL_STYLE, style & ~ES_PASSWORD );
2859 es->style &= ~ES_PASSWORD;
2860 }
2863}
2864
2865
2866/*********************************************************************
2867 *
2868 * EM_SETTABSTOPS
2869 *
2871static BOOL EDIT_EM_SetTabStops(EDITSTATE *es, INT count, const INT *tabs)
2872{
2873 if (!(es->style & ES_MULTILINE))
2874 return FALSE;
2875 heap_free(es->tabs);
2876 es->tabs_count = count;
2877 if (!count)
2878 es->tabs = NULL;
2879 else {
2880 es->tabs = heap_alloc(count * sizeof(INT));
2881 memcpy(es->tabs, tabs, count * sizeof(INT));
2882 }
2884 return TRUE;
2885}
2886
2887
2888/*********************************************************************
2889 *
2890 * EM_SETWORDBREAKPROC
2891 *
2894{
2895 if (es->word_break_proc == wbp)
2896 return;
2897
2898 es->word_break_proc = wbp;
2899
2900 if ((es->style & ES_MULTILINE) && !(es->style & ES_AUTOHSCROLL)) {
2903 }
2904}
2905
2906
2907/*********************************************************************
2908 *
2909 * EM_UNDO / WM_UNDO
2910 *
2913{
2914 INT ulength;
2915 LPWSTR utext;
2916
2917 /* As per MSDN spec, for a single-line edit control,
2918 the return value is always TRUE */
2919 if( es->style & ES_READONLY )
2920 return !(es->style & ES_MULTILINE);
2921
2922 ulength = strlenW(es->undo_text);
2923
2924 utext = heap_alloc((ulength + 1) * sizeof(WCHAR));
2925
2926 strcpyW(utext, es->undo_text);
2927
2928 TRACE("before UNDO:insertion length = %d, deletion buffer = %s\n",
2929 es->undo_insert_count, debugstr_w(utext));
2930
2931 EDIT_EM_SetSel(es, es->undo_position, es->undo_position + es->undo_insert_count, FALSE);
2933 EDIT_EM_ReplaceSel(es, TRUE, utext, ulength, TRUE, TRUE);
2934 EDIT_EM_SetSel(es, es->undo_position, es->undo_position + es->undo_insert_count, FALSE);
2935 /* send the notification after the selection start and end are set */
2936 if (!notify_parent(es, EN_CHANGE)) return TRUE;
2938 heap_free(utext);
2939
2940 TRACE("after UNDO:insertion length = %d, deletion buffer = %s\n",
2941 es->undo_insert_count, debugstr_w(es->undo_text));
2942 return TRUE;
2943}
2944
2945
2946/* Helper function for WM_CHAR
2947 *
2948 * According to an MSDN blog article titled "Just because you're a control
2949 * doesn't mean that you're necessarily inside a dialog box," multiline edit
2950 * controls without ES_WANTRETURN would attempt to detect whether it is inside
2951 * a dialog box or not.
2953static inline BOOL EDIT_IsInsideDialog(EDITSTATE *es)
2954{
2955 return (es->flags & EF_DIALOGMODE);
2956}
2957
2958
2959/*********************************************************************
2960 *
2961 * WM_PASTE
2962 *
2964static void EDIT_WM_Paste(EDITSTATE *es)
2965{
2966 HGLOBAL hsrc;
2967 LPWSTR src, ptr;
2968 int len;
2969
2970 /* Protect read-only edit control from modification */
2971 if(es->style & ES_READONLY)
2972 return;
2973
2974 OpenClipboard(es->hwndSelf);
2975 if ((hsrc = GetClipboardData(CF_UNICODETEXT))) {
2976 src = GlobalLock(hsrc);
2977 len = strlenW(src);
2978 /* Protect single-line edit against pasting new line character */
2979 if (!(es->style & ES_MULTILINE) && ((ptr = strchrW(src, '\n')))) {
2980 len = ptr - src;
2981 if (len && src[len - 1] == '\r')
2982 --len;
2983 }
2985 GlobalUnlock(hsrc);
2986 }
2987 else if (es->style & ES_PASSWORD) {
2988 /* clear selected text in password edit box even with empty clipboard */
2990 }
2992}
2993
2994
2995/*********************************************************************
2996 *
2997 * WM_COPY
2998 *
3000static void EDIT_WM_Copy(EDITSTATE *es)
3001{
3002 INT s = min(es->selection_start, es->selection_end);
3003 INT e = max(es->selection_start, es->selection_end);
3004 HGLOBAL hdst;
3005 LPWSTR dst;
3006 DWORD len;
3007
3008 if (e == s) return;
3009
3010 len = e - s;
3011 hdst = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, (len + 1) * sizeof(WCHAR));
3012 dst = GlobalLock(hdst);
3013 memcpy(dst, es->text + s, len * sizeof(WCHAR));
3014 dst[len] = 0; /* ensure 0 termination */
3015 TRACE("%s\n", debugstr_w(dst));
3016 GlobalUnlock(hdst);
3017 OpenClipboard(es->hwndSelf);
3021}
3022
3023
3024/*********************************************************************
3025 *
3026 * WM_CLEAR
3027 *
3029static inline void EDIT_WM_Clear(EDITSTATE *es)
3030{
3031 /* Protect read-only edit control from modification */
3032 if(es->style & ES_READONLY)
3033 return;
3034
3036}
3037
3038
3039/*********************************************************************
3040 *
3041 * WM_CUT
3042 *
3044static inline void EDIT_WM_Cut(EDITSTATE *es)
3045{
3048}
3049
3050
3051/*********************************************************************
3052 *
3053 * WM_CHAR
3054 *
3057{
3058 BOOL control;
3059
3060#ifdef __REACTOS__
3061 if (es->bCaptureState)
3062 return 0;
3063#endif
3064
3065 control = GetKeyState(VK_CONTROL) & 0x8000;
3066
3067 switch (c) {
3068 case '\r':
3069 /* If it's not a multiline edit box, it would be ignored below.
3070 * For multiline edit without ES_WANTRETURN, we have to make a
3071 * special case.
3072 */
3073 if ((es->style & ES_MULTILINE) && !(es->style & ES_WANTRETURN))
3075 break;
3076 case '\n':
3077 if (es->style & ES_MULTILINE) {
3078 if (es->style & ES_READONLY) {
3081 } else {
3082 static const WCHAR cr_lfW[] = {'\r','\n'};
3083 EDIT_EM_ReplaceSel(es, TRUE, cr_lfW, 2, TRUE, TRUE);
3084 }
3085 }
3086 break;
3087 case '\t':
3088 if ((es->style & ES_MULTILINE) && !(es->style & ES_READONLY))
3089 {
3090 static const WCHAR tabW[] = {'\t'};
3092 break;
3093 EDIT_EM_ReplaceSel(es, TRUE, tabW, 1, TRUE, TRUE);
3094 }
3095 break;
3096 case VK_BACK:
3097 if (!(es->style & ES_READONLY) && !control) {
3098 if (es->selection_start != es->selection_end)
3100 else {
3101 /* delete character left of caret */
3102 EDIT_EM_SetSel(es, (UINT)-1, 0, FALSE);
3105 }
3106 }
3107 break;
3108 case 0x03: /* ^C */
3109 if (!(es->style & ES_PASSWORD))
3110 SendMessageW(es->hwndSelf, WM_COPY, 0, 0);
3111 break;
3112 case 0x16: /* ^V */
3113 if (!(es->style & ES_READONLY))
3114 SendMessageW(es->hwndSelf, WM_PASTE, 0, 0);
3115 break;
3116 case 0x18: /* ^X */
3117 if (!((es->style & ES_READONLY) || (es->style & ES_PASSWORD)))
3118 SendMessageW(es->hwndSelf, WM_CUT, 0, 0);
3119 break;
3120 case 0x1A: /* ^Z */
3121 if (!(es->style & ES_READONLY))
3122 SendMessageW(es->hwndSelf, WM_UNDO, 0, 0);
3123 break;
3124
3125 default:
3126 /*If Edit control style is ES_NUMBER allow users to key in only numeric values*/
3127 if( (es->style & ES_NUMBER) && !( c >= '0' && c <= '9') )
3128 break;
3129
3130 if (!(es->style & ES_READONLY) && (c >= ' ') && (c != 127))
3132 break;
3133 }
3134 return 1;
3135}
3136
3137
3138/*********************************************************************
3139 *
3140 * EDIT_ContextMenuCommand
3141 *
3143static void EDIT_ContextMenuCommand(EDITSTATE *es, UINT id)
3144{
3145 switch (id) {
3146 case EM_UNDO:
3147 SendMessageW(es->hwndSelf, WM_UNDO, 0, 0);
3148 break;
3149 case WM_CUT:
3150 SendMessageW(es->hwndSelf, WM_CUT, 0, 0);
3151 break;
3152 case WM_COPY:
3153 SendMessageW(es->hwndSelf, WM_COPY, 0, 0);
3154 break;
3155 case WM_PASTE:
3156 SendMessageW(es->hwndSelf, WM_PASTE, 0, 0);
3157 break;
3158 case WM_CLEAR:
3159 SendMessageW(es->hwndSelf, WM_CLEAR, 0, 0);
3160 break;
3161 case EM_SETSEL:
3162 SendMessageW(es->hwndSelf, EM_SETSEL, 0, -1);
3163 break;
3164 default:
3165 ERR("unknown menu item, please report\n");
3166 break;
3167 }
3168}
3169
3170
3171/*********************************************************************
3172 *
3173 * WM_CONTEXTMENU
3174 *
3175 * Note: the resource files resource/sysres_??.rc cannot define a
3176 * single popup menu. Hence we use a (dummy) menubar
3177 * containing the single popup menu as its first item.
3178 *
3179 * FIXME: the message identifiers have been chosen arbitrarily,
3180 * hence we use MF_BYPOSITION.
3181 * We might as well use the "real" values (anybody knows ?)
3182 * The menu definition is in resources/sysres_??.rc.
3183 * Once these are OK, we better use MF_BYCOMMAND here
3184 * (as we do in EDIT_WM_Command()).
3185 *
3187static void EDIT_WM_ContextMenu(EDITSTATE *es, INT x, INT y)
3188{
3189 HMENU menu = LoadMenuA(GetModuleHandleA("user32.dll"), "EDITMENU");
3190 HMENU popup = GetSubMenu(menu, 0);
3191 UINT start = es->selection_start;
3192 UINT end = es->selection_end;
3193 UINT cmd;
3194 POINT pt;
3195
3197
3198 /* undo */
3200 /* cut */
3201 EnableMenuItem(popup, 2, MF_BYPOSITION | ((end - start) && !(es->style & ES_PASSWORD) && !(es->style & ES_READONLY) ? MF_ENABLED : MF_GRAYED));
3202 /* copy */
3203 EnableMenuItem(popup, 3, MF_BYPOSITION | ((end - start) && !(es->style & ES_PASSWORD) ? MF_ENABLED : MF_GRAYED));
3204 /* paste */
3206 /* delete */
3207 EnableMenuItem(popup, 5, MF_BYPOSITION | ((end - start) && !(es->style & ES_READONLY) ? MF_ENABLED : MF_GRAYED));
3208 /* select all */
3210
3211 pt.x = x;
3212 pt.y = y;
3213
3214 if (pt.x == -1 && pt.y == -1) /* passed via VK_APPS press/release */
3215 {
3216 RECT rc;
3217
3218 /* Windows places the menu at the edit's center in this case */
3219 GetClientRect(es->hwndSelf, &rc);
3220 pt.x = rc.left + (rc.right - rc.left) / 2;
3221 pt.y = rc.top + (rc.bottom - rc.top) / 2;
3222 ClientToScreen(es->hwndSelf, &pt);
3223 }
3224
3225 if (!(es->flags & EF_FOCUSED))
3226 SetFocus(es->hwndSelf);
3227
3229 pt.x, pt.y, 0, es->hwndSelf, NULL);
3230
3231 if (cmd)
3233
3234 DestroyMenu(menu);
3235}
3236
3237
3238/*********************************************************************
3239 *
3240 * WM_GETTEXT
3241 *
3244{
3245 if (!count)
3246 return 0;
3247
3248 lstrcpynW(dst, es->text, count);
3249 return strlenW(dst);
3250}
3251
3252/*********************************************************************
3253 *
3254 * EDIT_CheckCombo
3255 *
3258{
3259 HWND hLBox = es->hwndListBox;
3260 HWND hCombo;
3261 BOOL bDropped;
3262 int nEUI;
3263
3264 if (!hLBox)
3265 return FALSE;
3266
3267 hCombo = GetParent(es->hwndSelf);
3268 bDropped = TRUE;
3269 nEUI = 0;
3270
3271 TRACE("[%p]: handling msg %x (%x)\n", es->hwndSelf, msg, key);
3272
3273 if (key == VK_UP || key == VK_DOWN)
3274 {
3275 if (SendMessageW(hCombo, CB_GETEXTENDEDUI, 0, 0))
3276 nEUI = 1;
3277
3278 if (msg == WM_KEYDOWN || nEUI)
3279 bDropped = (BOOL)SendMessageW(hCombo, CB_GETDROPPEDSTATE, 0, 0);
3280 }
3281
3282 switch (msg)
3283 {
3284 case WM_KEYDOWN:
3285 if (!bDropped && nEUI && (key == VK_UP || key == VK_DOWN))
3286 {
3287 /* make sure ComboLBox pops up */
3288 SendMessageW(hCombo, CB_SETEXTENDEDUI, FALSE, 0);
3289 key = VK_F4;
3290 nEUI = 2;
3291 }
3292
3293 SendMessageW(hLBox, WM_KEYDOWN, key, 0);
3294 break;
3295
3296 case WM_SYSKEYDOWN: /* Handle Alt+up/down arrows */
3297 if (nEUI)
3298 SendMessageW(hCombo, CB_SHOWDROPDOWN, !bDropped, 0);
3299 else
3300 SendMessageW(hLBox, WM_KEYDOWN, VK_F4, 0);
3301 break;
3302 }
3303
3304 if (nEUI == 2)
3305 SendMessageW(hCombo, CB_SETEXTENDEDUI, TRUE, 0);
3306
3307 return TRUE;
3308}
3309
3310
3311/*********************************************************************
3312 *
3313 * WM_KEYDOWN
3314 *
3315 * Handling of special keys that don't produce a WM_CHAR
3316 * (i.e. non-printable keys) & Backspace & Delete
3317 *
3320{
3321 BOOL shift;
3322 BOOL control;
3323
3324 if (GetKeyState(VK_MENU) & 0x8000)
3325 return 0;
3326
3327 shift = GetKeyState(VK_SHIFT) & 0x8000;
3328 control = GetKeyState(VK_CONTROL) & 0x8000;
3329
3330 switch (key) {
3331 case VK_F4:
3332 case VK_UP:
3334 break;
3335
3336 /* fall through */
3337 case VK_LEFT:
3338 if ((es->style & ES_MULTILINE) && (key == VK_UP))
3340 else
3341 if (control)
3343 else
3345 break;
3346 case VK_DOWN:
3348 break;
3349 /* fall through */
3350 case VK_RIGHT:
3351 if ((es->style & ES_MULTILINE) && (key == VK_DOWN))
3353 else if (control)
3355 else
3357 break;
3358 case VK_HOME:
3359 EDIT_MoveHome(es, shift, control);
3360 break;
3361 case VK_END:
3362 EDIT_MoveEnd(es, shift, control);
3363 break;
3364 case VK_PRIOR:
3365 if (es->style & ES_MULTILINE)
3367 else
3369 break;
3370 case VK_NEXT:
3371 if (es->style & ES_MULTILINE)
3373 else
3375 break;
3376 case VK_DELETE:
3377 if (!(es->style & ES_READONLY) && !(shift && control)) {
3378 if (es->selection_start != es->selection_end) {
3379 if (shift)
3380 EDIT_WM_Cut(es);
3381 else
3383 } else {
3384 if (shift) {
3385 /* delete character left of caret */
3386 EDIT_EM_SetSel(es, (UINT)-1, 0, FALSE);
3389 } else if (control) {
3390 /* delete to end of line */
3391 EDIT_EM_SetSel(es, (UINT)-1, 0, FALSE);
3394 } else {
3395 /* delete character right of caret */
3396 EDIT_EM_SetSel(es, (UINT)-1, 0, FALSE);
3399 }
3400 }
3401 }
3402 break;
3403 case VK_INSERT:
3404 if (shift) {
3405 if (!(es->style & ES_READONLY))
3407 } else if (control)
3409 break;
3410 case VK_RETURN:
3411 /* If the edit doesn't want the return send a message to the default object */
3412 if(!(es->style & ES_MULTILINE) || !(es->style & ES_WANTRETURN))
3413 {
3414 DWORD dw;
3415
3416 if (!EDIT_IsInsideDialog(es)) break;
3417 if (control) break;
3418 dw = SendMessageW(es->hwndParent, DM_GETDEFID, 0, 0);
3419 if (HIWORD(dw) == DC_HASDEFID)
3420 {
3421 HWND hwDefCtrl = GetDlgItem(es->hwndParent, LOWORD(dw));
3422 if (hwDefCtrl)
3423 {
3424 SendMessageW(es->hwndParent, WM_NEXTDLGCTL, (WPARAM)hwDefCtrl, TRUE);
3425 PostMessageW(hwDefCtrl, WM_KEYDOWN, VK_RETURN, 0);
3426 }
3427 }
3428 }
3429 break;
3430 case VK_ESCAPE:
3431 if ((es->style & ES_MULTILINE) && EDIT_IsInsideDialog(es))
3432 PostMessageW(es->hwndParent, WM_CLOSE, 0, 0);
3433 break;
3434 case VK_TAB:
3435 if ((es->style & ES_MULTILINE) && EDIT_IsInsideDialog(es))
3436 SendMessageW(es->hwndParent, WM_NEXTDLGCTL, shift, 0);
3437 break;
3438 case 'A':
3439 if (control)
3440 {
3442 {
3443 if (!notify_parent(es, EN_UPDATE)) break;
3445 }
3446 }
3447 break;
3448 }
3449 return TRUE;
3450}
3451
3452
3453/*********************************************************************
3454 *
3455 * WM_KILLFOCUS
3456 *
3459{
3461 HWND hwndSelf = es->hwndSelf;
3462
3463 es->flags &= ~EF_FOCUSED;
3464 DestroyCaret();
3465 if (!(es->style & ES_NOHIDESEL))
3466 EDIT_InvalidateText(es, es->selection_start, es->selection_end);
3467 if (!notify_parent(es, EN_KILLFOCUS)) return 0;
3468 /* Throw away left over scroll when we lose focus */
3469 es->wheelDeltaRemainder = 0;
3470
3471 if (theme)
3472 flags |= RDW_FRAME;
3473
3474 RedrawWindow(hwndSelf, NULL, NULL, flags);
3475 return 0;
3476}
3477
3478
3479/*********************************************************************
3480 *
3481 * WM_LBUTTONDBLCLK
3482 *
3483 * The caret position has been set on the WM_LBUTTONDOWN message
3484 *
3487{
3488 INT s;
3489 INT e = es->selection_end;
3490 INT l;
3491 INT li;
3492 INT ll;
3493
3494 es->bCaptureState = TRUE;
3495 SetCapture(es->hwndSelf);
3496
3504 es->region_posx = es->region_posy = 0;
3505 SetTimer(es->hwndSelf, 0, 100, NULL);
3506 return 0;
3507}
3508
3509
3510/*********************************************************************
3511 *
3512 * WM_LBUTTONDOWN
3513 *
3516{
3517 INT e;
3518 BOOL after_wrap;
3519
3520 es->bCaptureState = TRUE;
3521 SetCapture(es->hwndSelf);
3522 EDIT_ConfinePoint(es, &x, &y);
3523 e = EDIT_CharFromPos(es, x, y, &after_wrap);
3524 EDIT_EM_SetSel(es, (keys & MK_SHIFT) ? es->selection_start : e, e, after_wrap);
3526 es->region_posx = es->region_posy = 0;
3527 SetTimer(es->hwndSelf, 0, 100, NULL);
3528
3529 if (!(es->flags & EF_FOCUSED))
3530 SetFocus(es->hwndSelf);
3531
3532 return 0;
3533}
3534
3535
3536/*********************************************************************
3537 *
3538 * WM_LBUTTONUP
3539 *
3542{
3543 if (es->bCaptureState) {
3544 KillTimer(es->hwndSelf, 0);
3545 if (GetCapture() == es->hwndSelf) ReleaseCapture();
3546 }
3547 es->bCaptureState = FALSE;
3548 return 0;
3549}
3550
3551
3552/*********************************************************************
3553 *
3554 * WM_MBUTTONDOWN
3555 *
3558{
3559 SendMessageW(es->hwndSelf, WM_PASTE, 0, 0);
3560 return 0;
3561}
3562
3563
3564/*********************************************************************
3565 *
3566 * WM_MOUSEMOVE
3567 *
3570{
3571 INT e;
3572 BOOL after_wrap;
3573 INT prex, prey;
3574
3575 /* If the mouse has been captured by process other than the edit control itself,
3576 * the windows edit controls will not select the strings with mouse move.
3577 */
3578 if (!es->bCaptureState || GetCapture() != es->hwndSelf)
3579 return 0;
3580
3581 /*
3582 * FIXME: gotta do some scrolling if outside client
3583 * area. Maybe reset the timer ?
3584 */
3585 prex = x; prey = y;
3586 EDIT_ConfinePoint(es, &x, &y);
3587 es->region_posx = (prex < x) ? -1 : ((prex > x) ? 1 : 0);
3588 es->region_posy = (prey < y) ? -1 : ((prey > y) ? 1 : 0);
3589 e = EDIT_CharFromPos(es, x, y, &after_wrap);
3590 EDIT_EM_SetSel(es, es->selection_start, e, after_wrap);
3591 EDIT_SetCaretPos(es,es->selection_end,es->flags & EF_AFTER_WRAP);
3592 return 0;
3593}
3594
3595
3596/*********************************************************************
3597 *
3598 * WM_PAINT
3599 *
3601static void EDIT_WM_Paint(EDITSTATE *es, HDC hdc)
3602{
3603 PAINTSTRUCT ps;
3604 INT i;
3605 HDC dc;
3606 HFONT old_font = 0;
3607 RECT rc;
3608 RECT rcClient;
3609 RECT rcLine;
3610 RECT rcRgn;
3611 HBRUSH brush;
3612 HBRUSH old_brush;
3613 INT bw, bh;
3614 BOOL rev = es->bEnableState &&
3615 ((es->flags & EF_FOCUSED) ||
3616 (es->style & ES_NOHIDESEL));
3617 dc = hdc ? hdc : BeginPaint(es->hwndSelf, &ps);
3618
3619 /* The dc we use for calculating may not be the one we paint into.
3620 This is the safest action. */
3622 GetClientRect(es->hwndSelf, &rcClient);
3623
3624 /* get the background brush */
3625 brush = EDIT_NotifyCtlColor(es, dc);
3626
3627 /* paint the border and the background */
3628 IntersectClipRect(dc, rcClient.left, rcClient.top, rcClient.right, rcClient.bottom);
3629
3630 if(es->style & WS_BORDER) {
3633 rc = rcClient;
3634 if(es->style & ES_MULTILINE) {
3635 if(es->style & WS_HSCROLL) rc.bottom+=bh;
3636 if(es->style & WS_VSCROLL) rc.right+=bw;
3637 }
3638
3639 /* Draw the frame. Same code as in nonclient.c */
3641 PatBlt(dc, rc.left, rc.top, rc.right - rc.left, bh, PATCOPY);
3642 PatBlt(dc, rc.left, rc.top, bw, rc.bottom - rc.top, PATCOPY);
3643 PatBlt(dc, rc.left, rc.bottom - 1, rc.right - rc.left, -bw, PATCOPY);
3644 PatBlt(dc, rc.right - 1, rc.top, -bw, rc.bottom - rc.top, PATCOPY);
3645 SelectObject(dc, old_brush);
3646
3647 /* Keep the border clean */
3648 IntersectClipRect(dc, rc.left+bw, rc.top+bh,
3649 max(rc.right-bw, rc.left+bw), max(rc.bottom-bh, rc.top+bh));
3650 }
3651
3652 GetClipBox(dc, &rc);
3653 FillRect(dc, &rc, brush);
3654
3655 IntersectClipRect(dc, es->format_rect.left,
3656 es->format_rect.top,
3657 es->format_rect.right,
3658 es->format_rect.bottom);
3659 if (es->style & ES_MULTILINE) {
3660 rc = rcClient;
3661 IntersectClipRect(dc, rc.left, rc.top, rc.right, rc.bottom);
3662 }
3663 if (es->font)
3664 old_font = SelectObject(dc, es->font);
3665
3666 if (!es->bEnableState)
3668 GetClipBox(dc, &rcRgn);
3669 if (es->style & ES_MULTILINE) {
3671 for (i = es->y_offset ; i <= min(es->y_offset + vlc, es->y_offset + es->line_count - 1) ; i++) {
3673 EDIT_GetLineRect(es, i, 0, -1, &rcLine);
3674 if (IntersectRect(&rc, &rcRgn, &rcLine))
3675 EDIT_PaintLine(es, dc, i, rev);
3676 }
3677 } else {
3679 EDIT_GetLineRect(es, 0, 0, -1, &rcLine);
3680 if (IntersectRect(&rc, &rcRgn, &rcLine))
3681 EDIT_PaintLine(es, dc, 0, rev);
3682 }
3683 if (es->font)
3684 SelectObject(dc, old_font);
3685
3686 if (!hdc)
3687 EndPaint(es->hwndSelf, &ps);
3688}
3690static void EDIT_WM_NCPaint(HWND hwnd, HRGN region)
3691{
3693 HTHEME theme = GetWindowTheme(hwnd);
3694 HRGN cliprgn = region;
3695
3696 if (theme && exStyle & WS_EX_CLIENTEDGE)
3697 {
3698 HDC dc;
3699 RECT r;
3700 int cxEdge = GetSystemMetrics(SM_CXEDGE),
3701 cyEdge = GetSystemMetrics(SM_CYEDGE);
3702 const int part = EP_EDITTEXT;
3703 int state = ETS_NORMAL;
3704 DWORD dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
3705
3706 if (!IsWindowEnabled(hwnd))
3708 else if (dwStyle & ES_READONLY)
3710 else if (GetFocus() == hwnd)
3712
3713 GetWindowRect(hwnd, &r);
3714
3715 /* New clipping region passed to default proc to exclude border */
3716 cliprgn = CreateRectRgn(r.left + cxEdge, r.top + cyEdge,
3717 r.right - cxEdge, r.bottom - cyEdge);
3718 if (region != (HRGN)1)
3719 CombineRgn(cliprgn, cliprgn, region, RGN_AND);
3720 OffsetRect(&r, -r.left, -r.top);
3721
3722#ifdef __REACTOS__ /* r73789 */
3723 dc = GetWindowDC(hwnd);
3724 /* Exclude client part */
3726 r.left + cxEdge,
3727 r.top + cyEdge,
3728 r.right - cxEdge,
3729 r.bottom -cyEdge);
3730#else
3732 OffsetRect(&r, -r.left, -r.top);
3733#endif
3734
3737 DrawThemeBackground(theme, dc, part, state, &r, 0);
3738 ReleaseDC(hwnd, dc);
3739 }
3740
3741 /* Call default proc to get the scrollbars etc. also painted */
3742 DefWindowProcW (hwnd, WM_NCPAINT, (WPARAM)cliprgn, 0);
3743 if (cliprgn != region)
3744 DeleteObject(cliprgn);
3745}
3746
3747/*********************************************************************
3748 *
3749 * WM_SETFOCUS
3750 *
3752static void EDIT_WM_SetFocus(HTHEME theme, EDITSTATE *es)
3753{
3755
3756 es->flags |= EF_FOCUSED;
3757
3758 if (!(es->style & ES_NOHIDESEL))
3759 EDIT_InvalidateText(es, es->selection_start, es->selection_end);
3760
3761#ifdef __REACTOS__
3762 SystemParametersInfo(SPI_GETCARETWIDTH, 0, &es->dwCaretWidth, 0);
3763 CreateCaret(es->hwndSelf, NULL, es->dwCaretWidth, es->line_height);
3764#else
3765 CreateCaret(es->hwndSelf, 0, 1, es->line_height);
3766#endif
3767 EDIT_SetCaretPos(es, es->selection_end, es->flags & EF_AFTER_WRAP);
3768 ShowCaret(es->hwndSelf);
3769 if (!notify_parent(es, EN_SETFOCUS)) return;
3770
3771 if (theme)
3773
3774 RedrawWindow(es->hwndSelf, NULL, NULL, flags);
3775}
3776
3777
3778/*********************************************************************
3779 *
3780 * WM_SETFONT
3781 *
3782 * With Win95 look the margins are set to default font value unless
3783 * the system font (font == 0) is being set, in which case they are left
3784 * unchanged.
3785 *
3788{
3790 HDC dc;
3791 HFONT old_font = 0;
3792 RECT clientRect;
3793
3794 es->font = font;
3796 dc = GetDC(es->hwndSelf);
3797 if (font)
3798 old_font = SelectObject(dc, font);
3800 es->line_height = tm.tmHeight;
3801 es->char_width = tm.tmAveCharWidth;
3802 if (font)
3803 SelectObject(dc, old_font);
3804 ReleaseDC(es->hwndSelf, dc);
3805
3806 /* Reset the format rect and the margins */
3807 GetClientRect(es->hwndSelf, &clientRect);
3808 EDIT_SetRectNP(es, &clientRect);
3811
3812 if (es->style & ES_MULTILINE)
3814 else
3816
3817 if (redraw)
3819 if (es->flags & EF_FOCUSED) {
3820 DestroyCaret();
3821#ifdef __REACTOS__
3822 CreateCaret(es->hwndSelf, NULL, es->dwCaretWidth, es->line_height);
3823#else
3824 CreateCaret(es->hwndSelf, 0, 1, es->line_height);
3825#endif
3826 EDIT_SetCaretPos(es, es->selection_end,
3827 es->flags & EF_AFTER_WRAP);
3828 ShowCaret(es->hwndSelf);
3829 }
3830#ifdef __REACTOS__
3832 {
3833 LOGFONTW lf;
3834 HIMC hIMC = ImmGetContext(es->hwndSelf);
3835 if (font == NULL)
3837 GetObjectW(font, sizeof(lf), &lf);
3838 ImmSetCompositionFontW(hIMC, &lf);
3839 ImmReleaseContext(es->hwndSelf, hIMC);
3840 }
3841#endif
3842}
3843
3844
3845/*********************************************************************
3846 *
3847 * WM_SETTEXT
3848 *
3849 * NOTES
3850 * For multiline controls (ES_MULTILINE), reception of WM_SETTEXT triggers:
3851 * The modified flag is reset. No notifications are sent.
3852 *
3853 * For single-line controls, reception of WM_SETTEXT triggers:
3854 * The modified flag is reset. EN_UPDATE and EN_CHANGE notifications are sent.
3855 *
3858{
3859 if (es->flags & EF_UPDATE)
3860 /* fixed this bug once; complain if we see it about to happen again. */
3861 ERR("SetSel may generate UPDATE message whose handler may reset "
3862 "selection.\n");
3863
3864 EDIT_EM_SetSel(es, 0, (UINT)-1, FALSE);
3865 if (text)
3866 {
3867 TRACE("%s\n", debugstr_w(text));
3869 }
3870 else
3871 {