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