include: Windows SDK C headers have snprintf.
[wine.git] / dlls / riched20 / editor.c
blobc4a69027333e401c6e8d762b4620cf15fb29cc37
1 /*
2 * RichEdit - functions dealing with editor object
4 * Copyright 2004 by Krzysztof Foltman
5 * Copyright 2005 by Cihan Altinay
6 * Copyright 2005 by Phil Krylov
7 * Copyright 2008 Eric Pouech
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24 /*
25 API implementation status:
27 Messages (ANSI versions not done yet)
28 + EM_AUTOURLDETECT 2.0
29 + EM_CANPASTE
30 + EM_CANREDO 2.0
31 + EM_CANUNDO
32 + EM_CHARFROMPOS
33 - EM_DISPLAYBAND
34 + EM_EMPTYUNDOBUFFER
35 + EM_EXGETSEL
36 + EM_EXLIMITTEXT
37 + EM_EXLINEFROMCHAR
38 + EM_EXSETSEL
39 + EM_FINDTEXT (only FR_DOWN flag implemented)
40 + EM_FINDTEXTEX (only FR_DOWN flag implemented)
41 - EM_FINDWORDBREAK
42 - EM_FMTLINES
43 - EM_FORMATRANGE
44 + EM_GETAUTOURLDETECT 2.0
45 - EM_GETBIDIOPTIONS 3.0
46 - EM_GETCHARFORMAT (partly done)
47 - EM_GETEDITSTYLE
48 + EM_GETEVENTMASK
49 + EM_GETFIRSTVISIBLELINE (can be optimized if needed)
50 - EM_GETIMECOLOR 1.0asian
51 - EM_GETIMECOMPMODE 2.0
52 - EM_GETIMEOPTIONS 1.0asian
53 - EM_GETIMESTATUS
54 - EM_GETLANGOPTIONS 2.0
55 + EM_GETLIMITTEXT
56 + EM_GETLINE
57 + EM_GETLINECOUNT returns number of rows, not of paragraphs
58 + EM_GETMODIFY
59 + EM_GETOLEINTERFACE
60 + EM_GETOPTIONS
61 + EM_GETPARAFORMAT
62 + EM_GETPASSWORDCHAR 2.0
63 - EM_GETPUNCTUATION 1.0asian
64 + EM_GETRECT
65 - EM_GETREDONAME 2.0
66 + EM_GETSEL
67 + EM_GETSELTEXT (ANSI&Unicode)
68 + EM_GETSCROLLPOS 3.0
69 ! - EM_GETTHUMB
70 + EM_GETTEXTEX 2.0
71 + EM_GETTEXTLENGTHEX (GTL_PRECISE unimplemented)
72 + EM_GETTEXTMODE 2.0
73 ? + EM_GETTEXTRANGE (ANSI&Unicode)
74 - EM_GETTYPOGRAPHYOPTIONS 3.0
75 - EM_GETUNDONAME
76 + EM_GETWORDBREAKPROC
77 - EM_GETWORDBREAKPROCEX
78 - EM_GETWORDWRAPMODE 1.0asian
79 + EM_GETZOOM 3.0
80 + EM_HIDESELECTION
81 + EM_LIMITTEXT (Also called EM_SETLIMITTEXT)
82 + EM_LINEFROMCHAR
83 + EM_LINEINDEX
84 + EM_LINELENGTH
85 + EM_LINESCROLL
86 - EM_PASTESPECIAL
87 + EM_POSFROMCHAR
88 + EM_REDO 2.0
89 + EM_REQUESTRESIZE
90 + EM_REPLACESEL (proper style?) ANSI&Unicode
91 + EM_SCROLL
92 + EM_SCROLLCARET
93 + EM_SELECTIONTYPE
94 - EM_SETBIDIOPTIONS 3.0
95 + EM_SETBKGNDCOLOR
96 + EM_SETCHARFORMAT (partly done, no ANSI)
97 - EM_SETEDITSTYLE
98 + EM_SETEVENTMASK (few notifications supported)
99 + EM_SETFONTSIZE
100 - EM_SETIMECOLOR 1.0asian
101 - EM_SETIMEOPTIONS 1.0asian
102 - EM_SETIMESTATUS
103 - EM_SETLANGOPTIONS 2.0
104 - EM_SETLIMITTEXT
105 - EM_SETMARGINS
106 + EM_SETMODIFY (not sure if implementation is correct)
107 - EM_SETOLECALLBACK
108 + EM_SETOPTIONS (partially implemented)
109 - EM_SETPALETTE 2.0
110 + EM_SETPARAFORMAT
111 + EM_SETPASSWORDCHAR 2.0
112 - EM_SETPUNCTUATION 1.0asian
113 + EM_SETREADONLY no beep on modification attempt
114 + EM_SETRECT
115 + EM_SETRECTNP (EM_SETRECT without repainting)
116 + EM_SETSEL
117 + EM_SETSCROLLPOS 3.0
118 - EM_SETTABSTOPS 3.0
119 - EM_SETTARGETDEVICE (partial)
120 + EM_SETTEXTEX 3.0 (proper style?)
121 - EM_SETTEXTMODE 2.0
122 - EM_SETTYPOGRAPHYOPTIONS 3.0
123 + EM_SETUNDOLIMIT 2.0
124 + EM_SETWORDBREAKPROC (used only for word movement at the moment)
125 - EM_SETWORDBREAKPROCEX
126 - EM_SETWORDWRAPMODE 1.0asian
127 + EM_SETZOOM 3.0
128 + EM_SHOWSCROLLBAR 2.0
129 + EM_STOPGROUPTYPING 2.0
130 + EM_STREAMIN
131 + EM_STREAMOUT
132 + EM_UNDO
133 + WM_CHAR
134 + WM_CLEAR
135 + WM_COPY
136 + WM_CUT
137 + WM_GETDLGCODE (the current implementation is incomplete)
138 + WM_GETTEXT (ANSI&Unicode)
139 + WM_GETTEXTLENGTH (ANSI version sucks)
140 + WM_HSCROLL
141 + WM_PASTE
142 + WM_SETFONT
143 + WM_SETTEXT (resets undo stack !) (proper style?) ANSI&Unicode
144 + WM_STYLECHANGING (seems to do nothing)
145 + WM_STYLECHANGED (seems to do nothing)
146 + WM_UNICHAR
147 + WM_VSCROLL
149 Notifications
151 * EN_CHANGE (sent from the wrong place)
152 - EN_CORRECTTEXT
153 - EN_DROPFILES
154 - EN_ERRSPACE
155 - EN_HSCROLL
156 - EN_IMECHANGE
157 + EN_KILLFOCUS
158 - EN_LINK
159 - EN_MAXTEXT
160 - EN_MSGFILTER
161 - EN_OLEOPFAILED
162 - EN_PROTECTED
163 + EN_REQUESTRESIZE
164 - EN_SAVECLIPBOARD
165 + EN_SELCHANGE
166 + EN_SETFOCUS
167 - EN_STOPNOUNDO
168 * EN_UPDATE (sent from the wrong place)
169 - EN_VSCROLL
171 Styles
173 - ES_AUTOHSCROLL
174 - ES_AUTOVSCROLL
175 + ES_CENTER
176 + ES_DISABLENOSCROLL (scrollbar is always visible)
177 - ES_EX_NOCALLOLEINIT
178 + ES_LEFT
179 + ES_MULTILINE
180 - ES_NOIME
181 - ES_READONLY (I'm not sure if beeping is the proper behaviour)
182 + ES_RIGHT
183 - ES_SAVESEL
184 - ES_SELFIME
185 - ES_SUNKEN
186 - ES_VERTICAL
187 - ES_WANTRETURN (don't know how to do WM_GETDLGCODE part)
188 - WS_SETFONT
189 + WS_HSCROLL
190 + WS_VSCROLL
194 * RICHED20 TODO (incomplete):
196 * - messages/styles/notifications listed above
197 * - add remaining CHARFORMAT/PARAFORMAT fields
198 * - right/center align should strip spaces from the beginning
199 * - pictures/OLE objects (not just smiling faces that lack API support ;-) )
200 * - COM interface (looks like a major pain in the TODO list)
201 * - calculate heights of pictures (half-done)
202 * - hysteresis during wrapping (related to scrollbars appearing/disappearing)
203 * - find/replace
204 * - how to implement EM_FORMATRANGE and EM_DISPLAYBAND ? (Mission Impossible)
205 * - italic caret with italic fonts
206 * - IME
207 * - most notifications aren't sent at all (the most important ones are)
208 * - when should EN_SELCHANGE be sent after text change ? (before/after EN_UPDATE?)
209 * - WM_SETTEXT may use wrong style (but I'm 80% sure it's OK)
210 * - EM_GETCHARFORMAT with SCF_SELECTION may not behave 100% like in original (but very close)
211 * - full justification
212 * - hyphenation
213 * - tables
214 * - ListBox & ComboBox not implemented
216 * Bugs that are probably fixed, but not so easy to verify:
217 * - EN_UPDATE/EN_CHANGE are handled very incorrectly (should be OK now)
218 * - undo for ME_JoinParagraphs doesn't store paragraph format ? (it does)
219 * - check/fix artificial EOL logic (bCursorAtEnd, hardly logical)
220 * - caret shouldn't be displayed when selection isn't empty
221 * - check refcounting in style management functions (looks perfect now, but no bugs is suspicious)
222 * - undo for setting default format (done, might be buggy)
223 * - styles might be not released properly (looks like they work like charm, but who knows?
227 #define NONAMELESSUNION
229 #include "editor.h"
230 #include "commdlg.h"
231 #include "winreg.h"
232 #define NO_SHLWAPI_STREAM
233 #include "shlwapi.h"
234 #include "rtf.h"
235 #include "imm.h"
236 #include "res.h"
238 #define STACK_SIZE_DEFAULT 100
239 #define STACK_SIZE_MAX 1000
241 #define TEXT_LIMIT_DEFAULT 32767
243 WINE_DEFAULT_DEBUG_CHANNEL(richedit);
245 static BOOL ME_RegisterEditorClass(HINSTANCE);
246 static BOOL ME_UpdateLinkAttribute(ME_TextEditor *editor, ME_Cursor *start, int nChars);
248 static const WCHAR REListBox20W[] = {'R','E','L','i','s','t','B','o','x','2','0','W', 0};
249 static const WCHAR REComboBox20W[] = {'R','E','C','o','m','b','o','B','o','x','2','0','W', 0};
250 static HCURSOR hLeft;
252 BOOL me_debug = FALSE;
253 HANDLE me_heap = NULL;
255 static BOOL ME_ListBoxRegistered = FALSE;
256 static BOOL ME_ComboBoxRegistered = FALSE;
258 static inline BOOL is_version_nt(void)
260 return !(GetVersion() & 0x80000000);
263 static ME_TextBuffer *ME_MakeText(void) {
264 ME_TextBuffer *buf = heap_alloc(sizeof(*buf));
265 ME_DisplayItem *p1 = ME_MakeDI(diTextStart);
266 ME_DisplayItem *p2 = ME_MakeDI(diTextEnd);
268 p1->prev = NULL;
269 p1->next = p2;
270 p2->prev = p1;
271 p2->next = NULL;
272 p1->member.para.next_para = p2;
273 p2->member.para.prev_para = p1;
274 p2->member.para.nCharOfs = 0;
276 buf->pFirst = p1;
277 buf->pLast = p2;
278 buf->pCharStyle = NULL;
280 return buf;
284 static LRESULT ME_StreamInText(ME_TextEditor *editor, DWORD dwFormat, ME_InStream *stream, ME_Style *style)
286 WCHAR *pText;
287 LRESULT total_bytes_read = 0;
288 BOOL is_read = FALSE;
289 DWORD cp = CP_ACP, copy = 0;
290 char conv_buf[4 + STREAMIN_BUFFER_SIZE]; /* up to 4 additional UTF-8 bytes */
292 static const char bom_utf8[] = {0xEF, 0xBB, 0xBF};
294 TRACE("%08x %p\n", dwFormat, stream);
296 do {
297 LONG nWideChars = 0;
298 WCHAR wszText[STREAMIN_BUFFER_SIZE+1];
300 if (!stream->dwSize)
302 ME_StreamInFill(stream);
303 if (stream->editstream->dwError)
304 break;
305 if (!stream->dwSize)
306 break;
307 total_bytes_read += stream->dwSize;
310 if (!(dwFormat & SF_UNICODE))
312 char * buf = stream->buffer;
313 DWORD size = stream->dwSize, end;
315 if (!is_read)
317 is_read = TRUE;
318 if (stream->dwSize >= 3 && !memcmp(stream->buffer, bom_utf8, 3))
320 cp = CP_UTF8;
321 buf += 3;
322 size -= 3;
326 if (cp == CP_UTF8)
328 if (copy)
330 memcpy(conv_buf + copy, buf, size);
331 buf = conv_buf;
332 size += copy;
334 end = size;
335 while ((buf[end-1] & 0xC0) == 0x80)
337 --end;
338 --total_bytes_read; /* strange, but seems to match windows */
340 if (buf[end-1] & 0x80)
342 DWORD need = 0;
343 if ((buf[end-1] & 0xE0) == 0xC0)
344 need = 1;
345 if ((buf[end-1] & 0xF0) == 0xE0)
346 need = 2;
347 if ((buf[end-1] & 0xF8) == 0xF0)
348 need = 3;
350 if (size - end >= need)
352 /* we have enough bytes for this sequence */
353 end = size;
355 else
357 /* need more bytes, so don't transcode this sequence */
358 --end;
362 else
363 end = size;
365 nWideChars = MultiByteToWideChar(cp, 0, buf, end, wszText, STREAMIN_BUFFER_SIZE);
366 pText = wszText;
368 if (cp == CP_UTF8)
370 if (end != size)
372 memcpy(conv_buf, buf + end, size - end);
373 copy = size - end;
377 else
379 nWideChars = stream->dwSize >> 1;
380 pText = (WCHAR *)stream->buffer;
383 ME_InsertTextFromCursor(editor, 0, pText, nWideChars, style);
384 if (stream->dwSize == 0)
385 break;
386 stream->dwSize = 0;
387 } while(1);
388 return total_bytes_read;
391 static void ME_ApplyBorderProperties(RTF_Info *info,
392 ME_BorderRect *borderRect,
393 RTFBorder *borderDef)
395 int i, colorNum;
396 ME_Border *pBorders[] = {&borderRect->top,
397 &borderRect->left,
398 &borderRect->bottom,
399 &borderRect->right};
400 for (i = 0; i < 4; i++)
402 RTFColor *colorDef = info->colorList;
403 pBorders[i]->width = borderDef[i].width;
404 colorNum = borderDef[i].color;
405 while (colorDef && colorDef->rtfCNum != colorNum)
406 colorDef = colorDef->rtfNextColor;
407 if (colorDef)
408 pBorders[i]->colorRef = RGB(
409 colorDef->rtfCRed >= 0 ? colorDef->rtfCRed : 0,
410 colorDef->rtfCGreen >= 0 ? colorDef->rtfCGreen : 0,
411 colorDef->rtfCBlue >= 0 ? colorDef->rtfCBlue : 0);
412 else
413 pBorders[i]->colorRef = RGB(0, 0, 0);
417 void ME_RTFCharAttrHook(RTF_Info *info)
419 CHARFORMAT2W fmt;
420 fmt.cbSize = sizeof(fmt);
421 fmt.dwMask = 0;
422 fmt.dwEffects = 0;
424 switch(info->rtfMinor)
426 case rtfPlain:
427 /* FIXME add more flags once they're implemented */
428 fmt.dwMask = CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE | CFM_UNDERLINETYPE | CFM_STRIKEOUT |
429 CFM_COLOR | CFM_BACKCOLOR | CFM_SIZE | CFM_WEIGHT;
430 fmt.dwEffects = CFE_AUTOCOLOR | CFE_AUTOBACKCOLOR;
431 fmt.yHeight = 12*20; /* 12pt */
432 fmt.wWeight = FW_NORMAL;
433 fmt.bUnderlineType = CFU_UNDERLINE;
434 break;
435 case rtfBold:
436 fmt.dwMask = CFM_BOLD | CFM_WEIGHT;
437 fmt.dwEffects = info->rtfParam ? CFE_BOLD : 0;
438 fmt.wWeight = info->rtfParam ? FW_BOLD : FW_NORMAL;
439 break;
440 case rtfItalic:
441 fmt.dwMask = CFM_ITALIC;
442 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
443 break;
444 case rtfUnderline:
445 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
446 fmt.bUnderlineType = CFU_UNDERLINE;
447 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
448 break;
449 case rtfDotUnderline:
450 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
451 fmt.bUnderlineType = CFU_UNDERLINEDOTTED;
452 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
453 break;
454 case rtfDbUnderline:
455 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
456 fmt.bUnderlineType = CFU_UNDERLINEDOUBLE;
457 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
458 break;
459 case rtfWordUnderline:
460 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
461 fmt.bUnderlineType = CFU_UNDERLINEWORD;
462 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
463 break;
464 case rtfNoUnderline:
465 fmt.dwMask = CFM_UNDERLINE;
466 fmt.dwEffects = 0;
467 break;
468 case rtfStrikeThru:
469 fmt.dwMask = CFM_STRIKEOUT;
470 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
471 break;
472 case rtfSubScript:
473 case rtfSuperScript:
474 case rtfSubScrShrink:
475 case rtfSuperScrShrink:
476 case rtfNoSuperSub:
477 fmt.dwMask = CFM_SUBSCRIPT|CFM_SUPERSCRIPT;
478 if (info->rtfMinor == rtfSubScrShrink) fmt.dwEffects = CFE_SUBSCRIPT;
479 if (info->rtfMinor == rtfSuperScrShrink) fmt.dwEffects = CFE_SUPERSCRIPT;
480 if (info->rtfMinor == rtfNoSuperSub) fmt.dwEffects = 0;
481 break;
482 case rtfInvisible:
483 fmt.dwMask = CFM_HIDDEN;
484 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
485 break;
486 case rtfBackColor:
487 fmt.dwMask = CFM_BACKCOLOR;
488 fmt.dwEffects = 0;
489 if (info->rtfParam == 0)
490 fmt.dwEffects = CFE_AUTOBACKCOLOR;
491 else if (info->rtfParam != rtfNoParam)
493 RTFColor *c = RTFGetColor(info, info->rtfParam);
494 if (c && c->rtfCBlue >= 0)
495 fmt.crBackColor = (c->rtfCBlue<<16)|(c->rtfCGreen<<8)|(c->rtfCRed);
496 else
497 fmt.dwEffects = CFE_AUTOBACKCOLOR;
499 break;
500 case rtfForeColor:
501 fmt.dwMask = CFM_COLOR;
502 fmt.dwEffects = 0;
503 if (info->rtfParam == 0)
504 fmt.dwEffects = CFE_AUTOCOLOR;
505 else if (info->rtfParam != rtfNoParam)
507 RTFColor *c = RTFGetColor(info, info->rtfParam);
508 if (c && c->rtfCBlue >= 0)
509 fmt.crTextColor = (c->rtfCBlue<<16)|(c->rtfCGreen<<8)|(c->rtfCRed);
510 else {
511 fmt.dwEffects = CFE_AUTOCOLOR;
514 break;
515 case rtfFontNum:
516 if (info->rtfParam != rtfNoParam)
518 RTFFont *f = RTFGetFont(info, info->rtfParam);
519 if (f)
521 MultiByteToWideChar(CP_ACP, 0, f->rtfFName, -1, fmt.szFaceName, ARRAY_SIZE(fmt.szFaceName));
522 fmt.szFaceName[ARRAY_SIZE(fmt.szFaceName)-1] = '\0';
523 fmt.bCharSet = f->rtfFCharSet;
524 fmt.dwMask = CFM_FACE | CFM_CHARSET;
525 fmt.bPitchAndFamily = f->rtfFPitch | (f->rtfFFamily << 4);
528 break;
529 case rtfFontSize:
530 fmt.dwMask = CFM_SIZE;
531 if (info->rtfParam != rtfNoParam)
532 fmt.yHeight = info->rtfParam*10;
533 break;
535 if (fmt.dwMask) {
536 ME_Style *style2;
537 RTFFlushOutputBuffer(info);
538 /* FIXME too slow ? how come ? */
539 style2 = ME_ApplyStyle(info->editor, info->style, &fmt);
540 ME_ReleaseStyle(info->style);
541 info->style = style2;
542 info->styleChanged = TRUE;
546 /* FIXME this function doesn't get any information about context of the RTF tag, which is very bad,
547 the same tags mean different things in different contexts */
548 void ME_RTFParAttrHook(RTF_Info *info)
550 switch(info->rtfMinor)
552 case rtfParDef: /* restores default paragraph attributes */
553 if (!info->editor->bEmulateVersion10) /* v4.1 */
554 info->borderType = RTFBorderParaLeft;
555 else /* v1.0 - 3.0 */
556 info->borderType = RTFBorderParaTop;
557 info->fmt.dwMask = PFM_ALIGNMENT | PFM_BORDER | PFM_LINESPACING | PFM_TABSTOPS |
558 PFM_OFFSET | PFM_RIGHTINDENT | PFM_SPACEAFTER | PFM_SPACEBEFORE |
559 PFM_STARTINDENT | PFM_RTLPARA | PFM_NUMBERING | PFM_NUMBERINGSTART |
560 PFM_NUMBERINGSTYLE | PFM_NUMBERINGTAB;
561 /* TODO: shading */
562 info->fmt.wAlignment = PFA_LEFT;
563 info->fmt.cTabCount = 0;
564 info->fmt.dxOffset = info->fmt.dxStartIndent = info->fmt.dxRightIndent = 0;
565 info->fmt.wBorderWidth = info->fmt.wBorders = 0;
566 info->fmt.wBorderSpace = 0;
567 info->fmt.bLineSpacingRule = 0;
568 info->fmt.dySpaceBefore = info->fmt.dySpaceAfter = 0;
569 info->fmt.dyLineSpacing = 0;
570 info->fmt.wEffects &= ~PFE_RTLPARA;
571 info->fmt.wNumbering = 0;
572 info->fmt.wNumberingStart = 0;
573 info->fmt.wNumberingStyle = 0;
574 info->fmt.wNumberingTab = 0;
576 if (!info->editor->bEmulateVersion10) /* v4.1 */
578 if (info->tableDef && info->tableDef->tableRowStart &&
579 info->tableDef->tableRowStart->member.para.nFlags & MEPF_ROWEND)
581 ME_Cursor cursor;
582 ME_DisplayItem *para;
583 /* We are just after a table row. */
584 RTFFlushOutputBuffer(info);
585 cursor = info->editor->pCursors[0];
586 para = cursor.pPara;
587 if (para == info->tableDef->tableRowStart->member.para.next_para
588 && !cursor.nOffset && !cursor.pRun->member.run.nCharOfs)
590 /* Since the table row end, no text has been inserted, and the \intbl
591 * control word has not be used. We can confirm that we are not in a
592 * table anymore.
594 info->tableDef->tableRowStart = NULL;
595 info->canInheritInTbl = FALSE;
598 } else { /* v1.0 - v3.0 */
599 info->fmt.dwMask |= PFM_TABLE;
600 info->fmt.wEffects &= ~PFE_TABLE;
602 break;
603 case rtfNestLevel:
604 if (!info->editor->bEmulateVersion10) /* v4.1 */
606 while (info->rtfParam > info->nestingLevel) {
607 RTFTable *tableDef = heap_alloc_zero(sizeof(*tableDef));
608 tableDef->parent = info->tableDef;
609 info->tableDef = tableDef;
611 RTFFlushOutputBuffer(info);
612 if (tableDef->tableRowStart &&
613 tableDef->tableRowStart->member.para.nFlags & MEPF_ROWEND)
615 ME_DisplayItem *para = tableDef->tableRowStart;
616 para = para->member.para.next_para;
617 para = ME_InsertTableRowStartAtParagraph(info->editor, para);
618 tableDef->tableRowStart = para;
619 } else {
620 ME_Cursor cursor;
621 WCHAR endl = '\r';
622 cursor = info->editor->pCursors[0];
623 if (cursor.nOffset || cursor.pRun->member.run.nCharOfs)
624 ME_InsertTextFromCursor(info->editor, 0, &endl, 1, info->style);
625 tableDef->tableRowStart = ME_InsertTableRowStartFromCursor(info->editor);
628 info->nestingLevel++;
630 info->canInheritInTbl = FALSE;
632 break;
633 case rtfInTable:
635 if (!info->editor->bEmulateVersion10) /* v4.1 */
637 if (info->nestingLevel < 1)
639 RTFTable *tableDef;
640 if (!info->tableDef)
641 info->tableDef = heap_alloc_zero(sizeof(*info->tableDef));
642 tableDef = info->tableDef;
643 RTFFlushOutputBuffer(info);
644 if (tableDef->tableRowStart &&
645 tableDef->tableRowStart->member.para.nFlags & MEPF_ROWEND)
647 ME_DisplayItem *para = tableDef->tableRowStart;
648 para = para->member.para.next_para;
649 para = ME_InsertTableRowStartAtParagraph(info->editor, para);
650 tableDef->tableRowStart = para;
651 } else {
652 ME_Cursor cursor;
653 WCHAR endl = '\r';
654 cursor = info->editor->pCursors[0];
655 if (cursor.nOffset || cursor.pRun->member.run.nCharOfs)
656 ME_InsertTextFromCursor(info->editor, 0, &endl, 1, info->style);
657 tableDef->tableRowStart = ME_InsertTableRowStartFromCursor(info->editor);
659 info->nestingLevel = 1;
660 info->canInheritInTbl = TRUE;
662 return;
663 } else { /* v1.0 - v3.0 */
664 info->fmt.dwMask |= PFM_TABLE;
665 info->fmt.wEffects |= PFE_TABLE;
667 break;
669 case rtfFirstIndent:
670 case rtfLeftIndent:
671 if ((info->fmt.dwMask & (PFM_STARTINDENT | PFM_OFFSET)) != (PFM_STARTINDENT | PFM_OFFSET))
673 PARAFORMAT2 fmt;
674 fmt.cbSize = sizeof(fmt);
675 ME_GetSelectionParaFormat(info->editor, &fmt);
676 info->fmt.dwMask |= PFM_STARTINDENT | PFM_OFFSET;
677 info->fmt.dxStartIndent = fmt.dxStartIndent;
678 info->fmt.dxOffset = fmt.dxOffset;
680 if (info->rtfMinor == rtfFirstIndent)
682 info->fmt.dxStartIndent += info->fmt.dxOffset + info->rtfParam;
683 info->fmt.dxOffset = -info->rtfParam;
685 else
686 info->fmt.dxStartIndent = info->rtfParam - info->fmt.dxOffset;
687 break;
688 case rtfRightIndent:
689 info->fmt.dwMask |= PFM_RIGHTINDENT;
690 info->fmt.dxRightIndent = info->rtfParam;
691 break;
692 case rtfQuadLeft:
693 case rtfQuadJust:
694 info->fmt.dwMask |= PFM_ALIGNMENT;
695 info->fmt.wAlignment = PFA_LEFT;
696 break;
697 case rtfQuadRight:
698 info->fmt.dwMask |= PFM_ALIGNMENT;
699 info->fmt.wAlignment = PFA_RIGHT;
700 break;
701 case rtfQuadCenter:
702 info->fmt.dwMask |= PFM_ALIGNMENT;
703 info->fmt.wAlignment = PFA_CENTER;
704 break;
705 case rtfTabPos:
706 if (!(info->fmt.dwMask & PFM_TABSTOPS))
708 PARAFORMAT2 fmt;
709 fmt.cbSize = sizeof(fmt);
710 ME_GetSelectionParaFormat(info->editor, &fmt);
711 memcpy(info->fmt.rgxTabs, fmt.rgxTabs,
712 fmt.cTabCount * sizeof(fmt.rgxTabs[0]));
713 info->fmt.cTabCount = fmt.cTabCount;
714 info->fmt.dwMask |= PFM_TABSTOPS;
716 if (info->fmt.cTabCount < MAX_TAB_STOPS && info->rtfParam < 0x1000000)
717 info->fmt.rgxTabs[info->fmt.cTabCount++] = info->rtfParam;
718 break;
719 case rtfKeep:
720 info->fmt.dwMask |= PFM_KEEP;
721 info->fmt.wEffects |= PFE_KEEP;
722 break;
723 case rtfNoWidowControl:
724 info->fmt.dwMask |= PFM_NOWIDOWCONTROL;
725 info->fmt.wEffects |= PFE_NOWIDOWCONTROL;
726 break;
727 case rtfKeepNext:
728 info->fmt.dwMask |= PFM_KEEPNEXT;
729 info->fmt.wEffects |= PFE_KEEPNEXT;
730 break;
731 case rtfSpaceAfter:
732 info->fmt.dwMask |= PFM_SPACEAFTER;
733 info->fmt.dySpaceAfter = info->rtfParam;
734 break;
735 case rtfSpaceBefore:
736 info->fmt.dwMask |= PFM_SPACEBEFORE;
737 info->fmt.dySpaceBefore = info->rtfParam;
738 break;
739 case rtfSpaceBetween:
740 info->fmt.dwMask |= PFM_LINESPACING;
741 if ((int)info->rtfParam > 0)
743 info->fmt.dyLineSpacing = info->rtfParam;
744 info->fmt.bLineSpacingRule = 3;
746 else
748 info->fmt.dyLineSpacing = info->rtfParam;
749 info->fmt.bLineSpacingRule = 4;
751 break;
752 case rtfSpaceMultiply:
753 info->fmt.dwMask |= PFM_LINESPACING;
754 info->fmt.dyLineSpacing = info->rtfParam * 20;
755 info->fmt.bLineSpacingRule = 5;
756 break;
757 case rtfParBullet:
758 info->fmt.dwMask |= PFM_NUMBERING;
759 info->fmt.wNumbering = PFN_BULLET;
760 break;
761 case rtfParSimple:
762 info->fmt.dwMask |= PFM_NUMBERING;
763 info->fmt.wNumbering = 2; /* FIXME: MSDN says it's not used ?? */
764 break;
765 case rtfBorderLeft:
766 info->borderType = RTFBorderParaLeft;
767 info->fmt.wBorders |= 1;
768 info->fmt.dwMask |= PFM_BORDER;
769 break;
770 case rtfBorderRight:
771 info->borderType = RTFBorderParaRight;
772 info->fmt.wBorders |= 2;
773 info->fmt.dwMask |= PFM_BORDER;
774 break;
775 case rtfBorderTop:
776 info->borderType = RTFBorderParaTop;
777 info->fmt.wBorders |= 4;
778 info->fmt.dwMask |= PFM_BORDER;
779 break;
780 case rtfBorderBottom:
781 info->borderType = RTFBorderParaBottom;
782 info->fmt.wBorders |= 8;
783 info->fmt.dwMask |= PFM_BORDER;
784 break;
785 case rtfBorderSingle:
786 info->fmt.wBorders &= ~0x700;
787 info->fmt.wBorders |= 1 << 8;
788 info->fmt.dwMask |= PFM_BORDER;
789 break;
790 case rtfBorderThick:
791 info->fmt.wBorders &= ~0x700;
792 info->fmt.wBorders |= 2 << 8;
793 info->fmt.dwMask |= PFM_BORDER;
794 break;
795 case rtfBorderShadow:
796 info->fmt.wBorders &= ~0x700;
797 info->fmt.wBorders |= 10 << 8;
798 info->fmt.dwMask |= PFM_BORDER;
799 break;
800 case rtfBorderDouble:
801 info->fmt.wBorders &= ~0x700;
802 info->fmt.wBorders |= 7 << 8;
803 info->fmt.dwMask |= PFM_BORDER;
804 break;
805 case rtfBorderDot:
806 info->fmt.wBorders &= ~0x700;
807 info->fmt.wBorders |= 11 << 8;
808 info->fmt.dwMask |= PFM_BORDER;
809 break;
810 case rtfBorderWidth:
812 int borderSide = info->borderType & RTFBorderSideMask;
813 RTFTable *tableDef = info->tableDef;
814 if ((info->borderType & RTFBorderTypeMask) == RTFBorderTypeCell)
816 RTFBorder *border;
817 if (!tableDef || tableDef->numCellsDefined >= MAX_TABLE_CELLS)
818 break;
819 border = &tableDef->cells[tableDef->numCellsDefined].border[borderSide];
820 border->width = info->rtfParam;
821 break;
823 info->fmt.wBorderWidth = info->rtfParam;
824 info->fmt.dwMask |= PFM_BORDER;
825 break;
827 case rtfBorderSpace:
828 info->fmt.wBorderSpace = info->rtfParam;
829 info->fmt.dwMask |= PFM_BORDER;
830 break;
831 case rtfBorderColor:
833 RTFTable *tableDef = info->tableDef;
834 int borderSide = info->borderType & RTFBorderSideMask;
835 int borderType = info->borderType & RTFBorderTypeMask;
836 switch(borderType)
838 case RTFBorderTypePara:
839 if (!info->editor->bEmulateVersion10) /* v4.1 */
840 break;
841 /* v1.0 - 3.0 treat paragraph and row borders the same. */
842 case RTFBorderTypeRow:
843 if (tableDef) {
844 tableDef->border[borderSide].color = info->rtfParam;
846 break;
847 case RTFBorderTypeCell:
848 if (tableDef && tableDef->numCellsDefined < MAX_TABLE_CELLS) {
849 tableDef->cells[tableDef->numCellsDefined].border[borderSide].color = info->rtfParam;
851 break;
853 break;
855 case rtfRTLPar:
856 info->fmt.dwMask |= PFM_RTLPARA;
857 info->fmt.wEffects |= PFE_RTLPARA;
858 break;
859 case rtfLTRPar:
860 info->fmt.dwMask |= PFM_RTLPARA;
861 info->fmt.wEffects &= ~PFE_RTLPARA;
862 break;
866 void ME_RTFTblAttrHook(RTF_Info *info)
868 switch (info->rtfMinor)
870 case rtfRowDef:
872 if (!info->editor->bEmulateVersion10) /* v4.1 */
873 info->borderType = 0; /* Not sure */
874 else /* v1.0 - 3.0 */
875 info->borderType = RTFBorderRowTop;
876 if (!info->tableDef) {
877 info->tableDef = ME_MakeTableDef(info->editor);
878 } else {
879 ME_InitTableDef(info->editor, info->tableDef);
881 break;
883 case rtfCellPos:
885 int cellNum;
886 if (!info->tableDef)
888 info->tableDef = ME_MakeTableDef(info->editor);
890 cellNum = info->tableDef->numCellsDefined;
891 if (cellNum >= MAX_TABLE_CELLS)
892 break;
893 info->tableDef->cells[cellNum].rightBoundary = info->rtfParam;
894 if (cellNum < MAX_TAB_STOPS) {
895 /* Tab stops were used to store cell positions before v4.1 but v4.1
896 * still seems to set the tabstops without using them. */
897 ME_DisplayItem *para = info->editor->pCursors[0].pPara;
898 PARAFORMAT2 *pFmt = &para->member.para.fmt;
899 pFmt->rgxTabs[cellNum] &= ~0x00FFFFFF;
900 pFmt->rgxTabs[cellNum] |= 0x00FFFFFF & info->rtfParam;
902 info->tableDef->numCellsDefined++;
903 break;
905 case rtfRowBordTop:
906 info->borderType = RTFBorderRowTop;
907 break;
908 case rtfRowBordLeft:
909 info->borderType = RTFBorderRowLeft;
910 break;
911 case rtfRowBordBottom:
912 info->borderType = RTFBorderRowBottom;
913 break;
914 case rtfRowBordRight:
915 info->borderType = RTFBorderRowRight;
916 break;
917 case rtfCellBordTop:
918 info->borderType = RTFBorderCellTop;
919 break;
920 case rtfCellBordLeft:
921 info->borderType = RTFBorderCellLeft;
922 break;
923 case rtfCellBordBottom:
924 info->borderType = RTFBorderCellBottom;
925 break;
926 case rtfCellBordRight:
927 info->borderType = RTFBorderCellRight;
928 break;
929 case rtfRowGapH:
930 if (info->tableDef)
931 info->tableDef->gapH = info->rtfParam;
932 break;
933 case rtfRowLeftEdge:
934 if (info->tableDef)
935 info->tableDef->leftEdge = info->rtfParam;
936 break;
940 void ME_RTFSpecialCharHook(RTF_Info *info)
942 RTFTable *tableDef = info->tableDef;
943 switch (info->rtfMinor)
945 case rtfNestCell:
946 if (info->editor->bEmulateVersion10) /* v1.0 - v3.0 */
947 break;
948 /* else fall through since v4.1 treats rtfNestCell and rtfCell the same */
949 case rtfCell:
950 if (!tableDef)
951 break;
952 RTFFlushOutputBuffer(info);
953 if (!info->editor->bEmulateVersion10) { /* v4.1 */
954 if (tableDef->tableRowStart)
956 if (!info->nestingLevel &&
957 tableDef->tableRowStart->member.para.nFlags & MEPF_ROWEND)
959 ME_DisplayItem *para = tableDef->tableRowStart;
960 para = para->member.para.next_para;
961 para = ME_InsertTableRowStartAtParagraph(info->editor, para);
962 tableDef->tableRowStart = para;
963 info->nestingLevel = 1;
965 ME_InsertTableCellFromCursor(info->editor);
967 } else { /* v1.0 - v3.0 */
968 ME_DisplayItem *para = info->editor->pCursors[0].pPara;
969 PARAFORMAT2 *pFmt = &para->member.para.fmt;
970 if (pFmt->dwMask & PFM_TABLE && pFmt->wEffects & PFE_TABLE &&
971 tableDef->numCellsInserted < tableDef->numCellsDefined)
973 WCHAR tab = '\t';
974 ME_InsertTextFromCursor(info->editor, 0, &tab, 1, info->style);
975 tableDef->numCellsInserted++;
978 break;
979 case rtfNestRow:
980 if (info->editor->bEmulateVersion10) /* v1.0 - v3.0 */
981 break;
982 /* else fall through since v4.1 treats rtfNestRow and rtfRow the same */
983 case rtfRow:
985 ME_DisplayItem *para, *cell, *run;
986 int i;
988 if (!tableDef)
989 break;
990 RTFFlushOutputBuffer(info);
991 if (!info->editor->bEmulateVersion10) { /* v4.1 */
992 if (!tableDef->tableRowStart)
993 break;
994 if (!info->nestingLevel &&
995 tableDef->tableRowStart->member.para.nFlags & MEPF_ROWEND)
997 para = tableDef->tableRowStart;
998 para = para->member.para.next_para;
999 para = ME_InsertTableRowStartAtParagraph(info->editor, para);
1000 tableDef->tableRowStart = para;
1001 info->nestingLevel++;
1003 para = tableDef->tableRowStart;
1004 cell = ME_FindItemFwd(para, diCell);
1005 assert(cell && !cell->member.cell.prev_cell);
1006 if (tableDef->numCellsDefined < 1)
1008 /* 2000 twips appears to be the cell size that native richedit uses
1009 * when no cell sizes are specified. */
1010 const int defaultCellSize = 2000;
1011 int nRightBoundary = defaultCellSize;
1012 cell->member.cell.nRightBoundary = nRightBoundary;
1013 while (cell->member.cell.next_cell) {
1014 cell = cell->member.cell.next_cell;
1015 nRightBoundary += defaultCellSize;
1016 cell->member.cell.nRightBoundary = nRightBoundary;
1018 para = ME_InsertTableCellFromCursor(info->editor);
1019 cell = para->member.para.pCell;
1020 cell->member.cell.nRightBoundary = nRightBoundary;
1021 } else {
1022 for (i = 0; i < tableDef->numCellsDefined; i++)
1024 RTFCell *cellDef = &tableDef->cells[i];
1025 cell->member.cell.nRightBoundary = cellDef->rightBoundary;
1026 ME_ApplyBorderProperties(info, &cell->member.cell.border,
1027 cellDef->border);
1028 cell = cell->member.cell.next_cell;
1029 if (!cell)
1031 para = ME_InsertTableCellFromCursor(info->editor);
1032 cell = para->member.para.pCell;
1035 /* Cell for table row delimiter is empty */
1036 cell->member.cell.nRightBoundary = tableDef->cells[i-1].rightBoundary;
1039 run = ME_FindItemFwd(cell, diRun);
1040 if (info->editor->pCursors[0].pRun != run ||
1041 info->editor->pCursors[0].nOffset)
1043 int nOfs, nChars;
1044 /* Delete inserted cells that aren't defined. */
1045 info->editor->pCursors[1].pRun = run;
1046 info->editor->pCursors[1].pPara = ME_GetParagraph(run);
1047 info->editor->pCursors[1].nOffset = 0;
1048 nOfs = ME_GetCursorOfs(&info->editor->pCursors[1]);
1049 nChars = ME_GetCursorOfs(&info->editor->pCursors[0]) - nOfs;
1050 ME_InternalDeleteText(info->editor, &info->editor->pCursors[1],
1051 nChars, TRUE);
1054 para = ME_InsertTableRowEndFromCursor(info->editor);
1055 para->member.para.fmt.dxOffset = abs(info->tableDef->gapH);
1056 para->member.para.fmt.dxStartIndent = info->tableDef->leftEdge;
1057 ME_ApplyBorderProperties(info, &para->member.para.border,
1058 tableDef->border);
1059 info->nestingLevel--;
1060 if (!info->nestingLevel)
1062 if (info->canInheritInTbl) {
1063 tableDef->tableRowStart = para;
1064 } else {
1065 while (info->tableDef) {
1066 tableDef = info->tableDef;
1067 info->tableDef = tableDef->parent;
1068 heap_free(tableDef);
1071 } else {
1072 info->tableDef = tableDef->parent;
1073 heap_free(tableDef);
1075 } else { /* v1.0 - v3.0 */
1076 WCHAR endl = '\r';
1077 ME_DisplayItem *para = info->editor->pCursors[0].pPara;
1078 PARAFORMAT2 *pFmt = &para->member.para.fmt;
1079 pFmt->dxOffset = info->tableDef->gapH;
1080 pFmt->dxStartIndent = info->tableDef->leftEdge;
1082 ME_ApplyBorderProperties(info, &para->member.para.border,
1083 tableDef->border);
1084 while (tableDef->numCellsInserted < tableDef->numCellsDefined)
1086 WCHAR tab = '\t';
1087 ME_InsertTextFromCursor(info->editor, 0, &tab, 1, info->style);
1088 tableDef->numCellsInserted++;
1090 pFmt->cTabCount = min(tableDef->numCellsDefined, MAX_TAB_STOPS);
1091 if (!tableDef->numCellsDefined)
1092 pFmt->wEffects &= ~PFE_TABLE;
1093 ME_InsertTextFromCursor(info->editor, 0, &endl, 1, info->style);
1094 tableDef->numCellsInserted = 0;
1096 break;
1098 case rtfTab:
1099 case rtfPar:
1100 if (info->editor->bEmulateVersion10) { /* v1.0 - 3.0 */
1101 ME_DisplayItem *para;
1102 PARAFORMAT2 *pFmt;
1103 RTFFlushOutputBuffer(info);
1104 para = info->editor->pCursors[0].pPara;
1105 pFmt = &para->member.para.fmt;
1106 if (pFmt->dwMask & PFM_TABLE && pFmt->wEffects & PFE_TABLE)
1108 /* rtfPar is treated like a space within a table. */
1109 info->rtfClass = rtfText;
1110 info->rtfMajor = ' ';
1112 else if (info->rtfMinor == rtfPar && tableDef)
1113 tableDef->numCellsInserted = 0;
1115 break;
1119 static HRESULT insert_static_object(ME_TextEditor *editor, HENHMETAFILE hemf, HBITMAP hbmp,
1120 const SIZEL* sz)
1122 LPOLEOBJECT lpObject = NULL;
1123 LPSTORAGE lpStorage = NULL;
1124 LPOLECLIENTSITE lpClientSite = NULL;
1125 LPDATAOBJECT lpDataObject = NULL;
1126 LPOLECACHE lpOleCache = NULL;
1127 LPRICHEDITOLE lpReOle = NULL;
1128 STGMEDIUM stgm;
1129 FORMATETC fm;
1130 CLSID clsid;
1131 HRESULT hr = E_FAIL;
1132 DWORD conn;
1134 if (hemf)
1136 stgm.tymed = TYMED_ENHMF;
1137 stgm.u.hEnhMetaFile = hemf;
1138 fm.cfFormat = CF_ENHMETAFILE;
1140 else if (hbmp)
1142 stgm.tymed = TYMED_GDI;
1143 stgm.u.hBitmap = hbmp;
1144 fm.cfFormat = CF_BITMAP;
1146 stgm.pUnkForRelease = NULL;
1148 fm.ptd = NULL;
1149 fm.dwAspect = DVASPECT_CONTENT;
1150 fm.lindex = -1;
1151 fm.tymed = stgm.tymed;
1153 if (!editor->reOle)
1155 if (!CreateIRichEditOle(NULL, editor, (LPVOID *)&editor->reOle))
1156 return hr;
1159 if (OleCreateDefaultHandler(&CLSID_NULL, NULL, &IID_IOleObject, (void**)&lpObject) == S_OK &&
1160 IUnknown_QueryInterface(editor->reOle, &IID_IRichEditOle, (void**)&lpReOle) == S_OK &&
1161 IRichEditOle_GetClientSite(lpReOle, &lpClientSite) == S_OK &&
1162 IOleObject_SetClientSite(lpObject, lpClientSite) == S_OK &&
1163 IOleObject_GetUserClassID(lpObject, &clsid) == S_OK &&
1164 IOleObject_QueryInterface(lpObject, &IID_IOleCache, (void**)&lpOleCache) == S_OK &&
1165 IOleCache_Cache(lpOleCache, &fm, 0, &conn) == S_OK &&
1166 IOleObject_QueryInterface(lpObject, &IID_IDataObject, (void**)&lpDataObject) == S_OK &&
1167 IDataObject_SetData(lpDataObject, &fm, &stgm, TRUE) == S_OK)
1169 REOBJECT reobject;
1171 reobject.cbStruct = sizeof(reobject);
1172 reobject.cp = REO_CP_SELECTION;
1173 reobject.clsid = clsid;
1174 reobject.poleobj = lpObject;
1175 reobject.pstg = lpStorage;
1176 reobject.polesite = lpClientSite;
1177 /* convert from twips to .01 mm */
1178 reobject.sizel.cx = MulDiv(sz->cx, 254, 144);
1179 reobject.sizel.cy = MulDiv(sz->cy, 254, 144);
1180 reobject.dvaspect = DVASPECT_CONTENT;
1181 reobject.dwFlags = 0; /* FIXME */
1182 reobject.dwUser = 0;
1184 ME_InsertOLEFromCursor(editor, &reobject, 0);
1185 hr = S_OK;
1188 if (lpObject) IOleObject_Release(lpObject);
1189 if (lpClientSite) IOleClientSite_Release(lpClientSite);
1190 if (lpStorage) IStorage_Release(lpStorage);
1191 if (lpDataObject) IDataObject_Release(lpDataObject);
1192 if (lpOleCache) IOleCache_Release(lpOleCache);
1193 if (lpReOle) IRichEditOle_Release(lpReOle);
1195 return hr;
1198 static void ME_RTFReadShpPictGroup( RTF_Info *info )
1200 int level = 1;
1202 for (;;)
1204 RTFGetToken (info);
1206 if (info->rtfClass == rtfEOF) return;
1207 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1209 if (--level == 0) break;
1211 else if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1213 level++;
1215 else
1217 RTFRouteToken( info );
1218 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1219 level--;
1223 RTFRouteToken( info ); /* feed "}" back to router */
1224 return;
1227 static DWORD read_hex_data( RTF_Info *info, BYTE **out )
1229 DWORD read = 0, size = 1024;
1230 BYTE *buf, val;
1231 BOOL flip;
1233 *out = NULL;
1235 if (info->rtfClass != rtfText)
1237 ERR("Called with incorrect token\n");
1238 return 0;
1241 buf = HeapAlloc( GetProcessHeap(), 0, size );
1242 if (!buf) return 0;
1244 val = info->rtfMajor;
1245 for (flip = TRUE;; flip = !flip)
1247 RTFGetToken( info );
1248 if (info->rtfClass == rtfEOF)
1250 HeapFree( GetProcessHeap(), 0, buf );
1251 return 0;
1253 if (info->rtfClass != rtfText) break;
1254 if (flip)
1256 if (read >= size)
1258 size *= 2;
1259 buf = HeapReAlloc( GetProcessHeap(), 0, buf, size );
1260 if (!buf) return 0;
1262 buf[read++] = RTFCharToHex(val) * 16 + RTFCharToHex(info->rtfMajor);
1264 else
1265 val = info->rtfMajor;
1267 if (flip) FIXME("wrong hex string\n");
1269 *out = buf;
1270 return read;
1273 static void ME_RTFReadPictGroup(RTF_Info *info)
1275 SIZEL sz;
1276 BYTE *buffer = NULL;
1277 DWORD size = 0;
1278 METAFILEPICT mfp;
1279 HENHMETAFILE hemf;
1280 HBITMAP hbmp;
1281 enum gfxkind {gfx_unknown = 0, gfx_enhmetafile, gfx_metafile, gfx_dib} gfx = gfx_unknown;
1282 int level = 1;
1284 mfp.mm = MM_TEXT;
1285 sz.cx = sz.cy = 0;
1287 for (;;)
1289 RTFGetToken( info );
1291 if (info->rtfClass == rtfText)
1293 if (level == 1)
1295 if (!buffer)
1296 size = read_hex_data( info, &buffer );
1298 else
1300 RTFSkipGroup( info );
1302 } /* We potentially have a new token so fall through. */
1304 if (info->rtfClass == rtfEOF) return;
1306 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1308 if (--level == 0) break;
1309 continue;
1311 if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1313 level++;
1314 continue;
1316 if (!RTFCheckCM( info, rtfControl, rtfPictAttr ))
1318 RTFRouteToken( info );
1319 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1320 level--;
1321 continue;
1324 if (RTFCheckMM( info, rtfPictAttr, rtfWinMetafile ))
1326 mfp.mm = info->rtfParam;
1327 gfx = gfx_metafile;
1329 else if (RTFCheckMM( info, rtfPictAttr, rtfDevIndBitmap ))
1331 if (info->rtfParam != 0) FIXME("dibitmap should be 0 (%d)\n", info->rtfParam);
1332 gfx = gfx_dib;
1334 else if (RTFCheckMM( info, rtfPictAttr, rtfEmfBlip ))
1335 gfx = gfx_enhmetafile;
1336 else if (RTFCheckMM( info, rtfPictAttr, rtfPicWid ))
1337 mfp.xExt = info->rtfParam;
1338 else if (RTFCheckMM( info, rtfPictAttr, rtfPicHt ))
1339 mfp.yExt = info->rtfParam;
1340 else if (RTFCheckMM( info, rtfPictAttr, rtfPicGoalWid ))
1341 sz.cx = info->rtfParam;
1342 else if (RTFCheckMM( info, rtfPictAttr, rtfPicGoalHt ))
1343 sz.cy = info->rtfParam;
1344 else
1345 FIXME("Non supported attribute: %d %d %d\n", info->rtfClass, info->rtfMajor, info->rtfMinor);
1348 if (buffer)
1350 switch (gfx)
1352 case gfx_enhmetafile:
1353 if ((hemf = SetEnhMetaFileBits( size, buffer )))
1354 insert_static_object( info->editor, hemf, NULL, &sz );
1355 break;
1356 case gfx_metafile:
1357 if ((hemf = SetWinMetaFileBits( size, buffer, NULL, &mfp )))
1358 insert_static_object( info->editor, hemf, NULL, &sz );
1359 break;
1360 case gfx_dib:
1362 BITMAPINFO *bi = (BITMAPINFO*)buffer;
1363 HDC hdc = GetDC(0);
1364 unsigned nc = bi->bmiHeader.biClrUsed;
1366 /* not quite right, especially for bitfields type of compression */
1367 if (!nc && bi->bmiHeader.biBitCount <= 8)
1368 nc = 1 << bi->bmiHeader.biBitCount;
1369 if ((hbmp = CreateDIBitmap( hdc, &bi->bmiHeader,
1370 CBM_INIT, (char*)(bi + 1) + nc * sizeof(RGBQUAD),
1371 bi, DIB_RGB_COLORS)) )
1372 insert_static_object( info->editor, NULL, hbmp, &sz );
1373 ReleaseDC( 0, hdc );
1374 break;
1376 default:
1377 break;
1380 HeapFree( GetProcessHeap(), 0, buffer );
1381 RTFRouteToken( info ); /* feed "}" back to router */
1382 return;
1385 /* for now, lookup the \result part and use it, whatever the object */
1386 static void ME_RTFReadObjectGroup(RTF_Info *info)
1388 for (;;)
1390 RTFGetToken (info);
1391 if (info->rtfClass == rtfEOF)
1392 return;
1393 if (RTFCheckCM(info, rtfGroup, rtfEndGroup))
1394 break;
1395 if (RTFCheckCM(info, rtfGroup, rtfBeginGroup))
1397 RTFGetToken (info);
1398 if (info->rtfClass == rtfEOF)
1399 return;
1400 if (RTFCheckCMM(info, rtfControl, rtfDestination, rtfObjResult))
1402 int level = 1;
1404 while (RTFGetToken (info) != rtfEOF)
1406 if (info->rtfClass == rtfGroup)
1408 if (info->rtfMajor == rtfBeginGroup) level++;
1409 else if (info->rtfMajor == rtfEndGroup && --level < 0) break;
1411 RTFRouteToken(info);
1414 else RTFSkipGroup(info);
1415 continue;
1417 if (!RTFCheckCM (info, rtfControl, rtfObjAttr))
1419 FIXME("Non supported attribute: %d %d %d\n", info->rtfClass, info->rtfMajor, info->rtfMinor);
1420 return;
1423 RTFRouteToken(info); /* feed "}" back to router */
1426 static void ME_RTFReadParnumGroup( RTF_Info *info )
1428 int level = 1, type = -1;
1429 WORD indent = 0, start = 1;
1430 WCHAR txt_before = 0, txt_after = 0;
1432 for (;;)
1434 RTFGetToken( info );
1436 if (RTFCheckCMM( info, rtfControl, rtfDestination, rtfParNumTextBefore ) ||
1437 RTFCheckCMM( info, rtfControl, rtfDestination, rtfParNumTextAfter ))
1439 int loc = info->rtfMinor;
1441 RTFGetToken( info );
1442 if (info->rtfClass == rtfText)
1444 if (loc == rtfParNumTextBefore)
1445 txt_before = info->rtfMajor;
1446 else
1447 txt_after = info->rtfMajor;
1448 continue;
1450 /* falling through to catch EOFs and group level changes */
1453 if (info->rtfClass == rtfEOF)
1454 return;
1456 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1458 if (--level == 0) break;
1459 continue;
1462 if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1464 level++;
1465 continue;
1468 /* Ignore non para-attr */
1469 if (!RTFCheckCM( info, rtfControl, rtfParAttr ))
1470 continue;
1472 switch (info->rtfMinor)
1474 case rtfParLevel: /* Para level is ignored */
1475 case rtfParSimple:
1476 break;
1477 case rtfParBullet:
1478 type = PFN_BULLET;
1479 break;
1481 case rtfParNumDecimal:
1482 type = PFN_ARABIC;
1483 break;
1484 case rtfParNumULetter:
1485 type = PFN_UCLETTER;
1486 break;
1487 case rtfParNumURoman:
1488 type = PFN_UCROMAN;
1489 break;
1490 case rtfParNumLLetter:
1491 type = PFN_LCLETTER;
1492 break;
1493 case rtfParNumLRoman:
1494 type = PFN_LCROMAN;
1495 break;
1497 case rtfParNumIndent:
1498 indent = info->rtfParam;
1499 break;
1500 case rtfParNumStartAt:
1501 start = info->rtfParam;
1502 break;
1506 if (type != -1)
1508 info->fmt.dwMask |= (PFM_NUMBERING | PFM_NUMBERINGSTART | PFM_NUMBERINGSTYLE | PFM_NUMBERINGTAB);
1509 info->fmt.wNumbering = type;
1510 info->fmt.wNumberingStart = start;
1511 info->fmt.wNumberingStyle = PFNS_PAREN;
1512 if (type != PFN_BULLET)
1514 if (txt_before == 0 && txt_after == 0)
1515 info->fmt.wNumberingStyle = PFNS_PLAIN;
1516 else if (txt_after == '.')
1517 info->fmt.wNumberingStyle = PFNS_PERIOD;
1518 else if (txt_before == '(' && txt_after == ')')
1519 info->fmt.wNumberingStyle = PFNS_PARENS;
1521 info->fmt.wNumberingTab = indent;
1524 TRACE("type %d indent %d start %d txt before %04x txt after %04x\n",
1525 type, indent, start, txt_before, txt_after);
1527 RTFRouteToken( info ); /* feed "}" back to router */
1530 static void ME_RTFReadHook(RTF_Info *info)
1532 switch(info->rtfClass)
1534 case rtfGroup:
1535 switch(info->rtfMajor)
1537 case rtfBeginGroup:
1538 if (info->stackTop < maxStack) {
1539 info->stack[info->stackTop].style = info->style;
1540 ME_AddRefStyle(info->style);
1541 info->stack[info->stackTop].codePage = info->codePage;
1542 info->stack[info->stackTop].unicodeLength = info->unicodeLength;
1544 info->stackTop++;
1545 info->styleChanged = FALSE;
1546 break;
1547 case rtfEndGroup:
1549 RTFFlushOutputBuffer(info);
1550 info->stackTop--;
1551 if (info->stackTop <= 0)
1552 info->rtfClass = rtfEOF;
1553 if (info->stackTop < 0)
1554 return;
1556 ME_ReleaseStyle(info->style);
1557 info->style = info->stack[info->stackTop].style;
1558 info->codePage = info->stack[info->stackTop].codePage;
1559 info->unicodeLength = info->stack[info->stackTop].unicodeLength;
1560 break;
1563 break;
1567 void
1568 ME_StreamInFill(ME_InStream *stream)
1570 stream->editstream->dwError = stream->editstream->pfnCallback(stream->editstream->dwCookie,
1571 (BYTE *)stream->buffer,
1572 sizeof(stream->buffer),
1573 (LONG *)&stream->dwSize);
1574 stream->dwUsed = 0;
1577 static LRESULT ME_StreamIn(ME_TextEditor *editor, DWORD format, EDITSTREAM *stream, BOOL stripLastCR)
1579 RTF_Info parser;
1580 ME_Style *style;
1581 int from, to, nUndoMode;
1582 int nEventMask = editor->nEventMask;
1583 ME_InStream inStream;
1584 BOOL invalidRTF = FALSE;
1585 ME_Cursor *selStart, *selEnd;
1586 LRESULT num_read = 0; /* bytes read for SF_TEXT, non-control chars inserted for SF_RTF */
1588 TRACE("stream==%p editor==%p format==0x%X\n", stream, editor, format);
1589 editor->nEventMask = 0;
1591 ME_GetSelectionOfs(editor, &from, &to);
1592 if (format & SFF_SELECTION && editor->mode & TM_RICHTEXT)
1594 ME_GetSelection(editor, &selStart, &selEnd);
1595 style = ME_GetSelectionInsertStyle(editor);
1597 ME_InternalDeleteText(editor, selStart, to - from, FALSE);
1599 /* Don't insert text at the end of the table row */
1600 if (!editor->bEmulateVersion10) { /* v4.1 */
1601 ME_DisplayItem *para = editor->pCursors->pPara;
1602 if (para->member.para.nFlags & MEPF_ROWEND)
1604 para = para->member.para.next_para;
1605 editor->pCursors[0].pPara = para;
1606 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
1607 editor->pCursors[0].nOffset = 0;
1609 if (para->member.para.nFlags & MEPF_ROWSTART)
1611 para = para->member.para.next_para;
1612 editor->pCursors[0].pPara = para;
1613 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
1614 editor->pCursors[0].nOffset = 0;
1616 editor->pCursors[1] = editor->pCursors[0];
1617 } else { /* v1.0 - 3.0 */
1618 if (editor->pCursors[0].pRun->member.run.nFlags & MERF_ENDPARA &&
1619 ME_IsInTable(editor->pCursors[0].pRun))
1620 return 0;
1622 } else {
1623 style = editor->pBuffer->pDefaultStyle;
1624 ME_AddRefStyle(style);
1625 set_selection_cursors(editor, 0, 0);
1626 ME_InternalDeleteText(editor, &editor->pCursors[1],
1627 ME_GetTextLength(editor), FALSE);
1628 from = to = 0;
1629 ME_ClearTempStyle(editor);
1630 ME_SetDefaultParaFormat(editor, &editor->pCursors[0].pPara->member.para.fmt);
1634 /* Back up undo mode to a local variable */
1635 nUndoMode = editor->nUndoMode;
1637 /* Only create an undo if SFF_SELECTION is set */
1638 if (!(format & SFF_SELECTION))
1639 editor->nUndoMode = umIgnore;
1641 inStream.editstream = stream;
1642 inStream.editstream->dwError = 0;
1643 inStream.dwSize = 0;
1644 inStream.dwUsed = 0;
1646 if (format & SF_RTF)
1648 /* Check if it's really RTF, and if it is not, use plain text */
1649 ME_StreamInFill(&inStream);
1650 if (!inStream.editstream->dwError)
1652 if ((!editor->bEmulateVersion10 && strncmp(inStream.buffer, "{\\rtf", 5) && strncmp(inStream.buffer, "{\\urtf", 6))
1653 || (editor->bEmulateVersion10 && *inStream.buffer != '{'))
1655 invalidRTF = TRUE;
1656 inStream.editstream->dwError = -16;
1661 if (!invalidRTF && !inStream.editstream->dwError)
1663 ME_Cursor start;
1664 from = ME_GetCursorOfs(&editor->pCursors[0]);
1665 if (format & SF_RTF) {
1667 /* setup the RTF parser */
1668 memset(&parser, 0, sizeof parser);
1669 RTFSetEditStream(&parser, &inStream);
1670 parser.rtfFormat = format&(SF_TEXT|SF_RTF);
1671 parser.editor = editor;
1672 parser.style = style;
1673 WriterInit(&parser);
1674 RTFInit(&parser);
1675 RTFSetReadHook(&parser, ME_RTFReadHook);
1676 RTFSetDestinationCallback(&parser, rtfShpPict, ME_RTFReadShpPictGroup);
1677 RTFSetDestinationCallback(&parser, rtfPict, ME_RTFReadPictGroup);
1678 RTFSetDestinationCallback(&parser, rtfObject, ME_RTFReadObjectGroup);
1679 RTFSetDestinationCallback(&parser, rtfParNumbering, ME_RTFReadParnumGroup);
1680 if (!parser.editor->bEmulateVersion10) /* v4.1 */
1682 RTFSetDestinationCallback(&parser, rtfNoNestTables, RTFSkipGroup);
1683 RTFSetDestinationCallback(&parser, rtfNestTableProps, RTFReadGroup);
1685 BeginFile(&parser);
1687 /* do the parsing */
1688 RTFRead(&parser);
1689 RTFFlushOutputBuffer(&parser);
1690 if (!editor->bEmulateVersion10) { /* v4.1 */
1691 if (parser.tableDef && parser.tableDef->tableRowStart &&
1692 (parser.nestingLevel > 0 || parser.canInheritInTbl))
1694 /* Delete any incomplete table row at the end of the rich text. */
1695 int nOfs, nChars;
1696 ME_DisplayItem *para;
1698 parser.rtfMinor = rtfRow;
1699 /* Complete the table row before deleting it.
1700 * By doing it this way we will have the current paragraph format set
1701 * properly to reflect that is not in the complete table, and undo items
1702 * will be added for this change to the current paragraph format. */
1703 if (parser.nestingLevel > 0)
1705 while (parser.nestingLevel > 1)
1706 ME_RTFSpecialCharHook(&parser); /* Decrements nestingLevel */
1707 para = parser.tableDef->tableRowStart;
1708 ME_RTFSpecialCharHook(&parser);
1709 } else {
1710 para = parser.tableDef->tableRowStart;
1711 ME_RTFSpecialCharHook(&parser);
1712 assert(para->member.para.nFlags & MEPF_ROWEND);
1713 para = para->member.para.next_para;
1716 editor->pCursors[1].pPara = para;
1717 editor->pCursors[1].pRun = ME_FindItemFwd(para, diRun);
1718 editor->pCursors[1].nOffset = 0;
1719 nOfs = ME_GetCursorOfs(&editor->pCursors[1]);
1720 nChars = ME_GetCursorOfs(&editor->pCursors[0]) - nOfs;
1721 ME_InternalDeleteText(editor, &editor->pCursors[1], nChars, TRUE);
1722 if (parser.tableDef)
1723 parser.tableDef->tableRowStart = NULL;
1726 ME_CheckTablesForCorruption(editor);
1727 RTFDestroy(&parser);
1729 if (parser.stackTop > 0)
1731 while (--parser.stackTop >= 0)
1733 ME_ReleaseStyle(parser.style);
1734 parser.style = parser.stack[parser.stackTop].style;
1736 if (!inStream.editstream->dwError)
1737 inStream.editstream->dwError = HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
1740 /* Remove last line break, as mandated by tests. This is not affected by
1741 CR/LF counters, since RTF streaming presents only \para tokens, which
1742 are converted according to the standard rules: \r for 2.0, \r\n for 1.0
1744 if (stripLastCR && !(format & SFF_SELECTION)) {
1745 int newto;
1746 ME_GetSelection(editor, &selStart, &selEnd);
1747 newto = ME_GetCursorOfs(selEnd);
1748 if (newto > to + (editor->bEmulateVersion10 ? 1 : 0)) {
1749 WCHAR lastchar[3] = {'\0', '\0'};
1750 int linebreakSize = editor->bEmulateVersion10 ? 2 : 1;
1751 ME_Cursor linebreakCursor = *selEnd, lastcharCursor = *selEnd;
1752 CHARFORMAT2W cf;
1754 /* Set the final eop to the char fmt of the last char */
1755 cf.cbSize = sizeof(cf);
1756 cf.dwMask = CFM_ALL2;
1757 ME_MoveCursorChars(editor, &lastcharCursor, -1, FALSE);
1758 ME_GetCharFormat(editor, &lastcharCursor, &linebreakCursor, &cf);
1759 set_selection_cursors(editor, newto, -1);
1760 ME_SetSelectionCharFormat(editor, &cf);
1761 set_selection_cursors(editor, newto, newto);
1763 ME_MoveCursorChars(editor, &linebreakCursor, -linebreakSize, FALSE);
1764 ME_GetTextW(editor, lastchar, 2, &linebreakCursor, linebreakSize, FALSE, FALSE);
1765 if (lastchar[0] == '\r' && (lastchar[1] == '\n' || lastchar[1] == '\0')) {
1766 ME_InternalDeleteText(editor, &linebreakCursor, linebreakSize, FALSE);
1770 to = ME_GetCursorOfs(&editor->pCursors[0]);
1771 num_read = to - from;
1773 style = parser.style;
1775 else if (format & SF_TEXT)
1777 num_read = ME_StreamInText(editor, format, &inStream, style);
1778 to = ME_GetCursorOfs(&editor->pCursors[0]);
1780 else
1781 ERR("EM_STREAMIN without SF_TEXT or SF_RTF\n");
1782 /* put the cursor at the top */
1783 if (!(format & SFF_SELECTION))
1784 set_selection_cursors(editor, 0, 0);
1785 ME_CursorFromCharOfs(editor, from, &start);
1786 ME_UpdateLinkAttribute(editor, &start, to - from);
1789 /* Restore saved undo mode */
1790 editor->nUndoMode = nUndoMode;
1792 /* even if we didn't add an undo, we need to commit anything on the stack */
1793 ME_CommitUndo(editor);
1795 /* If SFF_SELECTION isn't set, delete any undos from before we started too */
1796 if (!(format & SFF_SELECTION))
1797 ME_EmptyUndoStack(editor);
1799 ME_ReleaseStyle(style);
1800 editor->nEventMask = nEventMask;
1801 ME_UpdateRepaint(editor, FALSE);
1802 if (!(format & SFF_SELECTION)) {
1803 ME_ClearTempStyle(editor);
1805 update_caret(editor);
1806 ME_SendSelChange(editor);
1807 ME_SendRequestResize(editor, FALSE);
1809 return num_read;
1813 typedef struct tagME_RTFStringStreamStruct
1815 char *string;
1816 int pos;
1817 int length;
1818 } ME_RTFStringStreamStruct;
1820 static DWORD CALLBACK ME_ReadFromRTFString(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
1822 ME_RTFStringStreamStruct *pStruct = (ME_RTFStringStreamStruct *)dwCookie;
1823 int count;
1825 count = min(cb, pStruct->length - pStruct->pos);
1826 memmove(lpBuff, pStruct->string + pStruct->pos, count);
1827 pStruct->pos += count;
1828 *pcb = count;
1829 return 0;
1832 static void
1833 ME_StreamInRTFString(ME_TextEditor *editor, BOOL selection, char *string)
1835 EDITSTREAM es;
1836 ME_RTFStringStreamStruct data;
1838 data.string = string;
1839 data.length = strlen(string);
1840 data.pos = 0;
1841 es.dwCookie = (DWORD_PTR)&data;
1842 es.pfnCallback = ME_ReadFromRTFString;
1843 ME_StreamIn(editor, SF_RTF | (selection ? SFF_SELECTION : 0), &es, TRUE);
1847 static int
1848 ME_FindText(ME_TextEditor *editor, DWORD flags, const CHARRANGE *chrg, const WCHAR *text, CHARRANGE *chrgText)
1850 const int nLen = lstrlenW(text);
1851 const int nTextLen = ME_GetTextLength(editor);
1852 int nMin, nMax;
1853 ME_Cursor cursor;
1854 WCHAR wLastChar = ' ';
1856 TRACE("flags==0x%08x, chrg->cpMin==%d, chrg->cpMax==%d text==%s\n",
1857 flags, chrg->cpMin, chrg->cpMax, debugstr_w(text));
1859 if (flags & ~(FR_DOWN | FR_MATCHCASE | FR_WHOLEWORD))
1860 FIXME("Flags 0x%08x not implemented\n",
1861 flags & ~(FR_DOWN | FR_MATCHCASE | FR_WHOLEWORD));
1863 nMin = chrg->cpMin;
1864 if (chrg->cpMax == -1)
1865 nMax = nTextLen;
1866 else
1867 nMax = chrg->cpMax > nTextLen ? nTextLen : chrg->cpMax;
1869 /* In 1.0 emulation, if cpMax reaches end of text, add the FR_DOWN flag */
1870 if (editor->bEmulateVersion10 && nMax == nTextLen)
1872 flags |= FR_DOWN;
1875 /* In 1.0 emulation, cpMin must always be no greater than cpMax */
1876 if (editor->bEmulateVersion10 && nMax < nMin)
1878 if (chrgText)
1880 chrgText->cpMin = -1;
1881 chrgText->cpMax = -1;
1883 return -1;
1886 /* when searching up, if cpMin < cpMax, then instead of searching
1887 * on [cpMin,cpMax], we search on [0,cpMin], otherwise, search on
1888 * [cpMax, cpMin]. The exception is when cpMax is -1, in which
1889 * case, it is always bigger than cpMin.
1891 if (!editor->bEmulateVersion10 && !(flags & FR_DOWN))
1893 int nSwap = nMax;
1895 nMax = nMin > nTextLen ? nTextLen : nMin;
1896 if (nMin < nSwap || chrg->cpMax == -1)
1897 nMin = 0;
1898 else
1899 nMin = nSwap;
1902 if (!nLen || nMin < 0 || nMax < 0 || nMax < nMin)
1904 if (chrgText)
1905 chrgText->cpMin = chrgText->cpMax = -1;
1906 return -1;
1909 if (flags & FR_DOWN) /* Forward search */
1911 /* If possible, find the character before where the search starts */
1912 if ((flags & FR_WHOLEWORD) && nMin)
1914 ME_CursorFromCharOfs(editor, nMin - 1, &cursor);
1915 wLastChar = *get_text( &cursor.pRun->member.run, cursor.nOffset );
1916 ME_MoveCursorChars(editor, &cursor, 1, FALSE);
1917 } else {
1918 ME_CursorFromCharOfs(editor, nMin, &cursor);
1921 while (cursor.pRun && ME_GetCursorOfs(&cursor) + nLen <= nMax)
1923 ME_DisplayItem *pCurItem = cursor.pRun;
1924 int nCurStart = cursor.nOffset;
1925 int nMatched = 0;
1927 while (pCurItem && ME_CharCompare( *get_text( &pCurItem->member.run, nCurStart + nMatched ), text[nMatched], (flags & FR_MATCHCASE)))
1929 if ((flags & FR_WHOLEWORD) && iswalnum(wLastChar))
1930 break;
1932 nMatched++;
1933 if (nMatched == nLen)
1935 ME_DisplayItem *pNextItem = pCurItem;
1936 int nNextStart = nCurStart;
1937 WCHAR wNextChar;
1939 /* Check to see if next character is a whitespace */
1940 if (flags & FR_WHOLEWORD)
1942 if (nCurStart + nMatched == pCurItem->member.run.len)
1944 pNextItem = ME_FindItemFwd(pCurItem, diRun);
1945 nNextStart = -nMatched;
1948 if (pNextItem)
1949 wNextChar = *get_text( &pNextItem->member.run, nNextStart + nMatched );
1950 else
1951 wNextChar = ' ';
1953 if (iswalnum(wNextChar))
1954 break;
1957 cursor.nOffset += cursor.pPara->member.para.nCharOfs + cursor.pRun->member.run.nCharOfs;
1958 if (chrgText)
1960 chrgText->cpMin = cursor.nOffset;
1961 chrgText->cpMax = cursor.nOffset + nLen;
1963 TRACE("found at %d-%d\n", cursor.nOffset, cursor.nOffset + nLen);
1964 return cursor.nOffset;
1966 if (nCurStart + nMatched == pCurItem->member.run.len)
1968 pCurItem = ME_FindItemFwd(pCurItem, diRun);
1969 nCurStart = -nMatched;
1972 if (pCurItem)
1973 wLastChar = *get_text( &pCurItem->member.run, nCurStart + nMatched );
1974 else
1975 wLastChar = ' ';
1977 cursor.nOffset++;
1978 if (cursor.nOffset == cursor.pRun->member.run.len)
1980 ME_NextRun(&cursor.pPara, &cursor.pRun, TRUE);
1981 cursor.nOffset = 0;
1985 else /* Backward search */
1987 /* If possible, find the character after where the search ends */
1988 if ((flags & FR_WHOLEWORD) && nMax < nTextLen - 1)
1990 ME_CursorFromCharOfs(editor, nMax + 1, &cursor);
1991 wLastChar = *get_text( &cursor.pRun->member.run, cursor.nOffset );
1992 ME_MoveCursorChars(editor, &cursor, -1, FALSE);
1993 } else {
1994 ME_CursorFromCharOfs(editor, nMax, &cursor);
1997 while (cursor.pRun && ME_GetCursorOfs(&cursor) - nLen >= nMin)
1999 ME_DisplayItem *pCurItem = cursor.pRun;
2000 ME_DisplayItem *pCurPara = cursor.pPara;
2001 int nCurEnd = cursor.nOffset;
2002 int nMatched = 0;
2004 if (nCurEnd == 0)
2006 ME_PrevRun(&pCurPara, &pCurItem, TRUE);
2007 nCurEnd = pCurItem->member.run.len;
2010 while (pCurItem && ME_CharCompare( *get_text( &pCurItem->member.run, nCurEnd - nMatched - 1 ),
2011 text[nLen - nMatched - 1], (flags & FR_MATCHCASE) ))
2013 if ((flags & FR_WHOLEWORD) && iswalnum(wLastChar))
2014 break;
2016 nMatched++;
2017 if (nMatched == nLen)
2019 ME_DisplayItem *pPrevItem = pCurItem;
2020 int nPrevEnd = nCurEnd;
2021 WCHAR wPrevChar;
2022 int nStart;
2024 /* Check to see if previous character is a whitespace */
2025 if (flags & FR_WHOLEWORD)
2027 if (nPrevEnd - nMatched == 0)
2029 pPrevItem = ME_FindItemBack(pCurItem, diRun);
2030 if (pPrevItem)
2031 nPrevEnd = pPrevItem->member.run.len + nMatched;
2034 if (pPrevItem)
2035 wPrevChar = *get_text( &pPrevItem->member.run, nPrevEnd - nMatched - 1 );
2036 else
2037 wPrevChar = ' ';
2039 if (iswalnum(wPrevChar))
2040 break;
2043 nStart = pCurPara->member.para.nCharOfs
2044 + pCurItem->member.run.nCharOfs + nCurEnd - nMatched;
2045 if (chrgText)
2047 chrgText->cpMin = nStart;
2048 chrgText->cpMax = nStart + nLen;
2050 TRACE("found at %d-%d\n", nStart, nStart + nLen);
2051 return nStart;
2053 if (nCurEnd - nMatched == 0)
2055 ME_PrevRun(&pCurPara, &pCurItem, TRUE);
2056 /* Don't care about pCurItem becoming NULL here; it's already taken
2057 * care of in the exterior loop condition */
2058 nCurEnd = pCurItem->member.run.len + nMatched;
2061 if (pCurItem)
2062 wLastChar = *get_text( &pCurItem->member.run, nCurEnd - nMatched - 1 );
2063 else
2064 wLastChar = ' ';
2066 cursor.nOffset--;
2067 if (cursor.nOffset < 0)
2069 ME_PrevRun(&cursor.pPara, &cursor.pRun, TRUE);
2070 cursor.nOffset = cursor.pRun->member.run.len;
2074 TRACE("not found\n");
2075 if (chrgText)
2076 chrgText->cpMin = chrgText->cpMax = -1;
2077 return -1;
2080 static int ME_GetTextEx(ME_TextEditor *editor, GETTEXTEX *ex, LPARAM pText)
2082 int nChars;
2083 ME_Cursor start;
2085 if (!ex->cb || !pText) return 0;
2087 if (ex->flags & ~(GT_SELECTION | GT_USECRLF))
2088 FIXME("GETTEXTEX flags 0x%08x not supported\n", ex->flags & ~(GT_SELECTION | GT_USECRLF));
2090 if (ex->flags & GT_SELECTION)
2092 int from, to;
2093 int nStartCur = ME_GetSelectionOfs(editor, &from, &to);
2094 start = editor->pCursors[nStartCur];
2095 nChars = to - from;
2097 else
2099 ME_SetCursorToStart(editor, &start);
2100 nChars = INT_MAX;
2102 if (ex->codepage == CP_UNICODE)
2104 return ME_GetTextW(editor, (LPWSTR)pText, ex->cb / sizeof(WCHAR) - 1,
2105 &start, nChars, ex->flags & GT_USECRLF, FALSE);
2107 else
2109 /* potentially each char may be a CR, why calculate the exact value with O(N) when
2110 we can just take a bigger buffer? :)
2111 The above assumption still holds with CR/LF counters, since CR->CRLF expansion
2112 occurs only in richedit 2.0 mode, in which line breaks have only one CR
2114 int crlfmul = (ex->flags & GT_USECRLF) ? 2 : 1;
2115 DWORD buflen;
2116 LPWSTR buffer;
2117 LRESULT rc;
2119 buflen = min(crlfmul * nChars, ex->cb - 1);
2120 buffer = heap_alloc((buflen + 1) * sizeof(WCHAR));
2122 nChars = ME_GetTextW(editor, buffer, buflen, &start, nChars, ex->flags & GT_USECRLF, FALSE);
2123 rc = WideCharToMultiByte(ex->codepage, 0, buffer, nChars + 1,
2124 (LPSTR)pText, ex->cb, ex->lpDefaultChar, ex->lpUsedDefChar);
2125 if (rc) rc--; /* do not count 0 terminator */
2127 heap_free(buffer);
2128 return rc;
2132 static int ME_GetTextRange(ME_TextEditor *editor, WCHAR *strText,
2133 const ME_Cursor *start, int nLen, BOOL unicode)
2135 if (!strText) return 0;
2136 if (unicode) {
2137 return ME_GetTextW(editor, strText, INT_MAX, start, nLen, FALSE, FALSE);
2138 } else {
2139 int nChars;
2140 WCHAR *p = heap_alloc((nLen+1) * sizeof(*p));
2141 if (!p) return 0;
2142 nChars = ME_GetTextW(editor, p, nLen, start, nLen, FALSE, FALSE);
2143 WideCharToMultiByte(CP_ACP, 0, p, nChars+1, (char *)strText,
2144 nLen+1, NULL, NULL);
2145 heap_free(p);
2146 return nChars;
2150 int set_selection( ME_TextEditor *editor, int to, int from )
2152 int end;
2154 TRACE("%d - %d\n", to, from );
2156 if (!editor->bHideSelection) ME_InvalidateSelection( editor );
2157 end = set_selection_cursors( editor, to, from );
2158 if (!editor->bHideSelection) ME_InvalidateSelection( editor );
2159 update_caret( editor );
2160 ME_SendSelChange( editor );
2162 return end;
2165 typedef struct tagME_GlobalDestStruct
2167 HGLOBAL hData;
2168 int nLength;
2169 } ME_GlobalDestStruct;
2171 static DWORD CALLBACK ME_ReadFromHGLOBALUnicode(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
2173 ME_GlobalDestStruct *pData = (ME_GlobalDestStruct *)dwCookie;
2174 int i;
2175 WORD *pSrc, *pDest;
2177 cb = cb >> 1;
2178 pDest = (WORD *)lpBuff;
2179 pSrc = GlobalLock(pData->hData);
2180 for (i = 0; i<cb && pSrc[pData->nLength+i]; i++) {
2181 pDest[i] = pSrc[pData->nLength+i];
2183 pData->nLength += i;
2184 *pcb = 2*i;
2185 GlobalUnlock(pData->hData);
2186 return 0;
2189 static DWORD CALLBACK ME_ReadFromHGLOBALRTF(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
2191 ME_GlobalDestStruct *pData = (ME_GlobalDestStruct *)dwCookie;
2192 int i;
2193 BYTE *pSrc, *pDest;
2195 pDest = lpBuff;
2196 pSrc = GlobalLock(pData->hData);
2197 for (i = 0; i<cb && pSrc[pData->nLength+i]; i++) {
2198 pDest[i] = pSrc[pData->nLength+i];
2200 pData->nLength += i;
2201 *pcb = i;
2202 GlobalUnlock(pData->hData);
2203 return 0;
2206 static const WCHAR rtfW[] = {'R','i','c','h',' ','T','e','x','t',' ','F','o','r','m','a','t',0};
2208 static HRESULT paste_rtf(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2210 EDITSTREAM es;
2211 ME_GlobalDestStruct gds;
2212 HRESULT hr;
2214 gds.hData = med->u.hGlobal;
2215 gds.nLength = 0;
2216 es.dwCookie = (DWORD_PTR)&gds;
2217 es.pfnCallback = ME_ReadFromHGLOBALRTF;
2218 hr = ME_StreamIn( editor, SF_RTF | SFF_SELECTION, &es, FALSE ) == 0 ? E_FAIL : S_OK;
2219 ReleaseStgMedium( med );
2220 return hr;
2223 static HRESULT paste_text(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2225 EDITSTREAM es;
2226 ME_GlobalDestStruct gds;
2227 HRESULT hr;
2229 gds.hData = med->u.hGlobal;
2230 gds.nLength = 0;
2231 es.dwCookie = (DWORD_PTR)&gds;
2232 es.pfnCallback = ME_ReadFromHGLOBALUnicode;
2233 hr = ME_StreamIn( editor, SF_TEXT | SF_UNICODE | SFF_SELECTION, &es, FALSE ) == 0 ? E_FAIL : S_OK;
2234 ReleaseStgMedium( med );
2235 return hr;
2238 static HRESULT paste_emf(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2240 HRESULT hr;
2241 SIZEL sz = {0, 0};
2243 hr = insert_static_object( editor, med->u.hEnhMetaFile, NULL, &sz );
2244 if (SUCCEEDED(hr))
2246 ME_CommitUndo( editor );
2247 ME_UpdateRepaint( editor, FALSE );
2249 else
2250 ReleaseStgMedium( med );
2252 return hr;
2255 static struct paste_format
2257 FORMATETC fmt;
2258 HRESULT (*paste)(ME_TextEditor *, FORMATETC *, STGMEDIUM *);
2259 const WCHAR *name;
2260 } paste_formats[] =
2262 {{ -1, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, paste_rtf, rtfW },
2263 {{ CF_UNICODETEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, paste_text },
2264 {{ CF_ENHMETAFILE, NULL, DVASPECT_CONTENT, -1, TYMED_ENHMF }, paste_emf },
2265 {{ 0 }}
2268 static void init_paste_formats(void)
2270 struct paste_format *format;
2271 static int done;
2273 if (!done)
2275 for (format = paste_formats; format->fmt.cfFormat; format++)
2277 if (format->name)
2278 format->fmt.cfFormat = RegisterClipboardFormatW( format->name );
2280 done = 1;
2284 static BOOL paste_special(ME_TextEditor *editor, UINT cf, REPASTESPECIAL *ps, BOOL check_only)
2286 HRESULT hr;
2287 STGMEDIUM med;
2288 struct paste_format *format;
2289 IDataObject *data;
2291 /* Protect read-only edit control from modification */
2292 if (editor->styleFlags & ES_READONLY)
2294 if (!check_only)
2295 MessageBeep(MB_ICONERROR);
2296 return FALSE;
2299 init_paste_formats();
2301 if (ps && ps->dwAspect != DVASPECT_CONTENT)
2302 FIXME("Ignoring aspect %x\n", ps->dwAspect);
2304 hr = OleGetClipboard( &data );
2305 if (hr != S_OK) return FALSE;
2307 if (cf == CF_TEXT) cf = CF_UNICODETEXT;
2309 hr = S_FALSE;
2310 for (format = paste_formats; format->fmt.cfFormat; format++)
2312 if (cf && cf != format->fmt.cfFormat) continue;
2313 hr = IDataObject_QueryGetData( data, &format->fmt );
2314 if (hr == S_OK)
2316 if (!check_only)
2318 hr = IDataObject_GetData( data, &format->fmt, &med );
2319 if (hr != S_OK) goto done;
2320 hr = format->paste( editor, &format->fmt, &med );
2322 break;
2326 done:
2327 IDataObject_Release( data );
2329 return hr == S_OK;
2332 static BOOL ME_Copy(ME_TextEditor *editor, const ME_Cursor *start, int nChars)
2334 LPDATAOBJECT dataObj = NULL;
2335 HRESULT hr = S_OK;
2337 if (editor->cPasswordMask)
2338 return FALSE; /* Copying or Cutting masked text isn't allowed */
2340 if(editor->lpOleCallback)
2342 CHARRANGE range;
2343 range.cpMin = ME_GetCursorOfs(start);
2344 range.cpMax = range.cpMin + nChars;
2345 hr = IRichEditOleCallback_GetClipboardData(editor->lpOleCallback, &range, RECO_COPY, &dataObj);
2347 if(FAILED(hr) || !dataObj)
2348 hr = ME_GetDataObject(editor, start, nChars, &dataObj);
2349 if(SUCCEEDED(hr)) {
2350 hr = OleSetClipboard(dataObj);
2351 IDataObject_Release(dataObj);
2353 return SUCCEEDED(hr);
2356 static BOOL copy_or_cut(ME_TextEditor *editor, BOOL cut)
2358 BOOL result;
2359 int offs, num_chars;
2360 int start_cursor = ME_GetSelectionOfs(editor, &offs, &num_chars);
2361 ME_Cursor *sel_start = &editor->pCursors[start_cursor];
2363 if (cut && (editor->styleFlags & ES_READONLY))
2365 MessageBeep(MB_ICONERROR);
2366 return FALSE;
2369 num_chars -= offs;
2370 result = ME_Copy(editor, sel_start, num_chars);
2371 if (result && cut)
2373 ME_InternalDeleteText(editor, sel_start, num_chars, FALSE);
2374 ME_CommitUndo(editor);
2375 ME_UpdateRepaint(editor, TRUE);
2377 return result;
2380 /* helper to send a msg filter notification */
2381 static BOOL
2382 ME_FilterEvent(ME_TextEditor *editor, UINT msg, WPARAM* wParam, LPARAM* lParam)
2384 MSGFILTER msgf;
2386 if (!editor->hWnd || !editor->hwndParent) return FALSE;
2387 msgf.nmhdr.hwndFrom = editor->hWnd;
2388 msgf.nmhdr.idFrom = GetWindowLongW(editor->hWnd, GWLP_ID);
2389 msgf.nmhdr.code = EN_MSGFILTER;
2390 msgf.msg = msg;
2391 msgf.wParam = *wParam;
2392 msgf.lParam = *lParam;
2393 if (SendMessageW(editor->hwndParent, WM_NOTIFY, msgf.nmhdr.idFrom, (LPARAM)&msgf))
2394 return FALSE;
2395 *wParam = msgf.wParam;
2396 *lParam = msgf.lParam;
2397 msgf.wParam = *wParam;
2399 return TRUE;
2402 static void ME_UpdateSelectionLinkAttribute(ME_TextEditor *editor)
2404 ME_DisplayItem *startPara, *endPara;
2405 ME_DisplayItem *prev_para;
2406 ME_Cursor *from, *to;
2407 ME_Cursor start;
2408 int nChars;
2410 if (!editor->AutoURLDetect_bEnable) return;
2412 ME_GetSelection(editor, &from, &to);
2414 /* Find paragraph previous to the one that contains start cursor */
2415 startPara = from->pPara;
2416 prev_para = startPara->member.para.prev_para;
2417 if (prev_para->type == diParagraph) startPara = prev_para;
2419 /* Find paragraph that contains end cursor */
2420 endPara = to->pPara->member.para.next_para;
2422 start.pPara = startPara;
2423 start.pRun = ME_FindItemFwd(startPara, diRun);
2424 start.nOffset = 0;
2425 nChars = endPara->member.para.nCharOfs - startPara->member.para.nCharOfs;
2427 ME_UpdateLinkAttribute(editor, &start, nChars);
2430 static BOOL handle_enter(ME_TextEditor *editor)
2432 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2433 BOOL shift_is_down = GetKeyState(VK_SHIFT) & 0x8000;
2435 if (editor->bDialogMode)
2437 if (ctrl_is_down)
2438 return TRUE;
2440 if (!(editor->styleFlags & ES_WANTRETURN))
2442 if (editor->hwndParent)
2444 DWORD dw;
2445 dw = SendMessageW(editor->hwndParent, DM_GETDEFID, 0, 0);
2446 if (HIWORD(dw) == DC_HASDEFID)
2448 HWND hwDefCtrl = GetDlgItem(editor->hwndParent, LOWORD(dw));
2449 if (hwDefCtrl)
2451 SendMessageW(editor->hwndParent, WM_NEXTDLGCTL, (WPARAM)hwDefCtrl, TRUE);
2452 PostMessageW(hwDefCtrl, WM_KEYDOWN, VK_RETURN, 0);
2456 return TRUE;
2460 if (editor->styleFlags & ES_MULTILINE)
2462 static const WCHAR endl = '\r';
2463 static const WCHAR endlv10[] = {'\r','\n'};
2464 ME_Cursor cursor = editor->pCursors[0];
2465 ME_DisplayItem *para = cursor.pPara;
2466 int from, to;
2467 ME_Style *style, *eop_style;
2469 if (editor->styleFlags & ES_READONLY)
2471 MessageBeep(MB_ICONERROR);
2472 return TRUE;
2475 ME_GetSelectionOfs(editor, &from, &to);
2476 if (editor->nTextLimit > ME_GetTextLength(editor) - (to-from))
2478 if (!editor->bEmulateVersion10) /* v4.1 */
2480 if (para->member.para.nFlags & MEPF_ROWEND)
2482 /* Add a new table row after this row. */
2483 para = ME_AppendTableRow(editor, para);
2484 para = para->member.para.next_para;
2485 editor->pCursors[0].pPara = para;
2486 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2487 editor->pCursors[0].nOffset = 0;
2488 editor->pCursors[1] = editor->pCursors[0];
2489 ME_CommitUndo(editor);
2490 ME_CheckTablesForCorruption(editor);
2491 ME_UpdateRepaint(editor, FALSE);
2492 return TRUE;
2494 else if (para == editor->pCursors[1].pPara &&
2495 cursor.nOffset + cursor.pRun->member.run.nCharOfs == 0 &&
2496 para->member.para.prev_para->member.para.nFlags & MEPF_ROWSTART &&
2497 !para->member.para.prev_para->member.para.nCharOfs)
2499 /* Insert a newline before the table. */
2500 para = para->member.para.prev_para;
2501 para->member.para.nFlags &= ~MEPF_ROWSTART;
2502 editor->pCursors[0].pPara = para;
2503 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2504 editor->pCursors[1] = editor->pCursors[0];
2505 ME_InsertTextFromCursor(editor, 0, &endl, 1,
2506 editor->pCursors[0].pRun->member.run.style);
2507 para = editor->pBuffer->pFirst->member.para.next_para;
2508 ME_SetDefaultParaFormat(editor, &para->member.para.fmt);
2509 para->member.para.nFlags = 0;
2510 mark_para_rewrap(editor, para);
2511 editor->pCursors[0].pPara = para;
2512 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2513 editor->pCursors[1] = editor->pCursors[0];
2514 para->member.para.next_para->member.para.nFlags |= MEPF_ROWSTART;
2515 ME_CommitCoalescingUndo(editor);
2516 ME_CheckTablesForCorruption(editor);
2517 ME_UpdateRepaint(editor, FALSE);
2518 return TRUE;
2521 else /* v1.0 - 3.0 */
2523 ME_DisplayItem *para = cursor.pPara;
2524 if (ME_IsInTable(para))
2526 if (cursor.pRun->member.run.nFlags & MERF_ENDPARA)
2528 if (from == to)
2530 ME_ContinueCoalescingTransaction(editor);
2531 para = ME_AppendTableRow(editor, para);
2532 editor->pCursors[0].pPara = para;
2533 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2534 editor->pCursors[0].nOffset = 0;
2535 editor->pCursors[1] = editor->pCursors[0];
2536 ME_CommitCoalescingUndo(editor);
2537 ME_UpdateRepaint(editor, FALSE);
2538 return TRUE;
2541 else
2543 ME_ContinueCoalescingTransaction(editor);
2544 if (cursor.pRun->member.run.nCharOfs + cursor.nOffset == 0 &&
2545 !ME_IsInTable(para->member.para.prev_para))
2547 /* Insert newline before table */
2548 cursor.pRun = ME_FindItemBack(para, diRun);
2549 if (cursor.pRun)
2551 editor->pCursors[0].pRun = cursor.pRun;
2552 editor->pCursors[0].pPara = para->member.para.prev_para;
2554 editor->pCursors[0].nOffset = 0;
2555 editor->pCursors[1] = editor->pCursors[0];
2556 ME_InsertTextFromCursor(editor, 0, &endl, 1,
2557 editor->pCursors[0].pRun->member.run.style);
2559 else
2561 editor->pCursors[1] = editor->pCursors[0];
2562 para = ME_AppendTableRow(editor, para);
2563 editor->pCursors[0].pPara = para;
2564 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2565 editor->pCursors[0].nOffset = 0;
2566 editor->pCursors[1] = editor->pCursors[0];
2568 ME_CommitCoalescingUndo(editor);
2569 ME_UpdateRepaint(editor, FALSE);
2570 return TRUE;
2575 style = ME_GetInsertStyle(editor, 0);
2577 /* Normally the new eop style is the insert style, however in a list it is copied from the existing
2578 eop style (this prevents the list label style changing when the new eop is inserted).
2579 No extra ref is taken here on eop_style. */
2580 if (para->member.para.fmt.wNumbering)
2581 eop_style = para->member.para.eop_run->style;
2582 else
2583 eop_style = style;
2584 ME_ContinueCoalescingTransaction(editor);
2585 if (shift_is_down)
2586 ME_InsertEndRowFromCursor(editor, 0);
2587 else
2588 if (!editor->bEmulateVersion10)
2589 ME_InsertTextFromCursor(editor, 0, &endl, 1, eop_style);
2590 else
2591 ME_InsertTextFromCursor(editor, 0, endlv10, 2, eop_style);
2592 ME_CommitCoalescingUndo(editor);
2593 SetCursor(NULL);
2595 ME_UpdateSelectionLinkAttribute(editor);
2596 ME_UpdateRepaint(editor, FALSE);
2597 ME_SaveTempStyle(editor, style); /* set the temp insert style for the new para */
2598 ME_ReleaseStyle(style);
2600 return TRUE;
2602 return FALSE;
2605 static BOOL
2606 ME_KeyDown(ME_TextEditor *editor, WORD nKey)
2608 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2609 BOOL shift_is_down = GetKeyState(VK_SHIFT) & 0x8000;
2611 if (editor->bMouseCaptured)
2612 return FALSE;
2613 if (nKey != VK_SHIFT && nKey != VK_CONTROL && nKey != VK_MENU)
2614 editor->nSelectionType = stPosition;
2616 switch (nKey)
2618 case VK_LEFT:
2619 case VK_RIGHT:
2620 case VK_HOME:
2621 case VK_END:
2622 editor->nUDArrowX = -1;
2623 /* fall through */
2624 case VK_UP:
2625 case VK_DOWN:
2626 case VK_PRIOR:
2627 case VK_NEXT:
2628 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
2629 ME_ArrowKey(editor, nKey, shift_is_down, ctrl_is_down);
2630 return TRUE;
2631 case VK_BACK:
2632 case VK_DELETE:
2633 editor->nUDArrowX = -1;
2634 /* FIXME backspace and delete aren't the same, they act different wrt paragraph style of the merged paragraph */
2635 if (editor->styleFlags & ES_READONLY)
2636 return FALSE;
2637 if (ME_IsSelection(editor))
2639 ME_DeleteSelection(editor);
2640 ME_CommitUndo(editor);
2642 else if (nKey == VK_DELETE)
2644 /* Delete stops group typing.
2645 * (See MSDN remarks on EM_STOPGROUPTYPING message) */
2646 ME_DeleteTextAtCursor(editor, 1, 1);
2647 ME_CommitUndo(editor);
2649 else if (ME_ArrowKey(editor, VK_LEFT, FALSE, FALSE))
2651 BOOL bDeletionSucceeded;
2652 /* Backspace can be grouped for a single undo */
2653 ME_ContinueCoalescingTransaction(editor);
2654 bDeletionSucceeded = ME_DeleteTextAtCursor(editor, 1, 1);
2655 if (!bDeletionSucceeded && !editor->bEmulateVersion10) { /* v4.1 */
2656 /* Deletion was prevented so the cursor is moved back to where it was.
2657 * (e.g. this happens when trying to delete cell boundaries)
2659 ME_ArrowKey(editor, VK_RIGHT, FALSE, FALSE);
2661 ME_CommitCoalescingUndo(editor);
2663 else
2664 return TRUE;
2665 ME_MoveCursorFromTableRowStartParagraph(editor);
2666 ME_UpdateSelectionLinkAttribute(editor);
2667 ME_UpdateRepaint(editor, FALSE);
2668 ME_SendRequestResize(editor, FALSE);
2669 return TRUE;
2670 case VK_RETURN:
2671 if (!editor->bEmulateVersion10)
2672 return handle_enter(editor);
2673 break;
2674 case VK_ESCAPE:
2675 if (editor->bDialogMode && editor->hwndParent)
2676 PostMessageW(editor->hwndParent, WM_CLOSE, 0, 0);
2677 return TRUE;
2678 case VK_TAB:
2679 if (editor->bDialogMode && editor->hwndParent)
2680 SendMessageW(editor->hwndParent, WM_NEXTDLGCTL, shift_is_down, 0);
2681 return TRUE;
2682 case 'A':
2683 if (ctrl_is_down)
2685 set_selection( editor, 0, -1 );
2686 return TRUE;
2688 break;
2689 case 'V':
2690 if (ctrl_is_down)
2691 return paste_special( editor, 0, NULL, FALSE );
2692 break;
2693 case 'C':
2694 case 'X':
2695 if (ctrl_is_down)
2696 return copy_or_cut(editor, nKey == 'X');
2697 break;
2698 case 'Z':
2699 if (ctrl_is_down)
2701 ME_Undo(editor);
2702 return TRUE;
2704 break;
2705 case 'Y':
2706 if (ctrl_is_down)
2708 ME_Redo(editor);
2709 return TRUE;
2711 break;
2713 default:
2714 if (nKey != VK_SHIFT && nKey != VK_CONTROL && nKey && nKey != VK_MENU)
2715 editor->nUDArrowX = -1;
2716 if (ctrl_is_down)
2718 if (nKey == 'W')
2720 CHARFORMAT2W chf;
2721 char buf[2048];
2722 chf.cbSize = sizeof(chf);
2724 ME_GetSelectionCharFormat(editor, &chf);
2725 ME_DumpStyleToBuf(&chf, buf);
2726 MessageBoxA(NULL, buf, "Style dump", MB_OK);
2728 if (nKey == 'Q')
2730 ME_CheckCharOffsets(editor);
2734 return FALSE;
2737 static LRESULT ME_Char(ME_TextEditor *editor, WPARAM charCode,
2738 LPARAM flags, BOOL unicode)
2740 WCHAR wstr;
2742 if (editor->bMouseCaptured)
2743 return 0;
2745 if (editor->styleFlags & ES_READONLY)
2747 MessageBeep(MB_ICONERROR);
2748 return 0; /* FIXME really 0 ? */
2751 if (unicode)
2752 wstr = (WCHAR)charCode;
2753 else
2755 CHAR charA = charCode;
2756 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &wstr, 1);
2759 if (editor->bEmulateVersion10 && wstr == '\r')
2760 handle_enter(editor);
2762 if ((unsigned)wstr >= ' ' || wstr == '\t')
2764 ME_Cursor cursor = editor->pCursors[0];
2765 ME_DisplayItem *para = cursor.pPara;
2766 int from, to;
2767 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2768 ME_GetSelectionOfs(editor, &from, &to);
2769 if (wstr == '\t' &&
2770 /* v4.1 allows tabs to be inserted with ctrl key down */
2771 !(ctrl_is_down && !editor->bEmulateVersion10))
2773 ME_DisplayItem *para;
2774 BOOL bSelectedRow = FALSE;
2776 para = cursor.pPara;
2777 if (ME_IsSelection(editor) &&
2778 cursor.pRun->member.run.nCharOfs + cursor.nOffset == 0 &&
2779 to == ME_GetCursorOfs(&editor->pCursors[0]) &&
2780 para->member.para.prev_para->type == diParagraph)
2782 para = para->member.para.prev_para;
2783 bSelectedRow = TRUE;
2785 if (ME_IsInTable(para))
2787 ME_TabPressedInTable(editor, bSelectedRow);
2788 ME_CommitUndo(editor);
2789 return 0;
2791 } else if (!editor->bEmulateVersion10) { /* v4.1 */
2792 if (para->member.para.nFlags & MEPF_ROWEND) {
2793 if (from == to) {
2794 para = para->member.para.next_para;
2795 if (para->member.para.nFlags & MEPF_ROWSTART)
2796 para = para->member.para.next_para;
2797 editor->pCursors[0].pPara = para;
2798 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2799 editor->pCursors[0].nOffset = 0;
2800 editor->pCursors[1] = editor->pCursors[0];
2803 } else { /* v1.0 - 3.0 */
2804 if (ME_IsInTable(cursor.pRun) &&
2805 cursor.pRun->member.run.nFlags & MERF_ENDPARA &&
2806 from == to)
2808 /* Text should not be inserted at the end of the table. */
2809 MessageBeep(-1);
2810 return 0;
2813 /* FIXME maybe it would make sense to call EM_REPLACESEL instead ? */
2814 /* WM_CHAR is restricted to nTextLimit */
2815 if(editor->nTextLimit > ME_GetTextLength(editor) - (to-from))
2817 ME_Style *style = ME_GetInsertStyle(editor, 0);
2818 ME_ContinueCoalescingTransaction(editor);
2819 ME_InsertTextFromCursor(editor, 0, &wstr, 1, style);
2820 ME_ReleaseStyle(style);
2821 ME_CommitCoalescingUndo(editor);
2822 ITextHost_TxSetCursor(editor->texthost, NULL, FALSE);
2825 ME_UpdateSelectionLinkAttribute(editor);
2826 ME_UpdateRepaint(editor, FALSE);
2828 return 0;
2831 /* Process the message and calculate the new click count.
2833 * returns: The click count if it is mouse down event, else returns 0. */
2834 static int ME_CalculateClickCount(ME_TextEditor *editor, UINT msg, WPARAM wParam,
2835 LPARAM lParam)
2837 static int clickNum = 0;
2838 if (msg < WM_MOUSEFIRST || msg > WM_MOUSELAST)
2839 return 0;
2841 if ((msg == WM_LBUTTONDBLCLK) ||
2842 (msg == WM_RBUTTONDBLCLK) ||
2843 (msg == WM_MBUTTONDBLCLK) ||
2844 (msg == WM_XBUTTONDBLCLK))
2846 msg -= (WM_LBUTTONDBLCLK - WM_LBUTTONDOWN);
2849 if ((msg == WM_LBUTTONDOWN) ||
2850 (msg == WM_RBUTTONDOWN) ||
2851 (msg == WM_MBUTTONDOWN) ||
2852 (msg == WM_XBUTTONDOWN))
2854 static MSG prevClickMsg;
2855 MSG clickMsg;
2856 /* Compare the editor instead of the hwnd so that the this
2857 * can still be done for windowless richedit controls. */
2858 clickMsg.hwnd = (HWND)editor;
2859 clickMsg.message = msg;
2860 clickMsg.wParam = wParam;
2861 clickMsg.lParam = lParam;
2862 clickMsg.time = GetMessageTime();
2863 clickMsg.pt.x = (short)LOWORD(lParam);
2864 clickMsg.pt.y = (short)HIWORD(lParam);
2865 if ((clickNum != 0) &&
2866 (clickMsg.message == prevClickMsg.message) &&
2867 (clickMsg.hwnd == prevClickMsg.hwnd) &&
2868 (clickMsg.wParam == prevClickMsg.wParam) &&
2869 (clickMsg.time - prevClickMsg.time < GetDoubleClickTime()) &&
2870 (abs(clickMsg.pt.x - prevClickMsg.pt.x) < GetSystemMetrics(SM_CXDOUBLECLK)/2) &&
2871 (abs(clickMsg.pt.y - prevClickMsg.pt.y) < GetSystemMetrics(SM_CYDOUBLECLK)/2))
2873 clickNum++;
2874 } else {
2875 clickNum = 1;
2877 prevClickMsg = clickMsg;
2878 } else {
2879 return 0;
2881 return clickNum;
2884 static BOOL is_link( ME_Run *run )
2886 return (run->style->fmt.dwMask & CFM_LINK) && (run->style->fmt.dwEffects & CFE_LINK);
2889 static BOOL ME_SetCursor(ME_TextEditor *editor)
2891 ME_Cursor cursor;
2892 POINT pt;
2893 BOOL isExact;
2894 SCROLLBARINFO sbi;
2895 DWORD messagePos = GetMessagePos();
2896 pt.x = (short)LOWORD(messagePos);
2897 pt.y = (short)HIWORD(messagePos);
2899 if (editor->hWnd)
2901 sbi.cbSize = sizeof(sbi);
2902 GetScrollBarInfo(editor->hWnd, OBJID_HSCROLL, &sbi);
2903 if (!(sbi.rgstate[0] & (STATE_SYSTEM_INVISIBLE|STATE_SYSTEM_OFFSCREEN)) &&
2904 PtInRect(&sbi.rcScrollBar, pt))
2906 ITextHost_TxSetCursor(editor->texthost,
2907 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2908 return TRUE;
2910 sbi.cbSize = sizeof(sbi);
2911 GetScrollBarInfo(editor->hWnd, OBJID_VSCROLL, &sbi);
2912 if (!(sbi.rgstate[0] & (STATE_SYSTEM_INVISIBLE|STATE_SYSTEM_OFFSCREEN)) &&
2913 PtInRect(&sbi.rcScrollBar, pt))
2915 ITextHost_TxSetCursor(editor->texthost,
2916 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2917 return TRUE;
2920 ITextHost_TxScreenToClient(editor->texthost, &pt);
2922 if (editor->nSelectionType == stLine && editor->bMouseCaptured) {
2923 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2924 return TRUE;
2926 if (!editor->bEmulateVersion10 /* v4.1 */ &&
2927 pt.y < editor->rcFormat.top &&
2928 pt.x < editor->rcFormat.left)
2930 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2931 return TRUE;
2933 if (pt.y < editor->rcFormat.top || pt.y > editor->rcFormat.bottom)
2935 if (editor->bEmulateVersion10) /* v1.0 - 3.0 */
2936 ITextHost_TxSetCursor(editor->texthost,
2937 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2938 else /* v4.1 */
2939 ITextHost_TxSetCursor(editor->texthost,
2940 LoadCursorW(NULL, (WCHAR*)IDC_IBEAM), TRUE);
2941 return TRUE;
2943 if (pt.x < editor->rcFormat.left)
2945 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2946 return TRUE;
2948 ME_CharFromPos(editor, pt.x, pt.y, &cursor, &isExact);
2949 if (isExact)
2951 ME_Run *run;
2953 run = &cursor.pRun->member.run;
2954 if (is_link( run ))
2956 ITextHost_TxSetCursor(editor->texthost,
2957 LoadCursorW(NULL, (WCHAR*)IDC_HAND),
2958 FALSE);
2959 return TRUE;
2962 if (ME_IsSelection(editor))
2964 int selStart, selEnd;
2965 int offset = ME_GetCursorOfs(&cursor);
2967 ME_GetSelectionOfs(editor, &selStart, &selEnd);
2968 if (selStart <= offset && selEnd >= offset) {
2969 ITextHost_TxSetCursor(editor->texthost,
2970 LoadCursorW(NULL, (WCHAR*)IDC_ARROW),
2971 FALSE);
2972 return TRUE;
2976 ITextHost_TxSetCursor(editor->texthost,
2977 LoadCursorW(NULL, (WCHAR*)IDC_IBEAM), TRUE);
2978 return TRUE;
2981 static void ME_SetDefaultFormatRect(ME_TextEditor *editor)
2983 ITextHost_TxGetClientRect(editor->texthost, &editor->rcFormat);
2984 editor->rcFormat.top += editor->exStyleFlags & WS_EX_CLIENTEDGE ? 1 : 0;
2985 editor->rcFormat.left += 1 + editor->selofs;
2986 editor->rcFormat.right -= 1;
2989 static LONG ME_GetSelectionType(ME_TextEditor *editor)
2991 LONG sel_type = SEL_EMPTY;
2992 LONG start, end;
2994 ME_GetSelectionOfs(editor, &start, &end);
2995 if (start == end)
2996 sel_type = SEL_EMPTY;
2997 else
2999 LONG object_count = 0, character_count = 0;
3000 int i;
3002 for (i = 0; i < end - start; i++)
3004 ME_Cursor cursor;
3006 ME_CursorFromCharOfs(editor, start + i, &cursor);
3007 if (cursor.pRun->member.run.reobj)
3008 object_count++;
3009 else
3010 character_count++;
3011 if (character_count >= 2 && object_count >= 2)
3012 return (SEL_TEXT | SEL_MULTICHAR | SEL_OBJECT | SEL_MULTIOBJECT);
3014 if (character_count)
3016 sel_type |= SEL_TEXT;
3017 if (character_count >= 2)
3018 sel_type |= SEL_MULTICHAR;
3020 if (object_count)
3022 sel_type |= SEL_OBJECT;
3023 if (object_count >= 2)
3024 sel_type |= SEL_MULTIOBJECT;
3027 return sel_type;
3030 static BOOL ME_ShowContextMenu(ME_TextEditor *editor, int x, int y)
3032 CHARRANGE selrange;
3033 HMENU menu;
3034 int seltype;
3036 if(!editor->lpOleCallback || !editor->hWnd)
3037 return FALSE;
3038 ME_GetSelectionOfs(editor, &selrange.cpMin, &selrange.cpMax);
3039 seltype = ME_GetSelectionType(editor);
3040 if(SUCCEEDED(IRichEditOleCallback_GetContextMenu(editor->lpOleCallback, seltype, NULL, &selrange, &menu)))
3042 TrackPopupMenu(menu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, x, y, 0, editor->hwndParent, NULL);
3043 DestroyMenu(menu);
3045 return TRUE;
3048 ME_TextEditor *ME_MakeEditor(ITextHost *texthost, BOOL bEmulateVersion10)
3050 ME_TextEditor *ed = heap_alloc(sizeof(*ed));
3051 int i;
3052 DWORD props;
3053 LONG selbarwidth;
3055 ed->hWnd = NULL;
3056 ed->hwndParent = NULL;
3057 ed->sizeWindow.cx = ed->sizeWindow.cy = 0;
3058 ed->texthost = texthost;
3059 ed->reOle = NULL;
3060 ed->bEmulateVersion10 = bEmulateVersion10;
3061 ed->styleFlags = 0;
3062 ed->exStyleFlags = 0;
3063 ed->first_marked_para = NULL;
3064 ed->total_rows = 0;
3065 ITextHost_TxGetPropertyBits(texthost,
3066 (TXTBIT_RICHTEXT|TXTBIT_MULTILINE|
3067 TXTBIT_READONLY|TXTBIT_USEPASSWORD|
3068 TXTBIT_HIDESELECTION|TXTBIT_SAVESELECTION|
3069 TXTBIT_AUTOWORDSEL|TXTBIT_VERTICAL|
3070 TXTBIT_WORDWRAP|TXTBIT_DISABLEDRAG),
3071 &props);
3072 ITextHost_TxGetScrollBars(texthost, &ed->styleFlags);
3073 ed->styleFlags &= (WS_VSCROLL|WS_HSCROLL|ES_AUTOVSCROLL|
3074 ES_AUTOHSCROLL|ES_DISABLENOSCROLL);
3075 ed->pBuffer = ME_MakeText();
3076 ed->nZoomNumerator = ed->nZoomDenominator = 0;
3077 ed->nAvailWidth = 0; /* wrap to client area */
3078 list_init( &ed->style_list );
3079 ME_MakeFirstParagraph(ed);
3080 /* The four cursors are for:
3081 * 0 - The position where the caret is shown
3082 * 1 - The anchored end of the selection (for normal selection)
3083 * 2 & 3 - The anchored start and end respectively for word, line,
3084 * or paragraph selection.
3086 ed->nCursors = 4;
3087 ed->pCursors = heap_alloc(ed->nCursors * sizeof(*ed->pCursors));
3088 ME_SetCursorToStart(ed, &ed->pCursors[0]);
3089 ed->pCursors[1] = ed->pCursors[0];
3090 ed->pCursors[2] = ed->pCursors[0];
3091 ed->pCursors[3] = ed->pCursors[1];
3092 ed->nLastTotalLength = ed->nTotalLength = 0;
3093 ed->nLastTotalWidth = ed->nTotalWidth = 0;
3094 ed->nUDArrowX = -1;
3095 ed->rgbBackColor = -1;
3096 ed->hbrBackground = GetSysColorBrush(COLOR_WINDOW);
3097 ed->bCaretAtEnd = FALSE;
3098 ed->nEventMask = 0;
3099 ed->nModifyStep = 0;
3100 ed->nTextLimit = TEXT_LIMIT_DEFAULT;
3101 list_init( &ed->undo_stack );
3102 list_init( &ed->redo_stack );
3103 ed->nUndoStackSize = 0;
3104 ed->nUndoLimit = STACK_SIZE_DEFAULT;
3105 ed->nUndoMode = umAddToUndo;
3106 ed->nParagraphs = 1;
3107 ed->nLastSelStart = ed->nLastSelEnd = 0;
3108 ed->pLastSelStartPara = ed->pLastSelEndPara = ed->pCursors[0].pPara;
3109 ed->bHideSelection = FALSE;
3110 ed->pfnWordBreak = NULL;
3111 ed->lpOleCallback = NULL;
3112 ed->mode = TM_MULTILEVELUNDO | TM_MULTICODEPAGE;
3113 ed->mode |= (props & TXTBIT_RICHTEXT) ? TM_RICHTEXT : TM_PLAINTEXT;
3114 ed->AutoURLDetect_bEnable = FALSE;
3115 ed->bHaveFocus = FALSE;
3116 ed->bDialogMode = FALSE;
3117 ed->bMouseCaptured = FALSE;
3118 ed->caret_hidden = FALSE;
3119 ed->caret_height = 0;
3120 for (i=0; i<HFONT_CACHE_SIZE; i++)
3122 ed->pFontCache[i].nRefs = 0;
3123 ed->pFontCache[i].nAge = 0;
3124 ed->pFontCache[i].hFont = NULL;
3127 ME_CheckCharOffsets(ed);
3128 SetRectEmpty(&ed->rcFormat);
3129 ed->bDefaultFormatRect = TRUE;
3130 ITextHost_TxGetSelectionBarWidth(ed->texthost, &selbarwidth);
3131 if (selbarwidth) {
3132 /* FIXME: Convert selbarwidth from HIMETRIC to pixels */
3133 ed->selofs = SELECTIONBAR_WIDTH;
3134 ed->styleFlags |= ES_SELECTIONBAR;
3135 } else {
3136 ed->selofs = 0;
3138 ed->nSelectionType = stPosition;
3140 ed->cPasswordMask = 0;
3141 if (props & TXTBIT_USEPASSWORD)
3142 ITextHost_TxGetPasswordChar(texthost, &ed->cPasswordMask);
3144 if (props & TXTBIT_AUTOWORDSEL)
3145 ed->styleFlags |= ECO_AUTOWORDSELECTION;
3146 if (props & TXTBIT_MULTILINE) {
3147 ed->styleFlags |= ES_MULTILINE;
3148 ed->bWordWrap = (props & TXTBIT_WORDWRAP) != 0;
3149 } else {
3150 ed->bWordWrap = FALSE;
3152 if (props & TXTBIT_READONLY)
3153 ed->styleFlags |= ES_READONLY;
3154 if (!(props & TXTBIT_HIDESELECTION))
3155 ed->styleFlags |= ES_NOHIDESEL;
3156 if (props & TXTBIT_SAVESELECTION)
3157 ed->styleFlags |= ES_SAVESEL;
3158 if (props & TXTBIT_VERTICAL)
3159 ed->styleFlags |= ES_VERTICAL;
3160 if (props & TXTBIT_DISABLEDRAG)
3161 ed->styleFlags |= ES_NOOLEDRAGDROP;
3163 ed->notified_cr.cpMin = ed->notified_cr.cpMax = 0;
3165 /* Default scrollbar information */
3166 ed->vert_si.cbSize = sizeof(SCROLLINFO);
3167 ed->vert_si.nMin = 0;
3168 ed->vert_si.nMax = 0;
3169 ed->vert_si.nPage = 0;
3170 ed->vert_si.nPos = 0;
3172 ed->horz_si.cbSize = sizeof(SCROLLINFO);
3173 ed->horz_si.nMin = 0;
3174 ed->horz_si.nMax = 0;
3175 ed->horz_si.nPage = 0;
3176 ed->horz_si.nPos = 0;
3178 ed->wheel_remain = 0;
3180 list_init( &ed->reobj_list );
3181 OleInitialize(NULL);
3183 return ed;
3186 void ME_DestroyEditor(ME_TextEditor *editor)
3188 ME_DisplayItem *p = editor->pBuffer->pFirst, *pNext = NULL;
3189 ME_Style *s, *cursor2;
3190 int i;
3192 ME_ClearTempStyle(editor);
3193 ME_EmptyUndoStack(editor);
3194 editor->pBuffer->pFirst = NULL;
3195 while(p) {
3196 pNext = p->next;
3197 if (p->type == diParagraph)
3198 destroy_para(editor, p);
3199 else
3200 ME_DestroyDisplayItem(p);
3201 p = pNext;
3204 LIST_FOR_EACH_ENTRY_SAFE( s, cursor2, &editor->style_list, ME_Style, entry )
3205 ME_DestroyStyle( s );
3207 ME_ReleaseStyle(editor->pBuffer->pDefaultStyle);
3208 for (i=0; i<HFONT_CACHE_SIZE; i++)
3210 if (editor->pFontCache[i].hFont)
3211 DeleteObject(editor->pFontCache[i].hFont);
3213 if (editor->rgbBackColor != -1)
3214 DeleteObject(editor->hbrBackground);
3215 if(editor->lpOleCallback)
3216 IRichEditOleCallback_Release(editor->lpOleCallback);
3217 ITextHost_Release(editor->texthost);
3218 if (editor->reOle)
3220 IUnknown_Release(editor->reOle);
3221 editor->reOle = NULL;
3223 OleUninitialize();
3225 heap_free(editor->pBuffer);
3226 heap_free(editor->pCursors);
3227 heap_free(editor);
3230 BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
3232 TRACE("\n");
3233 switch (fdwReason)
3235 case DLL_PROCESS_ATTACH:
3236 DisableThreadLibraryCalls(hinstDLL);
3237 me_heap = HeapCreate (0, 0x10000, 0);
3238 if (!ME_RegisterEditorClass(hinstDLL)) return FALSE;
3239 hLeft = LoadCursorW(hinstDLL, MAKEINTRESOURCEW(OCR_REVERSE));
3240 LookupInit();
3241 break;
3243 case DLL_PROCESS_DETACH:
3244 if (lpvReserved) break;
3245 UnregisterClassW(RICHEDIT_CLASS20W, 0);
3246 UnregisterClassW(MSFTEDIT_CLASS, 0);
3247 UnregisterClassA(RICHEDIT_CLASS20A, 0);
3248 UnregisterClassA("RichEdit50A", 0);
3249 if (ME_ListBoxRegistered)
3250 UnregisterClassW(REListBox20W, 0);
3251 if (ME_ComboBoxRegistered)
3252 UnregisterClassW(REComboBox20W, 0);
3253 LookupCleanup();
3254 HeapDestroy (me_heap);
3255 release_typelib();
3256 break;
3258 return TRUE;
3261 static inline int get_default_line_height( ME_TextEditor *editor )
3263 int height = 0;
3265 if (editor->pBuffer && editor->pBuffer->pDefaultStyle)
3266 height = editor->pBuffer->pDefaultStyle->tm.tmHeight;
3267 if (height <= 0) height = 24;
3269 return height;
3272 static inline int calc_wheel_change( int *remain, int amount_per_click )
3274 int change = amount_per_click * (float)*remain / WHEEL_DELTA;
3275 *remain -= WHEEL_DELTA * change / amount_per_click;
3276 return change;
3279 static const char * const edit_messages[] = {
3280 "EM_GETSEL",
3281 "EM_SETSEL",
3282 "EM_GETRECT",
3283 "EM_SETRECT",
3284 "EM_SETRECTNP",
3285 "EM_SCROLL",
3286 "EM_LINESCROLL",
3287 "EM_SCROLLCARET",
3288 "EM_GETMODIFY",
3289 "EM_SETMODIFY",
3290 "EM_GETLINECOUNT",
3291 "EM_LINEINDEX",
3292 "EM_SETHANDLE",
3293 "EM_GETHANDLE",
3294 "EM_GETTHUMB",
3295 "EM_UNKNOWN_BF",
3296 "EM_UNKNOWN_C0",
3297 "EM_LINELENGTH",
3298 "EM_REPLACESEL",
3299 "EM_UNKNOWN_C3",
3300 "EM_GETLINE",
3301 "EM_LIMITTEXT",
3302 "EM_CANUNDO",
3303 "EM_UNDO",
3304 "EM_FMTLINES",
3305 "EM_LINEFROMCHAR",
3306 "EM_UNKNOWN_CA",
3307 "EM_SETTABSTOPS",
3308 "EM_SETPASSWORDCHAR",
3309 "EM_EMPTYUNDOBUFFER",
3310 "EM_GETFIRSTVISIBLELINE",
3311 "EM_SETREADONLY",
3312 "EM_SETWORDBREAKPROC",
3313 "EM_GETWORDBREAKPROC",
3314 "EM_GETPASSWORDCHAR",
3315 "EM_SETMARGINS",
3316 "EM_GETMARGINS",
3317 "EM_GETLIMITTEXT",
3318 "EM_POSFROMCHAR",
3319 "EM_CHARFROMPOS",
3320 "EM_SETIMESTATUS",
3321 "EM_GETIMESTATUS"
3324 static const char * const richedit_messages[] = {
3325 "EM_CANPASTE",
3326 "EM_DISPLAYBAND",
3327 "EM_EXGETSEL",
3328 "EM_EXLIMITTEXT",
3329 "EM_EXLINEFROMCHAR",
3330 "EM_EXSETSEL",
3331 "EM_FINDTEXT",
3332 "EM_FORMATRANGE",
3333 "EM_GETCHARFORMAT",
3334 "EM_GETEVENTMASK",
3335 "EM_GETOLEINTERFACE",
3336 "EM_GETPARAFORMAT",
3337 "EM_GETSELTEXT",
3338 "EM_HIDESELECTION",
3339 "EM_PASTESPECIAL",
3340 "EM_REQUESTRESIZE",
3341 "EM_SELECTIONTYPE",
3342 "EM_SETBKGNDCOLOR",
3343 "EM_SETCHARFORMAT",
3344 "EM_SETEVENTMASK",
3345 "EM_SETOLECALLBACK",
3346 "EM_SETPARAFORMAT",
3347 "EM_SETTARGETDEVICE",
3348 "EM_STREAMIN",
3349 "EM_STREAMOUT",
3350 "EM_GETTEXTRANGE",
3351 "EM_FINDWORDBREAK",
3352 "EM_SETOPTIONS",
3353 "EM_GETOPTIONS",
3354 "EM_FINDTEXTEX",
3355 "EM_GETWORDBREAKPROCEX",
3356 "EM_SETWORDBREAKPROCEX",
3357 "EM_SETUNDOLIMIT",
3358 "EM_UNKNOWN_USER_83",
3359 "EM_REDO",
3360 "EM_CANREDO",
3361 "EM_GETUNDONAME",
3362 "EM_GETREDONAME",
3363 "EM_STOPGROUPTYPING",
3364 "EM_SETTEXTMODE",
3365 "EM_GETTEXTMODE",
3366 "EM_AUTOURLDETECT",
3367 "EM_GETAUTOURLDETECT",
3368 "EM_SETPALETTE",
3369 "EM_GETTEXTEX",
3370 "EM_GETTEXTLENGTHEX",
3371 "EM_SHOWSCROLLBAR",
3372 "EM_SETTEXTEX",
3373 "EM_UNKNOWN_USER_98",
3374 "EM_UNKNOWN_USER_99",
3375 "EM_SETPUNCTUATION",
3376 "EM_GETPUNCTUATION",
3377 "EM_SETWORDWRAPMODE",
3378 "EM_GETWORDWRAPMODE",
3379 "EM_SETIMECOLOR",
3380 "EM_GETIMECOLOR",
3381 "EM_SETIMEOPTIONS",
3382 "EM_GETIMEOPTIONS",
3383 "EM_CONVPOSITION",
3384 "EM_UNKNOWN_USER_109",
3385 "EM_UNKNOWN_USER_110",
3386 "EM_UNKNOWN_USER_111",
3387 "EM_UNKNOWN_USER_112",
3388 "EM_UNKNOWN_USER_113",
3389 "EM_UNKNOWN_USER_114",
3390 "EM_UNKNOWN_USER_115",
3391 "EM_UNKNOWN_USER_116",
3392 "EM_UNKNOWN_USER_117",
3393 "EM_UNKNOWN_USER_118",
3394 "EM_UNKNOWN_USER_119",
3395 "EM_SETLANGOPTIONS",
3396 "EM_GETLANGOPTIONS",
3397 "EM_GETIMECOMPMODE",
3398 "EM_FINDTEXTW",
3399 "EM_FINDTEXTEXW",
3400 "EM_RECONVERSION",
3401 "EM_SETIMEMODEBIAS",
3402 "EM_GETIMEMODEBIAS"
3405 static const char *
3406 get_msg_name(UINT msg)
3408 if (msg >= EM_GETSEL && msg <= EM_CHARFROMPOS)
3409 return edit_messages[msg - EM_GETSEL];
3410 if (msg >= EM_CANPASTE && msg <= EM_GETIMEMODEBIAS)
3411 return richedit_messages[msg - EM_CANPASTE];
3412 return "";
3415 static void ME_LinkNotify(ME_TextEditor *editor, UINT msg, WPARAM wParam, LPARAM lParam)
3417 int x,y;
3418 BOOL isExact;
3419 ME_Cursor cursor; /* The start of the clicked text. */
3421 ENLINK info;
3422 x = (short)LOWORD(lParam);
3423 y = (short)HIWORD(lParam);
3424 ME_CharFromPos(editor, x, y, &cursor, &isExact);
3425 if (!isExact) return;
3427 if (is_link( &cursor.pRun->member.run ))
3428 { /* The clicked run has CFE_LINK set */
3429 ME_DisplayItem *di;
3431 info.nmhdr.hwndFrom = NULL;
3432 info.nmhdr.idFrom = 0;
3433 info.nmhdr.code = EN_LINK;
3434 info.msg = msg;
3435 info.wParam = wParam;
3436 info.lParam = lParam;
3437 cursor.nOffset = 0;
3439 /* find the first contiguous run with CFE_LINK set */
3440 info.chrg.cpMin = ME_GetCursorOfs(&cursor);
3441 di = cursor.pRun;
3442 while (ME_PrevRun( NULL, &di, FALSE ) && is_link( &di->member.run ))
3443 info.chrg.cpMin -= di->member.run.len;
3445 /* find the last contiguous run with CFE_LINK set */
3446 info.chrg.cpMax = ME_GetCursorOfs(&cursor) + cursor.pRun->member.run.len;
3447 di = cursor.pRun;
3448 while (ME_NextRun( NULL, &di, FALSE ) && is_link( &di->member.run ))
3449 info.chrg.cpMax += di->member.run.len;
3451 ITextHost_TxNotify(editor->texthost, info.nmhdr.code, &info);
3455 void ME_ReplaceSel(ME_TextEditor *editor, BOOL can_undo, const WCHAR *str, int len)
3457 int from, to, nStartCursor;
3458 ME_Style *style;
3460 nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3461 style = ME_GetSelectionInsertStyle(editor);
3462 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to-from, FALSE);
3463 ME_InsertTextFromCursor(editor, 0, str, len, style);
3464 ME_ReleaseStyle(style);
3465 /* drop temporary style if line end */
3467 * FIXME question: does abc\n mean: put abc,
3468 * clear temp style, put \n? (would require a change)
3470 if (len>0 && str[len-1] == '\n')
3471 ME_ClearTempStyle(editor);
3472 ME_CommitUndo(editor);
3473 ME_UpdateSelectionLinkAttribute(editor);
3474 if (!can_undo)
3475 ME_EmptyUndoStack(editor);
3476 ME_UpdateRepaint(editor, FALSE);
3479 static void ME_SetText(ME_TextEditor *editor, void *text, BOOL unicode)
3481 LONG codepage = unicode ? CP_UNICODE : CP_ACP;
3482 int textLen;
3484 LPWSTR wszText = ME_ToUnicode(codepage, text, &textLen);
3485 ME_InsertTextFromCursor(editor, 0, wszText, textLen, editor->pBuffer->pDefaultStyle);
3486 ME_EndToUnicode(codepage, wszText);
3489 static LRESULT ME_WmCreate(ME_TextEditor *editor, LPARAM lParam, BOOL unicode)
3491 CREATESTRUCTW *createW = (CREATESTRUCTW*)lParam;
3492 CREATESTRUCTA *createA = (CREATESTRUCTA*)lParam;
3493 void *text = NULL;
3494 INT max;
3496 if (lParam)
3497 text = unicode ? (void*)createW->lpszName : (void*)createA->lpszName;
3499 ME_SetDefaultFormatRect(editor);
3501 max = (editor->styleFlags & ES_DISABLENOSCROLL) ? 1 : 0;
3502 if (~editor->styleFlags & ES_DISABLENOSCROLL || editor->styleFlags & WS_VSCROLL)
3503 ITextHost_TxSetScrollRange(editor->texthost, SB_VERT, 0, max, TRUE);
3505 if (~editor->styleFlags & ES_DISABLENOSCROLL || editor->styleFlags & WS_HSCROLL)
3506 ITextHost_TxSetScrollRange(editor->texthost, SB_HORZ, 0, max, TRUE);
3508 if (editor->styleFlags & ES_DISABLENOSCROLL)
3510 if (editor->styleFlags & WS_VSCROLL)
3512 ITextHost_TxEnableScrollBar(editor->texthost, SB_VERT, ESB_DISABLE_BOTH);
3513 ITextHost_TxShowScrollBar(editor->texthost, SB_VERT, TRUE);
3515 if (editor->styleFlags & WS_HSCROLL)
3517 ITextHost_TxEnableScrollBar(editor->texthost, SB_HORZ, ESB_DISABLE_BOTH);
3518 ITextHost_TxShowScrollBar(editor->texthost, SB_HORZ, TRUE);
3522 if (text)
3524 ME_SetText(editor, text, unicode);
3525 ME_SetCursorToStart(editor, &editor->pCursors[0]);
3526 ME_SetCursorToStart(editor, &editor->pCursors[1]);
3529 ME_CommitUndo(editor);
3530 ME_WrapMarkedParagraphs(editor);
3531 update_caret(editor);
3532 return 0;
3535 static LRESULT handle_EM_SETCHARFORMAT( ME_TextEditor *editor, WPARAM flags, const CHARFORMAT2W *fmt_in )
3537 CHARFORMAT2W fmt;
3538 BOOL changed = TRUE;
3539 ME_Cursor start, end;
3541 if (!cfany_to_cf2w( &fmt, fmt_in )) return 0;
3543 if (flags & SCF_ALL)
3545 if (editor->mode & TM_PLAINTEXT)
3547 ME_SetDefaultCharFormat( editor, &fmt );
3549 else
3551 ME_SetCursorToStart( editor, &start );
3552 ME_SetCharFormat( editor, &start, NULL, &fmt );
3553 editor->nModifyStep = 1;
3556 else if (flags & SCF_SELECTION)
3558 if (editor->mode & TM_PLAINTEXT) return 0;
3559 if (flags & SCF_WORD)
3561 end = editor->pCursors[0];
3562 ME_MoveCursorWords( editor, &end, +1 );
3563 start = end;
3564 ME_MoveCursorWords( editor, &start, -1 );
3565 ME_SetCharFormat( editor, &start, &end, &fmt );
3567 changed = ME_IsSelection( editor );
3568 ME_SetSelectionCharFormat( editor, &fmt );
3569 if (changed) editor->nModifyStep = 1;
3571 else /* SCF_DEFAULT */
3573 ME_SetDefaultCharFormat( editor, &fmt );
3576 ME_CommitUndo( editor );
3577 if (changed)
3579 ME_WrapMarkedParagraphs( editor );
3580 ME_UpdateScrollBar( editor );
3582 return 1;
3585 #define UNSUPPORTED_MSG(e) \
3586 case e: \
3587 FIXME(#e ": stub\n"); \
3588 *phresult = S_FALSE; \
3589 return 0;
3591 /* Handle messages for windowless and windowed richedit controls.
3593 * The LRESULT that is returned is a return value for window procs,
3594 * and the phresult parameter is the COM return code needed by the
3595 * text services interface. */
3596 LRESULT ME_HandleMessage(ME_TextEditor *editor, UINT msg, WPARAM wParam,
3597 LPARAM lParam, BOOL unicode, HRESULT* phresult)
3599 *phresult = S_OK;
3601 switch(msg) {
3603 UNSUPPORTED_MSG(EM_DISPLAYBAND)
3604 UNSUPPORTED_MSG(EM_FINDWORDBREAK)
3605 UNSUPPORTED_MSG(EM_FMTLINES)
3606 UNSUPPORTED_MSG(EM_FORMATRANGE)
3607 UNSUPPORTED_MSG(EM_GETBIDIOPTIONS)
3608 UNSUPPORTED_MSG(EM_GETEDITSTYLE)
3609 UNSUPPORTED_MSG(EM_GETIMECOMPMODE)
3610 UNSUPPORTED_MSG(EM_GETIMESTATUS)
3611 UNSUPPORTED_MSG(EM_SETIMESTATUS)
3612 UNSUPPORTED_MSG(EM_GETLANGOPTIONS)
3613 UNSUPPORTED_MSG(EM_GETREDONAME)
3614 UNSUPPORTED_MSG(EM_GETTYPOGRAPHYOPTIONS)
3615 UNSUPPORTED_MSG(EM_GETUNDONAME)
3616 UNSUPPORTED_MSG(EM_GETWORDBREAKPROCEX)
3617 UNSUPPORTED_MSG(EM_SETBIDIOPTIONS)
3618 UNSUPPORTED_MSG(EM_SETEDITSTYLE)
3619 UNSUPPORTED_MSG(EM_SETLANGOPTIONS)
3620 UNSUPPORTED_MSG(EM_SETMARGINS)
3621 UNSUPPORTED_MSG(EM_SETPALETTE)
3622 UNSUPPORTED_MSG(EM_SETTABSTOPS)
3623 UNSUPPORTED_MSG(EM_SETTYPOGRAPHYOPTIONS)
3624 UNSUPPORTED_MSG(EM_SETWORDBREAKPROCEX)
3626 /* Messages specific to Richedit controls */
3628 case EM_STREAMIN:
3629 return ME_StreamIn(editor, wParam, (EDITSTREAM*)lParam, TRUE);
3630 case EM_STREAMOUT:
3631 return ME_StreamOut(editor, wParam, (EDITSTREAM *)lParam);
3632 case WM_GETDLGCODE:
3634 UINT code = DLGC_WANTCHARS|DLGC_WANTTAB|DLGC_WANTARROWS;
3636 if (lParam)
3637 editor->bDialogMode = TRUE;
3638 if (editor->styleFlags & ES_MULTILINE)
3639 code |= DLGC_WANTMESSAGE;
3640 if (!(editor->styleFlags & ES_SAVESEL))
3641 code |= DLGC_HASSETSEL;
3642 return code;
3644 case EM_EMPTYUNDOBUFFER:
3645 ME_EmptyUndoStack(editor);
3646 return 0;
3647 case EM_GETSEL:
3649 /* Note: wParam/lParam can be NULL */
3650 UINT from, to;
3651 PUINT pfrom = wParam ? (PUINT)wParam : &from;
3652 PUINT pto = lParam ? (PUINT)lParam : &to;
3653 ME_GetSelectionOfs(editor, (int *)pfrom, (int *)pto);
3654 if ((*pfrom|*pto) & 0xFFFF0000)
3655 return -1;
3656 return MAKELONG(*pfrom,*pto);
3658 case EM_EXGETSEL:
3660 CHARRANGE *pRange = (CHARRANGE *)lParam;
3661 ME_GetSelectionOfs(editor, &pRange->cpMin, &pRange->cpMax);
3662 TRACE("EM_EXGETSEL = (%d,%d)\n", pRange->cpMin, pRange->cpMax);
3663 return 0;
3665 case EM_SETUNDOLIMIT:
3667 if ((int)wParam < 0)
3668 editor->nUndoLimit = STACK_SIZE_DEFAULT;
3669 else
3670 editor->nUndoLimit = min(wParam, STACK_SIZE_MAX);
3671 /* Setting a max stack size keeps wine from getting killed
3672 for hogging memory. Windows allocates all this memory at once, so
3673 no program would realistically set a value above our maximum. */
3674 return editor->nUndoLimit;
3676 case EM_CANUNDO:
3677 return !list_empty( &editor->undo_stack );
3678 case EM_CANREDO:
3679 return !list_empty( &editor->redo_stack );
3680 case WM_UNDO: /* FIXME: actually not the same */
3681 case EM_UNDO:
3682 return ME_Undo(editor);
3683 case EM_REDO:
3684 return ME_Redo(editor);
3685 case EM_GETOPTIONS:
3687 /* these flags are equivalent to the ES_* counterparts */
3688 DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
3689 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN | ECO_SELECTIONBAR;
3690 DWORD settings = editor->styleFlags & mask;
3692 return settings;
3694 case EM_SETFONTSIZE:
3696 CHARFORMAT2W cf;
3697 LONG tmp_size, size;
3698 BOOL is_increase = ((LONG)wParam > 0);
3700 if (editor->mode & TM_PLAINTEXT)
3701 return FALSE;
3703 cf.cbSize = sizeof(cf);
3704 cf.dwMask = CFM_SIZE;
3705 ME_GetSelectionCharFormat(editor, &cf);
3706 tmp_size = (cf.yHeight / 20) + wParam;
3708 if (tmp_size <= 1)
3709 size = 1;
3710 else if (tmp_size > 12 && tmp_size < 28 && tmp_size % 2)
3711 size = tmp_size + (is_increase ? 1 : -1);
3712 else if (tmp_size > 28 && tmp_size < 36)
3713 size = is_increase ? 36 : 28;
3714 else if (tmp_size > 36 && tmp_size < 48)
3715 size = is_increase ? 48 : 36;
3716 else if (tmp_size > 48 && tmp_size < 72)
3717 size = is_increase ? 72 : 48;
3718 else if (tmp_size > 72 && tmp_size < 80)
3719 size = is_increase ? 80 : 72;
3720 else if (tmp_size > 80 && tmp_size < 1638)
3721 size = 10 * (is_increase ? (tmp_size / 10 + 1) : (tmp_size / 10));
3722 else if (tmp_size >= 1638)
3723 size = 1638;
3724 else
3725 size = tmp_size;
3727 cf.yHeight = size * 20; /* convert twips to points */
3728 ME_SetSelectionCharFormat(editor, &cf);
3729 ME_CommitUndo(editor);
3730 ME_WrapMarkedParagraphs(editor);
3731 ME_UpdateScrollBar(editor);
3733 return TRUE;
3735 case EM_SETOPTIONS:
3737 /* these flags are equivalent to ES_* counterparts, except for
3738 * ECO_AUTOWORDSELECTION that doesn't have an ES_* counterpart,
3739 * but is still stored in editor->styleFlags. */
3740 const DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
3741 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN |
3742 ECO_SELECTIONBAR | ECO_AUTOWORDSELECTION;
3743 DWORD settings = mask & editor->styleFlags;
3744 DWORD oldSettings = settings;
3745 DWORD changedSettings;
3747 switch(wParam)
3749 case ECOOP_SET:
3750 settings = lParam;
3751 break;
3752 case ECOOP_OR:
3753 settings |= lParam;
3754 break;
3755 case ECOOP_AND:
3756 settings &= lParam;
3757 break;
3758 case ECOOP_XOR:
3759 settings ^= lParam;
3761 changedSettings = oldSettings ^ settings;
3763 if (changedSettings) {
3764 editor->styleFlags = (editor->styleFlags & ~mask) | (settings & mask);
3766 if (changedSettings & ECO_SELECTIONBAR)
3768 ITextHost_TxInvalidateRect(editor->texthost, &editor->rcFormat, TRUE);
3769 if (settings & ECO_SELECTIONBAR) {
3770 assert(!editor->selofs);
3771 editor->selofs = SELECTIONBAR_WIDTH;
3772 editor->rcFormat.left += editor->selofs;
3773 } else {
3774 editor->rcFormat.left -= editor->selofs;
3775 editor->selofs = 0;
3777 ME_RewrapRepaint(editor);
3780 if ((changedSettings & settings & ES_NOHIDESEL) && !editor->bHaveFocus)
3781 ME_InvalidateSelection( editor );
3783 if (changedSettings & settings & ECO_VERTICAL)
3784 FIXME("ECO_VERTICAL not implemented yet!\n");
3785 if (changedSettings & settings & ECO_AUTOHSCROLL)
3786 FIXME("ECO_AUTOHSCROLL not implemented yet!\n");
3787 if (changedSettings & settings & ECO_AUTOVSCROLL)
3788 FIXME("ECO_AUTOVSCROLL not implemented yet!\n");
3789 if (changedSettings & settings & ECO_WANTRETURN)
3790 FIXME("ECO_WANTRETURN not implemented yet!\n");
3791 if (changedSettings & settings & ECO_AUTOWORDSELECTION)
3792 FIXME("ECO_AUTOWORDSELECTION not implemented yet!\n");
3795 return settings;
3797 case EM_SETSEL:
3799 return set_selection( editor, wParam, lParam );
3801 case EM_SETSCROLLPOS:
3803 POINT *point = (POINT *)lParam;
3804 ME_ScrollAbs(editor, point->x, point->y);
3805 return 0;
3807 case EM_AUTOURLDETECT:
3809 if (wParam==1 || wParam ==0)
3811 editor->AutoURLDetect_bEnable = (BOOL)wParam;
3812 return 0;
3814 return E_INVALIDARG;
3816 case EM_GETAUTOURLDETECT:
3818 return editor->AutoURLDetect_bEnable;
3820 case EM_EXSETSEL:
3822 CHARRANGE range = *(CHARRANGE *)lParam;
3824 return set_selection( editor, range.cpMin, range.cpMax );
3826 case EM_SHOWSCROLLBAR:
3828 DWORD flags;
3830 switch (wParam)
3832 case SB_HORZ:
3833 flags = WS_HSCROLL;
3834 break;
3835 case SB_VERT:
3836 flags = WS_VSCROLL;
3837 break;
3838 case SB_BOTH:
3839 flags = WS_HSCROLL|WS_VSCROLL;
3840 break;
3841 default:
3842 return 0;
3845 if (lParam) {
3846 editor->styleFlags |= flags;
3847 if (flags & WS_HSCROLL)
3848 ITextHost_TxShowScrollBar(editor->texthost, SB_HORZ,
3849 editor->nTotalWidth > editor->sizeWindow.cx);
3850 if (flags & WS_VSCROLL)
3851 ITextHost_TxShowScrollBar(editor->texthost, SB_VERT,
3852 editor->nTotalLength > editor->sizeWindow.cy);
3853 } else {
3854 editor->styleFlags &= ~flags;
3855 ITextHost_TxShowScrollBar(editor->texthost, wParam, FALSE);
3857 return 0;
3859 case EM_SETTEXTEX:
3861 LPWSTR wszText;
3862 SETTEXTEX *pStruct = (SETTEXTEX *)wParam;
3863 int from, to, len;
3864 ME_Style *style;
3865 BOOL bRtf, bUnicode, bSelection, bUTF8;
3866 int oldModify = editor->nModifyStep;
3867 static const char utf8_bom[] = {0xef, 0xbb, 0xbf};
3869 if (!pStruct) return 0;
3871 /* If we detect ascii rtf at the start of the string,
3872 * we know it isn't unicode. */
3873 bRtf = (lParam && (!strncmp((char *)lParam, "{\\rtf", 5) ||
3874 !strncmp((char *)lParam, "{\\urtf", 6)));
3875 bUnicode = !bRtf && pStruct->codepage == CP_UNICODE;
3876 bUTF8 = (lParam && (!strncmp((char *)lParam, utf8_bom, 3)));
3878 TRACE("EM_SETTEXTEX - %s, flags %d, cp %d\n",
3879 bUnicode ? debugstr_w((LPCWSTR)lParam) : debugstr_a((LPCSTR)lParam),
3880 pStruct->flags, pStruct->codepage);
3882 bSelection = (pStruct->flags & ST_SELECTION) != 0;
3883 if (bSelection) {
3884 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3885 style = ME_GetSelectionInsertStyle(editor);
3886 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to - from, FALSE);
3887 } else {
3888 ME_Cursor start;
3889 ME_SetCursorToStart(editor, &start);
3890 ME_InternalDeleteText(editor, &start, ME_GetTextLength(editor), FALSE);
3891 style = editor->pBuffer->pDefaultStyle;
3894 if (bRtf) {
3895 ME_StreamInRTFString(editor, bSelection, (char *)lParam);
3896 if (bSelection) {
3897 /* FIXME: The length returned doesn't include the rtf control
3898 * characters, only the actual text. */
3899 len = lParam ? strlen((char *)lParam) : 0;
3901 } else {
3902 if (bUTF8 && !bUnicode) {
3903 wszText = ME_ToUnicode(CP_UTF8, (void *)(lParam+3), &len);
3904 ME_InsertTextFromCursor(editor, 0, wszText, len, style);
3905 ME_EndToUnicode(CP_UTF8, wszText);
3906 } else {
3907 wszText = ME_ToUnicode(pStruct->codepage, (void *)lParam, &len);
3908 ME_InsertTextFromCursor(editor, 0, wszText, len, style);
3909 ME_EndToUnicode(pStruct->codepage, wszText);
3913 if (bSelection) {
3914 ME_ReleaseStyle(style);
3915 ME_UpdateSelectionLinkAttribute(editor);
3916 } else {
3917 ME_Cursor cursor;
3918 len = 1;
3919 ME_SetCursorToStart(editor, &cursor);
3920 ME_UpdateLinkAttribute(editor, &cursor, INT_MAX);
3922 ME_CommitUndo(editor);
3923 if (!(pStruct->flags & ST_KEEPUNDO))
3925 editor->nModifyStep = oldModify;
3926 ME_EmptyUndoStack(editor);
3928 ME_UpdateRepaint(editor, FALSE);
3929 return len;
3931 case EM_SELECTIONTYPE:
3932 return ME_GetSelectionType(editor);
3933 case EM_SETBKGNDCOLOR:
3935 LRESULT lColor;
3936 if (editor->rgbBackColor != -1) {
3937 DeleteObject(editor->hbrBackground);
3938 lColor = editor->rgbBackColor;
3940 else lColor = ITextHost_TxGetSysColor(editor->texthost, COLOR_WINDOW);
3942 if (wParam)
3944 editor->rgbBackColor = -1;
3945 editor->hbrBackground = GetSysColorBrush(COLOR_WINDOW);
3947 else
3949 editor->rgbBackColor = lParam;
3950 editor->hbrBackground = CreateSolidBrush(editor->rgbBackColor);
3952 ITextHost_TxInvalidateRect(editor->texthost, NULL, TRUE);
3953 return lColor;
3955 case EM_GETMODIFY:
3956 return editor->nModifyStep == 0 ? 0 : -1;
3957 case EM_SETMODIFY:
3959 if (wParam)
3960 editor->nModifyStep = 1;
3961 else
3962 editor->nModifyStep = 0;
3964 return 0;
3966 case EM_SETREADONLY:
3968 if (wParam)
3969 editor->styleFlags |= ES_READONLY;
3970 else
3971 editor->styleFlags &= ~ES_READONLY;
3972 return 1;
3974 case EM_SETEVENTMASK:
3976 DWORD nOldMask = editor->nEventMask;
3978 editor->nEventMask = lParam;
3979 return nOldMask;
3981 case EM_GETEVENTMASK:
3982 return editor->nEventMask;
3983 case EM_SETCHARFORMAT:
3984 return handle_EM_SETCHARFORMAT( editor, wParam, (CHARFORMAT2W *)lParam );
3985 case EM_GETCHARFORMAT:
3987 CHARFORMAT2W tmp, *dst = (CHARFORMAT2W *)lParam;
3988 if (dst->cbSize != sizeof(CHARFORMATA) &&
3989 dst->cbSize != sizeof(CHARFORMATW) &&
3990 dst->cbSize != sizeof(CHARFORMAT2A) &&
3991 dst->cbSize != sizeof(CHARFORMAT2W))
3992 return 0;
3993 tmp.cbSize = sizeof(tmp);
3994 if (!wParam)
3995 ME_GetDefaultCharFormat(editor, &tmp);
3996 else
3997 ME_GetSelectionCharFormat(editor, &tmp);
3998 cf2w_to_cfany(dst, &tmp);
3999 return tmp.dwMask;
4001 case EM_SETPARAFORMAT:
4003 BOOL result = ME_SetSelectionParaFormat(editor, (PARAFORMAT2 *)lParam);
4004 ME_WrapMarkedParagraphs(editor);
4005 ME_UpdateScrollBar(editor);
4006 ME_CommitUndo(editor);
4007 return result;
4009 case EM_GETPARAFORMAT:
4010 ME_GetSelectionParaFormat(editor, (PARAFORMAT2 *)lParam);
4011 return ((PARAFORMAT2 *)lParam)->dwMask;
4012 case EM_GETFIRSTVISIBLELINE:
4014 ME_DisplayItem *p = editor->pBuffer->pFirst;
4015 int y = editor->vert_si.nPos;
4016 int ypara = 0;
4017 int count = 0;
4018 int ystart, yend;
4019 while(p) {
4020 p = ME_FindItemFwd(p, diStartRowOrParagraphOrEnd);
4021 if (p->type == diTextEnd)
4022 break;
4023 if (p->type == diParagraph) {
4024 ypara = p->member.para.pt.y;
4025 continue;
4027 ystart = ypara + p->member.row.pt.y;
4028 yend = ystart + p->member.row.nHeight;
4029 if (y < yend) {
4030 break;
4032 count++;
4034 return count;
4036 case EM_HIDESELECTION:
4038 editor->bHideSelection = (wParam != 0);
4039 ME_InvalidateSelection(editor);
4040 return 0;
4042 case EM_LINESCROLL:
4044 if (!(editor->styleFlags & ES_MULTILINE))
4045 return FALSE;
4046 ME_ScrollDown( editor, lParam * get_default_line_height( editor ) );
4047 return TRUE;
4049 case WM_CLEAR:
4051 int from, to;
4052 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
4053 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to-from, FALSE);
4054 ME_CommitUndo(editor);
4055 ME_UpdateRepaint(editor, TRUE);
4056 return 0;
4058 case EM_REPLACESEL:
4060 int len = 0;
4061 LONG codepage = unicode ? CP_UNICODE : CP_ACP;
4062 LPWSTR wszText = ME_ToUnicode(codepage, (void *)lParam, &len);
4064 TRACE("EM_REPLACESEL - %s\n", debugstr_w(wszText));
4066 ME_ReplaceSel(editor, !!wParam, wszText, len);
4067 ME_EndToUnicode(codepage, wszText);
4068 return len;
4070 case EM_SCROLLCARET:
4071 ME_EnsureVisible(editor, &editor->pCursors[0]);
4072 return 0;
4073 case WM_SETFONT:
4075 LOGFONTW lf;
4076 CHARFORMAT2W fmt;
4077 HDC hDC;
4078 BOOL bRepaint = LOWORD(lParam);
4080 if (!wParam)
4081 wParam = (WPARAM)GetStockObject(SYSTEM_FONT);
4083 if (!GetObjectW((HGDIOBJ)wParam, sizeof(LOGFONTW), &lf))
4084 return 0;
4086 hDC = ITextHost_TxGetDC(editor->texthost);
4087 ME_CharFormatFromLogFont(hDC, &lf, &fmt);
4088 ITextHost_TxReleaseDC(editor->texthost, hDC);
4089 if (editor->mode & TM_RICHTEXT) {
4090 ME_Cursor start;
4091 ME_SetCursorToStart(editor, &start);
4092 ME_SetCharFormat(editor, &start, NULL, &fmt);
4094 ME_SetDefaultCharFormat(editor, &fmt);
4096 ME_CommitUndo(editor);
4097 ME_MarkAllForWrapping(editor);
4098 ME_WrapMarkedParagraphs(editor);
4099 ME_UpdateScrollBar(editor);
4100 if (bRepaint)
4101 ME_Repaint(editor);
4102 return 0;
4104 case WM_SETTEXT:
4106 ME_Cursor cursor;
4107 ME_SetCursorToStart(editor, &cursor);
4108 ME_InternalDeleteText(editor, &cursor, ME_GetTextLength(editor), FALSE);
4109 if (lParam)
4111 TRACE("WM_SETTEXT lParam==%lx\n",lParam);
4112 if (!strncmp((char *)lParam, "{\\rtf", 5) ||
4113 !strncmp((char *)lParam, "{\\urtf", 6))
4115 /* Undocumented: WM_SETTEXT supports RTF text */
4116 ME_StreamInRTFString(editor, 0, (char *)lParam);
4118 else
4119 ME_SetText(editor, (void*)lParam, unicode);
4121 else
4122 TRACE("WM_SETTEXT - NULL\n");
4123 ME_SetCursorToStart(editor, &cursor);
4124 ME_UpdateLinkAttribute(editor, &cursor, INT_MAX);
4125 set_selection_cursors(editor, 0, 0);
4126 editor->nModifyStep = 0;
4127 ME_CommitUndo(editor);
4128 ME_EmptyUndoStack(editor);
4129 ME_UpdateRepaint(editor, FALSE);
4130 return 1;
4132 case EM_CANPASTE:
4133 return paste_special( editor, 0, NULL, TRUE );
4134 case WM_PASTE:
4135 case WM_MBUTTONDOWN:
4136 wParam = 0;
4137 lParam = 0;
4138 /* fall through */
4139 case EM_PASTESPECIAL:
4140 paste_special( editor, wParam, (REPASTESPECIAL *)lParam, FALSE );
4141 return 0;
4142 case WM_CUT:
4143 case WM_COPY:
4144 copy_or_cut(editor, msg == WM_CUT);
4145 return 0;
4146 case WM_GETTEXTLENGTH:
4148 GETTEXTLENGTHEX how;
4150 /* CR/LF conversion required in 2.0 mode, verbatim in 1.0 mode */
4151 how.flags = GTL_CLOSE | (editor->bEmulateVersion10 ? 0 : GTL_USECRLF) | GTL_NUMCHARS;
4152 how.codepage = unicode ? CP_UNICODE : CP_ACP;
4153 return ME_GetTextLengthEx(editor, &how);
4155 case EM_GETTEXTLENGTHEX:
4156 return ME_GetTextLengthEx(editor, (GETTEXTLENGTHEX *)wParam);
4157 case WM_GETTEXT:
4159 GETTEXTEX ex;
4160 ex.cb = wParam * (unicode ? sizeof(WCHAR) : sizeof(CHAR));
4161 ex.flags = GT_USECRLF;
4162 ex.codepage = unicode ? CP_UNICODE : CP_ACP;
4163 ex.lpDefaultChar = NULL;
4164 ex.lpUsedDefChar = NULL;
4165 return ME_GetTextEx(editor, &ex, lParam);
4167 case EM_GETTEXTEX:
4168 return ME_GetTextEx(editor, (GETTEXTEX*)wParam, lParam);
4169 case EM_GETSELTEXT:
4171 int nFrom, nTo, nStartCur = ME_GetSelectionOfs(editor, &nFrom, &nTo);
4172 ME_Cursor *from = &editor->pCursors[nStartCur];
4173 return ME_GetTextRange(editor, (WCHAR *)lParam, from,
4174 nTo - nFrom, unicode);
4176 case EM_GETSCROLLPOS:
4178 POINT *point = (POINT *)lParam;
4179 point->x = editor->horz_si.nPos;
4180 point->y = editor->vert_si.nPos;
4181 /* 16-bit scaled value is returned as stored in scrollinfo */
4182 if (editor->horz_si.nMax > 0xffff)
4183 point->x = MulDiv(point->x, 0xffff, editor->horz_si.nMax);
4184 if (editor->vert_si.nMax > 0xffff)
4185 point->y = MulDiv(point->y, 0xffff, editor->vert_si.nMax);
4186 return 1;
4188 case EM_GETTEXTRANGE:
4190 TEXTRANGEW *rng = (TEXTRANGEW *)lParam;
4191 ME_Cursor start;
4192 int nStart = rng->chrg.cpMin;
4193 int nEnd = rng->chrg.cpMax;
4194 int textlength = ME_GetTextLength(editor);
4196 TRACE("EM_GETTEXTRANGE min=%d max=%d unicode=%d textlength=%d\n",
4197 rng->chrg.cpMin, rng->chrg.cpMax, unicode, textlength);
4198 if (nStart < 0) return 0;
4199 if ((nStart == 0 && nEnd == -1) || nEnd > textlength)
4200 nEnd = textlength;
4201 if (nStart >= nEnd) return 0;
4203 ME_CursorFromCharOfs(editor, nStart, &start);
4204 return ME_GetTextRange(editor, rng->lpstrText, &start, nEnd - nStart, unicode);
4206 case EM_GETLINE:
4208 ME_DisplayItem *run;
4209 const unsigned int nMaxChars = *(WORD *) lParam;
4210 unsigned int nCharsLeft = nMaxChars;
4211 char *dest = (char *) lParam;
4212 BOOL wroteNull = FALSE;
4214 TRACE("EM_GETLINE: row=%d, nMaxChars=%d (%s)\n", (int) wParam, nMaxChars,
4215 unicode ? "Unicode" : "Ansi");
4217 run = ME_FindRowWithNumber(editor, wParam);
4218 if (run == NULL)
4219 return 0;
4221 while (nCharsLeft && (run = ME_FindItemFwd(run, diRunOrStartRow))
4222 && run->type == diRun)
4224 WCHAR *str = get_text( &run->member.run, 0 );
4225 unsigned int nCopy;
4227 nCopy = min(nCharsLeft, run->member.run.len);
4229 if (unicode)
4230 memcpy(dest, str, nCopy * sizeof(WCHAR));
4231 else
4232 nCopy = WideCharToMultiByte(CP_ACP, 0, str, nCopy, dest,
4233 nCharsLeft, NULL, NULL);
4234 dest += nCopy * (unicode ? sizeof(WCHAR) : 1);
4235 nCharsLeft -= nCopy;
4238 /* append line termination, space allowing */
4239 if (nCharsLeft > 0)
4241 if (unicode)
4242 *((WCHAR *)dest) = '\0';
4243 else
4244 *dest = '\0';
4245 nCharsLeft--;
4246 wroteNull = TRUE;
4249 TRACE("EM_GETLINE: got %u characters\n", nMaxChars - nCharsLeft);
4250 return nMaxChars - nCharsLeft - (wroteNull ? 1 : 0);
4252 case EM_GETLINECOUNT:
4254 ME_DisplayItem *item = editor->pBuffer->pLast;
4255 int nRows = editor->total_rows;
4256 ME_DisplayItem *prev_para = NULL, *last_para = NULL;
4258 last_para = ME_FindItemBack(item, diRun);
4259 prev_para = ME_FindItemBack(last_para, diRun);
4260 assert(last_para);
4261 assert(last_para->member.run.nFlags & MERF_ENDPARA);
4262 if (editor->bEmulateVersion10 && prev_para &&
4263 last_para->member.run.nCharOfs == 0 &&
4264 prev_para->member.run.len == 1 &&
4265 *get_text( &prev_para->member.run, 0 ) == '\r')
4267 /* In 1.0 emulation, the last solitary \r at the very end of the text
4268 (if one exists) is NOT a line break.
4269 FIXME: this is an ugly hack. This should have a more regular model. */
4270 nRows--;
4273 TRACE("EM_GETLINECOUNT: nRows==%d\n", nRows);
4274 return max(1, nRows);
4276 case EM_LINEFROMCHAR:
4278 if (wParam == -1)
4279 return ME_RowNumberFromCharOfs(editor, ME_GetCursorOfs(&editor->pCursors[1]));
4280 else
4281 return ME_RowNumberFromCharOfs(editor, wParam);
4283 case EM_EXLINEFROMCHAR:
4285 if (lParam == -1)
4286 return ME_RowNumberFromCharOfs(editor, ME_GetCursorOfs(&editor->pCursors[1]));
4287 else
4288 return ME_RowNumberFromCharOfs(editor, lParam);
4290 case EM_LINEINDEX:
4292 ME_DisplayItem *item, *para;
4293 int nCharOfs;
4295 if (wParam == -1)
4296 item = ME_FindItemBack(editor->pCursors[0].pRun, diStartRow);
4297 else
4298 item = ME_FindRowWithNumber(editor, wParam);
4299 if (!item)
4300 return -1;
4301 para = ME_GetParagraph(item);
4302 item = ME_FindItemFwd(item, diRun);
4303 nCharOfs = para->member.para.nCharOfs + item->member.run.nCharOfs;
4304 TRACE("EM_LINEINDEX: nCharOfs==%d\n", nCharOfs);
4305 return nCharOfs;
4307 case EM_LINELENGTH:
4309 ME_DisplayItem *item, *item_end;
4310 int nChars = 0, nThisLineOfs = 0, nNextLineOfs = 0;
4311 ME_DisplayItem *para, *run;
4313 if (wParam > ME_GetTextLength(editor))
4314 return 0;
4315 if (wParam == -1)
4317 FIXME("EM_LINELENGTH: returning number of unselected characters on lines with selection unsupported.\n");
4318 return 0;
4320 ME_RunOfsFromCharOfs(editor, wParam, &para, &run, NULL);
4321 item = ME_RowStart(run);
4322 nThisLineOfs = ME_CharOfsFromRunOfs(editor, para, ME_FindItemFwd(item, diRun), 0);
4323 item_end = ME_FindItemFwd(item, diStartRowOrParagraphOrEnd);
4324 if (item_end->type == diStartRow) {
4325 nNextLineOfs = ME_CharOfsFromRunOfs(editor, para, ME_FindItemFwd(item_end, diRun), 0);
4326 } else {
4327 ME_DisplayItem *endRun = ME_FindItemBack(item_end, diRun);
4328 assert(endRun && endRun->member.run.nFlags & MERF_ENDPARA);
4329 nNextLineOfs = item_end->member.para.nCharOfs - endRun->member.run.len;
4331 nChars = nNextLineOfs - nThisLineOfs;
4332 TRACE("EM_LINELENGTH(%ld)==%d\n",wParam, nChars);
4333 return nChars;
4335 case EM_EXLIMITTEXT:
4337 if ((int)lParam < 0)
4338 return 0;
4339 if (lParam == 0)
4340 editor->nTextLimit = 65536;
4341 else
4342 editor->nTextLimit = (int) lParam;
4343 return 0;
4345 case EM_LIMITTEXT:
4347 if (wParam == 0)
4348 editor->nTextLimit = 65536;
4349 else
4350 editor->nTextLimit = (int) wParam;
4351 return 0;
4353 case EM_GETLIMITTEXT:
4355 return editor->nTextLimit;
4357 case EM_FINDTEXT:
4359 LRESULT r;
4360 if(!unicode){
4361 FINDTEXTA *ft = (FINDTEXTA *)lParam;
4362 int nChars = MultiByteToWideChar(CP_ACP, 0, ft->lpstrText, -1, NULL, 0);
4363 WCHAR *tmp;
4365 if ((tmp = heap_alloc(nChars * sizeof(*tmp))) != NULL)
4366 MultiByteToWideChar(CP_ACP, 0, ft->lpstrText, -1, tmp, nChars);
4367 r = ME_FindText(editor, wParam, &ft->chrg, tmp, NULL);
4368 heap_free(tmp);
4369 }else{
4370 FINDTEXTW *ft = (FINDTEXTW *)lParam;
4371 r = ME_FindText(editor, wParam, &ft->chrg, ft->lpstrText, NULL);
4373 return r;
4375 case EM_FINDTEXTEX:
4377 LRESULT r;
4378 if(!unicode){
4379 FINDTEXTEXA *ex = (FINDTEXTEXA *)lParam;
4380 int nChars = MultiByteToWideChar(CP_ACP, 0, ex->lpstrText, -1, NULL, 0);
4381 WCHAR *tmp;
4383 if ((tmp = heap_alloc(nChars * sizeof(*tmp))) != NULL)
4384 MultiByteToWideChar(CP_ACP, 0, ex->lpstrText, -1, tmp, nChars);
4385 r = ME_FindText(editor, wParam, &ex->chrg, tmp, &ex->chrgText);
4386 heap_free(tmp);
4387 }else{
4388 FINDTEXTEXW *ex = (FINDTEXTEXW *)lParam;
4389 r = ME_FindText(editor, wParam, &ex->chrg, ex->lpstrText, &ex->chrgText);
4391 return r;
4393 case EM_FINDTEXTW:
4395 FINDTEXTW *ft = (FINDTEXTW *)lParam;
4396 return ME_FindText(editor, wParam, &ft->chrg, ft->lpstrText, NULL);
4398 case EM_FINDTEXTEXW:
4400 FINDTEXTEXW *ex = (FINDTEXTEXW *)lParam;
4401 return ME_FindText(editor, wParam, &ex->chrg, ex->lpstrText, &ex->chrgText);
4403 case EM_GETZOOM:
4404 if (!wParam || !lParam)
4405 return FALSE;
4406 *(int *)wParam = editor->nZoomNumerator;
4407 *(int *)lParam = editor->nZoomDenominator;
4408 return TRUE;
4409 case EM_SETZOOM:
4410 return ME_SetZoom(editor, wParam, lParam);
4411 case EM_CHARFROMPOS:
4413 ME_Cursor cursor;
4414 if (ME_CharFromPos(editor, ((POINTL *)lParam)->x, ((POINTL *)lParam)->y,
4415 &cursor, NULL))
4416 return ME_GetCursorOfs(&cursor);
4417 else
4418 return -1;
4420 case EM_POSFROMCHAR:
4422 ME_DisplayItem *pPara, *pRun;
4423 int nCharOfs, nOffset, nLength;
4424 POINTL pt = {0,0};
4426 nCharOfs = wParam;
4427 /* detect which API version we're dealing with */
4428 if (wParam >= 0x40000)
4429 nCharOfs = lParam;
4430 nLength = ME_GetTextLength(editor);
4431 nCharOfs = min(nCharOfs, nLength);
4432 nCharOfs = max(nCharOfs, 0);
4434 ME_RunOfsFromCharOfs(editor, nCharOfs, &pPara, &pRun, &nOffset);
4435 assert(pRun->type == diRun);
4436 pt.y = pRun->member.run.pt.y;
4437 pt.x = pRun->member.run.pt.x + ME_PointFromChar(editor, &pRun->member.run, nOffset, TRUE);
4438 pt.y += pPara->member.para.pt.y + editor->rcFormat.top;
4439 pt.x += editor->rcFormat.left;
4441 pt.x -= editor->horz_si.nPos;
4442 pt.y -= editor->vert_si.nPos;
4444 if (wParam >= 0x40000) {
4445 *(POINTL *)wParam = pt;
4447 return (wParam >= 0x40000) ? 0 : MAKELONG( pt.x, pt.y );
4449 case WM_CREATE:
4450 return ME_WmCreate(editor, lParam, unicode);
4451 case WM_DESTROY:
4452 ME_DestroyEditor(editor);
4453 return 0;
4454 case WM_SETCURSOR:
4456 POINT cursor_pos;
4457 if (wParam == (WPARAM)editor->hWnd && GetCursorPos(&cursor_pos) &&
4458 ScreenToClient(editor->hWnd, &cursor_pos))
4459 ME_LinkNotify(editor, msg, 0, MAKELPARAM(cursor_pos.x, cursor_pos.y));
4460 return ME_SetCursor(editor);
4462 case WM_LBUTTONDBLCLK:
4463 case WM_LBUTTONDOWN:
4465 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4466 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4467 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4468 return 0;
4469 ITextHost_TxSetFocus(editor->texthost);
4470 ME_LButtonDown(editor, (short)LOWORD(lParam), (short)HIWORD(lParam),
4471 ME_CalculateClickCount(editor, msg, wParam, lParam));
4472 ITextHost_TxSetCapture(editor->texthost, TRUE);
4473 editor->bMouseCaptured = TRUE;
4474 ME_LinkNotify(editor, msg, wParam, lParam);
4475 if (!ME_SetCursor(editor)) goto do_default;
4476 break;
4478 case WM_MOUSEMOVE:
4479 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4480 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4481 return 0;
4482 if (editor->bMouseCaptured)
4483 ME_MouseMove(editor, (short)LOWORD(lParam), (short)HIWORD(lParam));
4484 else
4485 ME_LinkNotify(editor, msg, wParam, lParam);
4486 /* Set cursor if mouse is captured, since WM_SETCURSOR won't be received. */
4487 if (editor->bMouseCaptured)
4488 ME_SetCursor(editor);
4489 break;
4490 case WM_LBUTTONUP:
4491 if (editor->bMouseCaptured) {
4492 ITextHost_TxSetCapture(editor->texthost, FALSE);
4493 editor->bMouseCaptured = FALSE;
4495 if (editor->nSelectionType == stDocument)
4496 editor->nSelectionType = stPosition;
4497 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4498 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4499 return 0;
4500 else
4502 ME_SetCursor(editor);
4503 ME_LinkNotify(editor, msg, wParam, lParam);
4505 break;
4506 case WM_RBUTTONUP:
4507 case WM_RBUTTONDOWN:
4508 case WM_RBUTTONDBLCLK:
4509 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4510 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4511 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4512 return 0;
4513 ME_LinkNotify(editor, msg, wParam, lParam);
4514 goto do_default;
4515 case WM_CONTEXTMENU:
4516 if (!ME_ShowContextMenu(editor, (short)LOWORD(lParam), (short)HIWORD(lParam)))
4517 goto do_default;
4518 break;
4519 case WM_SETFOCUS:
4520 editor->bHaveFocus = TRUE;
4521 create_caret(editor);
4522 update_caret(editor);
4523 ME_SendOldNotify(editor, EN_SETFOCUS);
4524 if (!editor->bHideSelection && !(editor->styleFlags & ES_NOHIDESEL))
4525 ME_InvalidateSelection( editor );
4526 return 0;
4527 case WM_KILLFOCUS:
4528 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4529 editor->bHaveFocus = FALSE;
4530 editor->wheel_remain = 0;
4531 hide_caret(editor);
4532 DestroyCaret();
4533 ME_SendOldNotify(editor, EN_KILLFOCUS);
4534 if (!editor->bHideSelection && !(editor->styleFlags & ES_NOHIDESEL))
4535 ME_InvalidateSelection( editor );
4536 return 0;
4537 case WM_COMMAND:
4538 TRACE("editor wnd command = %d\n", LOWORD(wParam));
4539 return 0;
4540 case WM_KEYUP:
4541 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4542 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4543 return 0;
4544 goto do_default;
4545 case WM_KEYDOWN:
4546 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4547 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4548 return 0;
4549 if (ME_KeyDown(editor, LOWORD(wParam)))
4550 return 0;
4551 goto do_default;
4552 case WM_CHAR:
4553 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4554 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4555 return 0;
4556 return ME_Char(editor, wParam, lParam, unicode);
4557 case WM_UNICHAR:
4558 if (unicode)
4560 if(wParam == UNICODE_NOCHAR) return TRUE;
4561 if(wParam <= 0x000fffff)
4563 if(wParam > 0xffff) /* convert to surrogates */
4565 wParam -= 0x10000;
4566 ME_Char(editor, (wParam >> 10) + 0xd800, 0, TRUE);
4567 ME_Char(editor, (wParam & 0x03ff) + 0xdc00, 0, TRUE);
4568 } else {
4569 ME_Char(editor, wParam, 0, TRUE);
4572 return 0;
4574 break;
4575 case EM_STOPGROUPTYPING:
4576 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4577 return 0;
4578 case WM_HSCROLL:
4580 const int scrollUnit = 7;
4582 switch(LOWORD(wParam))
4584 case SB_LEFT:
4585 ME_ScrollAbs(editor, 0, 0);
4586 break;
4587 case SB_RIGHT:
4588 ME_ScrollAbs(editor,
4589 editor->horz_si.nMax - (int)editor->horz_si.nPage,
4590 editor->vert_si.nMax - (int)editor->vert_si.nPage);
4591 break;
4592 case SB_LINELEFT:
4593 ME_ScrollLeft(editor, scrollUnit);
4594 break;
4595 case SB_LINERIGHT:
4596 ME_ScrollRight(editor, scrollUnit);
4597 break;
4598 case SB_PAGELEFT:
4599 ME_ScrollLeft(editor, editor->sizeWindow.cx);
4600 break;
4601 case SB_PAGERIGHT:
4602 ME_ScrollRight(editor, editor->sizeWindow.cx);
4603 break;
4604 case SB_THUMBTRACK:
4605 case SB_THUMBPOSITION:
4607 int pos = HIWORD(wParam);
4608 if (editor->horz_si.nMax > 0xffff)
4609 pos = MulDiv(pos, editor->horz_si.nMax, 0xffff);
4610 ME_HScrollAbs(editor, pos);
4611 break;
4614 break;
4616 case EM_SCROLL: /* fall through */
4617 case WM_VSCROLL:
4619 int origNPos;
4620 int lineHeight = get_default_line_height( editor );
4622 origNPos = editor->vert_si.nPos;
4624 switch(LOWORD(wParam))
4626 case SB_TOP:
4627 ME_ScrollAbs(editor, 0, 0);
4628 break;
4629 case SB_BOTTOM:
4630 ME_ScrollAbs(editor,
4631 editor->horz_si.nMax - (int)editor->horz_si.nPage,
4632 editor->vert_si.nMax - (int)editor->vert_si.nPage);
4633 break;
4634 case SB_LINEUP:
4635 ME_ScrollUp(editor,lineHeight);
4636 break;
4637 case SB_LINEDOWN:
4638 ME_ScrollDown(editor,lineHeight);
4639 break;
4640 case SB_PAGEUP:
4641 ME_ScrollUp(editor,editor->sizeWindow.cy);
4642 break;
4643 case SB_PAGEDOWN:
4644 ME_ScrollDown(editor,editor->sizeWindow.cy);
4645 break;
4646 case SB_THUMBTRACK:
4647 case SB_THUMBPOSITION:
4649 int pos = HIWORD(wParam);
4650 if (editor->vert_si.nMax > 0xffff)
4651 pos = MulDiv(pos, editor->vert_si.nMax, 0xffff);
4652 ME_VScrollAbs(editor, pos);
4653 break;
4656 if (msg == EM_SCROLL)
4657 return 0x00010000 | (((editor->vert_si.nPos - origNPos)/lineHeight) & 0xffff);
4658 break;
4660 case WM_MOUSEWHEEL:
4662 int delta;
4663 BOOL ctrl_is_down;
4665 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4666 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4667 return 0;
4669 ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
4671 delta = GET_WHEEL_DELTA_WPARAM(wParam);
4673 /* if scrolling changes direction, ignore left overs */
4674 if ((delta < 0 && editor->wheel_remain < 0) ||
4675 (delta > 0 && editor->wheel_remain > 0))
4676 editor->wheel_remain += delta;
4677 else
4678 editor->wheel_remain = delta;
4680 if (editor->wheel_remain)
4682 if (ctrl_is_down) {
4683 int numerator;
4684 if (!editor->nZoomNumerator || !editor->nZoomDenominator)
4686 numerator = 100;
4687 } else {
4688 numerator = editor->nZoomNumerator * 100 / editor->nZoomDenominator;
4690 numerator += calc_wheel_change( &editor->wheel_remain, 10 );
4691 if (numerator >= 10 && numerator <= 500)
4692 ME_SetZoom(editor, numerator, 100);
4693 } else {
4694 UINT max_lines = 3;
4695 int lines = 0;
4697 SystemParametersInfoW( SPI_GETWHEELSCROLLLINES, 0, &max_lines, 0 );
4698 if (max_lines)
4699 lines = calc_wheel_change( &editor->wheel_remain, (int)max_lines );
4700 if (lines)
4701 ME_ScrollDown( editor, -lines * get_default_line_height( editor ) );
4704 break;
4706 case EM_GETRECT:
4708 *((RECT *)lParam) = editor->rcFormat;
4709 if (editor->bDefaultFormatRect)
4710 ((RECT *)lParam)->left -= editor->selofs;
4711 return 0;
4713 case EM_SETRECT:
4714 case EM_SETRECTNP:
4716 if (lParam)
4718 int border = 0;
4719 RECT clientRect;
4720 RECT *rc = (RECT *)lParam;
4722 border = editor->exStyleFlags & WS_EX_CLIENTEDGE ? 1 : 0;
4723 ITextHost_TxGetClientRect(editor->texthost, &clientRect);
4724 if (wParam == 0)
4726 editor->rcFormat.top = max(0, rc->top - border);
4727 editor->rcFormat.left = max(0, rc->left - border);
4728 editor->rcFormat.bottom = min(clientRect.bottom, rc->bottom);
4729 editor->rcFormat.right = min(clientRect.right, rc->right + border);
4730 } else if (wParam == 1) {
4731 /* MSDN incorrectly says a wParam value of 1 causes the
4732 * lParam rect to be used as a relative offset,
4733 * however, the tests show it just prevents min/max bound
4734 * checking. */
4735 editor->rcFormat.top = rc->top - border;
4736 editor->rcFormat.left = rc->left - border;
4737 editor->rcFormat.bottom = rc->bottom;
4738 editor->rcFormat.right = rc->right + border;
4739 } else {
4740 return 0;
4742 editor->bDefaultFormatRect = FALSE;
4744 else
4746 ME_SetDefaultFormatRect(editor);
4747 editor->bDefaultFormatRect = TRUE;
4749 ME_MarkAllForWrapping(editor);
4750 ME_WrapMarkedParagraphs(editor);
4751 ME_UpdateScrollBar(editor);
4752 if (msg != EM_SETRECTNP)
4753 ME_Repaint(editor);
4754 return 0;
4756 case EM_REQUESTRESIZE:
4757 ME_SendRequestResize(editor, TRUE);
4758 return 0;
4759 case WM_SETREDRAW:
4760 goto do_default;
4761 case WM_WINDOWPOSCHANGED:
4763 RECT clientRect;
4764 WINDOWPOS *winpos = (WINDOWPOS *)lParam;
4766 if (winpos->flags & SWP_NOCLIENTSIZE) goto do_default;
4767 ITextHost_TxGetClientRect(editor->texthost, &clientRect);
4768 if (editor->bDefaultFormatRect) {
4769 ME_SetDefaultFormatRect(editor);
4770 } else {
4771 editor->rcFormat.right += clientRect.right - editor->prevClientRect.right;
4772 editor->rcFormat.bottom += clientRect.bottom - editor->prevClientRect.bottom;
4774 editor->prevClientRect = clientRect;
4775 ME_RewrapRepaint(editor);
4776 goto do_default;
4778 /* IME messages to make richedit controls IME aware */
4779 case WM_IME_SETCONTEXT:
4780 case WM_IME_CONTROL:
4781 case WM_IME_SELECT:
4782 case WM_IME_COMPOSITIONFULL:
4783 return 0;
4784 case WM_IME_STARTCOMPOSITION:
4786 editor->imeStartIndex=ME_GetCursorOfs(&editor->pCursors[0]);
4787 ME_DeleteSelection(editor);
4788 ME_CommitUndo(editor);
4789 ME_UpdateRepaint(editor, FALSE);
4790 return 0;
4792 case WM_IME_COMPOSITION:
4794 HIMC hIMC;
4796 ME_Style *style = ME_GetInsertStyle(editor, 0);
4797 hIMC = ITextHost_TxImmGetContext(editor->texthost);
4798 ME_DeleteSelection(editor);
4799 ME_SaveTempStyle(editor, style);
4800 if (lParam & (GCS_RESULTSTR|GCS_COMPSTR))
4802 LPWSTR lpCompStr = NULL;
4803 DWORD dwBufLen;
4804 DWORD dwIndex = lParam & GCS_RESULTSTR;
4805 if (!dwIndex)
4806 dwIndex = GCS_COMPSTR;
4808 dwBufLen = ImmGetCompositionStringW(hIMC, dwIndex, NULL, 0);
4809 lpCompStr = HeapAlloc(GetProcessHeap(),0,dwBufLen + sizeof(WCHAR));
4810 ImmGetCompositionStringW(hIMC, dwIndex, lpCompStr, dwBufLen);
4811 lpCompStr[dwBufLen/sizeof(WCHAR)] = 0;
4812 ME_InsertTextFromCursor(editor,0,lpCompStr,dwBufLen/sizeof(WCHAR),style);
4813 HeapFree(GetProcessHeap(), 0, lpCompStr);
4815 if (dwIndex == GCS_COMPSTR)
4816 set_selection_cursors(editor,editor->imeStartIndex,
4817 editor->imeStartIndex + dwBufLen/sizeof(WCHAR));
4819 ME_ReleaseStyle(style);
4820 ME_CommitUndo(editor);
4821 ME_UpdateRepaint(editor, FALSE);
4822 return 0;
4824 case WM_IME_ENDCOMPOSITION:
4826 ME_DeleteSelection(editor);
4827 editor->imeStartIndex=-1;
4828 return 0;
4830 case EM_GETOLEINTERFACE:
4832 if (!editor->reOle)
4833 if (!CreateIRichEditOle(NULL, editor, (LPVOID *)&editor->reOle))
4834 return 0;
4835 if (IUnknown_QueryInterface(editor->reOle, &IID_IRichEditOle, (LPVOID *)lParam) == S_OK)
4836 return 1;
4837 return 0;
4839 case EM_GETPASSWORDCHAR:
4841 return editor->cPasswordMask;
4843 case EM_SETOLECALLBACK:
4844 if(editor->lpOleCallback)
4845 IRichEditOleCallback_Release(editor->lpOleCallback);
4846 editor->lpOleCallback = (IRichEditOleCallback*)lParam;
4847 if(editor->lpOleCallback)
4848 IRichEditOleCallback_AddRef(editor->lpOleCallback);
4849 return TRUE;
4850 case EM_GETWORDBREAKPROC:
4851 return (LRESULT)editor->pfnWordBreak;
4852 case EM_SETWORDBREAKPROC:
4854 EDITWORDBREAKPROCW pfnOld = editor->pfnWordBreak;
4856 editor->pfnWordBreak = (EDITWORDBREAKPROCW)lParam;
4857 return (LRESULT)pfnOld;
4859 case EM_GETTEXTMODE:
4860 return editor->mode;
4861 case EM_SETTEXTMODE:
4863 int mask = 0;
4864 int changes = 0;
4866 if (ME_GetTextLength(editor) ||
4867 !list_empty( &editor->undo_stack ) || !list_empty( &editor->redo_stack ))
4868 return E_UNEXPECTED;
4870 /* Check for mutually exclusive flags in adjacent bits of wParam */
4871 if ((wParam & (TM_RICHTEXT | TM_MULTILEVELUNDO | TM_MULTICODEPAGE)) &
4872 (wParam & (TM_PLAINTEXT | TM_SINGLELEVELUNDO | TM_SINGLECODEPAGE)) << 1)
4873 return E_INVALIDARG;
4875 if (wParam & (TM_RICHTEXT | TM_PLAINTEXT))
4877 mask |= TM_RICHTEXT | TM_PLAINTEXT;
4878 changes |= wParam & (TM_RICHTEXT | TM_PLAINTEXT);
4879 if (wParam & TM_PLAINTEXT) {
4880 /* Clear selection since it should be possible to select the
4881 * end of text run for rich text */
4882 ME_InvalidateSelection(editor);
4883 ME_SetCursorToStart(editor, &editor->pCursors[0]);
4884 editor->pCursors[1] = editor->pCursors[0];
4885 /* plain text can only have the default style. */
4886 ME_ClearTempStyle(editor);
4887 ME_AddRefStyle(editor->pBuffer->pDefaultStyle);
4888 ME_ReleaseStyle(editor->pCursors[0].pRun->member.run.style);
4889 editor->pCursors[0].pRun->member.run.style = editor->pBuffer->pDefaultStyle;
4892 /* FIXME: Currently no support for undo level and code page options */
4893 editor->mode = (editor->mode & ~mask) | changes;
4894 return 0;
4896 case EM_SETPASSWORDCHAR:
4898 editor->cPasswordMask = wParam;
4899 ME_RewrapRepaint(editor);
4900 return 0;
4902 case EM_SETTARGETDEVICE:
4903 if (wParam == 0)
4905 BOOL new = (lParam == 0 && (editor->styleFlags & ES_MULTILINE));
4906 if (editor->nAvailWidth || editor->bWordWrap != new)
4908 editor->bWordWrap = new;
4909 editor->nAvailWidth = 0; /* wrap to client area */
4910 ME_RewrapRepaint(editor);
4912 } else {
4913 int width = max(0, lParam);
4914 if ((editor->styleFlags & ES_MULTILINE) &&
4915 (!editor->bWordWrap || editor->nAvailWidth != width))
4917 editor->nAvailWidth = width;
4918 editor->bWordWrap = TRUE;
4919 ME_RewrapRepaint(editor);
4921 FIXME("EM_SETTARGETDEVICE doesn't use non-NULL target devices\n");
4923 return TRUE;
4924 default:
4925 do_default:
4926 *phresult = S_FALSE;
4927 break;
4929 return 0L;
4932 static BOOL create_windowed_editor(HWND hwnd, CREATESTRUCTW *create, BOOL emulate_10)
4934 ITextHost *host = ME_CreateTextHost( hwnd, create, emulate_10 );
4935 ME_TextEditor *editor;
4937 if (!host) return FALSE;
4939 editor = ME_MakeEditor( host, emulate_10 );
4940 if (!editor)
4942 ITextHost_Release( host );
4943 return FALSE;
4946 editor->exStyleFlags = GetWindowLongW( hwnd, GWL_EXSTYLE );
4947 editor->styleFlags |= GetWindowLongW( hwnd, GWL_STYLE ) & ES_WANTRETURN;
4948 editor->hWnd = hwnd; /* FIXME: Remove editor's dependence on hWnd */
4949 editor->hwndParent = create->hwndParent;
4951 SetWindowLongPtrW( hwnd, 0, (LONG_PTR)editor );
4953 return TRUE;
4956 static LRESULT RichEditWndProc_common(HWND hWnd, UINT msg, WPARAM wParam,
4957 LPARAM lParam, BOOL unicode)
4959 ME_TextEditor *editor;
4960 HRESULT hresult;
4961 LRESULT lresult = 0;
4963 TRACE("enter hwnd %p msg %04x (%s) %lx %lx, unicode %d\n",
4964 hWnd, msg, get_msg_name(msg), wParam, lParam, unicode);
4966 editor = (ME_TextEditor *)GetWindowLongPtrW(hWnd, 0);
4967 if (!editor)
4969 if (msg == WM_NCCREATE)
4971 CREATESTRUCTW *pcs = (CREATESTRUCTW *)lParam;
4973 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd, pcs->style);
4974 return create_windowed_editor( hWnd, pcs, FALSE );
4976 else
4978 return DefWindowProcW(hWnd, msg, wParam, lParam);
4982 switch (msg)
4984 case WM_PAINT:
4986 HDC hdc;
4987 RECT rc;
4988 PAINTSTRUCT ps;
4989 HBRUSH old_brush;
4991 update_caret(editor);
4992 hdc = BeginPaint(editor->hWnd, &ps);
4993 if (!editor->bEmulateVersion10 || (editor->nEventMask & ENM_UPDATE))
4994 ME_SendOldNotify(editor, EN_UPDATE);
4995 old_brush = SelectObject(hdc, editor->hbrBackground);
4997 /* Erase area outside of the formatting rectangle */
4998 if (ps.rcPaint.top < editor->rcFormat.top)
5000 rc = ps.rcPaint;
5001 rc.bottom = editor->rcFormat.top;
5002 PatBlt(hdc, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, PATCOPY);
5003 ps.rcPaint.top = editor->rcFormat.top;
5005 if (ps.rcPaint.bottom > editor->rcFormat.bottom) {
5006 rc = ps.rcPaint;
5007 rc.top = editor->rcFormat.bottom;
5008 PatBlt(hdc, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, PATCOPY);
5009 ps.rcPaint.bottom = editor->rcFormat.bottom;
5011 if (ps.rcPaint.left < editor->rcFormat.left) {
5012 rc = ps.rcPaint;
5013 rc.right = editor->rcFormat.left;
5014 PatBlt(hdc, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, PATCOPY);
5015 ps.rcPaint.left = editor->rcFormat.left;
5017 if (ps.rcPaint.right > editor->rcFormat.right) {
5018 rc = ps.rcPaint;
5019 rc.left = editor->rcFormat.right;
5020 PatBlt(hdc, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, PATCOPY);
5021 ps.rcPaint.right = editor->rcFormat.right;
5024 ME_PaintContent(editor, hdc, &ps.rcPaint);
5025 SelectObject(hdc, old_brush);
5026 EndPaint(editor->hWnd, &ps);
5027 return 0;
5029 case WM_ERASEBKGND:
5031 HDC hDC = (HDC)wParam;
5032 RECT rc;
5034 if (GetUpdateRect(editor->hWnd, &rc, TRUE))
5035 FillRect(hDC, &rc, editor->hbrBackground);
5036 return 1;
5038 case EM_SETOPTIONS:
5040 DWORD dwStyle;
5041 const DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
5042 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN |
5043 ECO_SELECTIONBAR;
5044 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
5045 dwStyle = GetWindowLongW(hWnd, GWL_STYLE);
5046 dwStyle = (dwStyle & ~mask) | (lresult & mask);
5047 SetWindowLongW(hWnd, GWL_STYLE, dwStyle);
5048 return lresult;
5050 case EM_SETREADONLY:
5052 DWORD dwStyle;
5053 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
5054 dwStyle = GetWindowLongW(hWnd, GWL_STYLE);
5055 dwStyle &= ~ES_READONLY;
5056 if (wParam)
5057 dwStyle |= ES_READONLY;
5058 SetWindowLongW(hWnd, GWL_STYLE, dwStyle);
5059 return lresult;
5061 default:
5062 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
5065 if (hresult == S_FALSE)
5066 lresult = DefWindowProcW(hWnd, msg, wParam, lParam);
5068 TRACE("exit hwnd %p msg %04x (%s) %lx %lx, unicode %d -> %lu\n",
5069 hWnd, msg, get_msg_name(msg), wParam, lParam, unicode, lresult);
5071 return lresult;
5074 static LRESULT WINAPI RichEditWndProcW(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5076 BOOL unicode = TRUE;
5078 /* Under Win9x RichEdit20W returns ANSI strings, see the tests. */
5079 if (msg == WM_GETTEXT && (GetVersion() & 0x80000000))
5080 unicode = FALSE;
5082 return RichEditWndProc_common(hWnd, msg, wParam, lParam, unicode);
5085 static LRESULT WINAPI RichEditWndProcA(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5087 return RichEditWndProc_common(hWnd, msg, wParam, lParam, FALSE);
5090 /******************************************************************
5091 * RichEditANSIWndProc (RICHED20.10)
5093 LRESULT WINAPI RichEditANSIWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5095 return RichEditWndProcA(hWnd, msg, wParam, lParam);
5098 /******************************************************************
5099 * RichEdit10ANSIWndProc (RICHED20.9)
5101 LRESULT WINAPI RichEdit10ANSIWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5103 if (msg == WM_NCCREATE && !GetWindowLongPtrW(hWnd, 0))
5105 CREATESTRUCTW *pcs = (CREATESTRUCTW *)lParam;
5107 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd, pcs->style);
5108 return create_windowed_editor( hWnd, pcs, TRUE );
5110 return RichEditANSIWndProc(hWnd, msg, wParam, lParam);
5113 void ME_SendOldNotify(ME_TextEditor *editor, int nCode)
5115 ITextHost_TxNotify(editor->texthost, nCode, NULL);
5118 /* Fill buffer with srcChars unicode characters from the start cursor.
5120 * buffer: destination buffer
5121 * buflen: length of buffer in characters excluding the NULL terminator.
5122 * start: start of editor text to copy into buffer.
5123 * srcChars: Number of characters to use from the editor text.
5124 * bCRLF: if true, replaces all end of lines with \r\n pairs.
5126 * returns the number of characters written excluding the NULL terminator.
5128 * The written text is always NULL terminated.
5130 int ME_GetTextW(ME_TextEditor *editor, WCHAR *buffer, int buflen,
5131 const ME_Cursor *start, int srcChars, BOOL bCRLF,
5132 BOOL bEOP)
5134 ME_DisplayItem *pRun, *pNextRun;
5135 const WCHAR *pStart = buffer;
5136 const WCHAR cr_lf[] = {'\r', '\n', 0};
5137 const WCHAR *str;
5138 int nLen;
5140 /* bCRLF flag is only honored in 2.0 and up. 1.0 must always return text verbatim */
5141 if (editor->bEmulateVersion10) bCRLF = FALSE;
5143 pRun = start->pRun;
5144 assert(pRun);
5145 pNextRun = ME_FindItemFwd(pRun, diRun);
5147 nLen = pRun->member.run.len - start->nOffset;
5148 str = get_text( &pRun->member.run, start->nOffset );
5150 while (srcChars && buflen && pNextRun)
5152 int nFlags = pRun->member.run.nFlags;
5154 if (bCRLF && nFlags & MERF_ENDPARA && ~nFlags & MERF_ENDCELL)
5156 if (buflen == 1) break;
5157 /* FIXME: native fails to reduce srcChars here for WM_GETTEXT or
5158 * EM_GETTEXTEX, however, this is done for copying text which
5159 * also uses this function. */
5160 srcChars -= min(nLen, srcChars);
5161 nLen = 2;
5162 str = cr_lf;
5163 } else {
5164 nLen = min(nLen, srcChars);
5165 srcChars -= nLen;
5168 nLen = min(nLen, buflen);
5169 buflen -= nLen;
5171 CopyMemory(buffer, str, sizeof(WCHAR) * nLen);
5173 buffer += nLen;
5175 pRun = pNextRun;
5176 pNextRun = ME_FindItemFwd(pRun, diRun);
5178 nLen = pRun->member.run.len;
5179 str = get_text( &pRun->member.run, 0 );
5181 /* append '\r' to the last paragraph. */
5182 if (pRun->next->type == diTextEnd && bEOP)
5184 *buffer = '\r';
5185 buffer ++;
5187 *buffer = 0;
5188 return buffer - pStart;
5191 static BOOL ME_RegisterEditorClass(HINSTANCE hInstance)
5193 WNDCLASSW wcW;
5194 WNDCLASSA wcA;
5196 wcW.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS;
5197 wcW.lpfnWndProc = RichEditWndProcW;
5198 wcW.cbClsExtra = 0;
5199 wcW.cbWndExtra = sizeof(ME_TextEditor *);
5200 wcW.hInstance = NULL; /* hInstance would register DLL-local class */
5201 wcW.hIcon = NULL;
5202 wcW.hCursor = LoadCursorW(NULL, (LPWSTR)IDC_IBEAM);
5203 wcW.hbrBackground = GetStockObject(NULL_BRUSH);
5204 wcW.lpszMenuName = NULL;
5206 if (is_version_nt())
5208 wcW.lpszClassName = RICHEDIT_CLASS20W;
5209 if (!RegisterClassW(&wcW)) return FALSE;
5210 wcW.lpszClassName = MSFTEDIT_CLASS;
5211 if (!RegisterClassW(&wcW)) return FALSE;
5213 else
5215 /* WNDCLASSA/W have the same layout */
5216 wcW.lpszClassName = (LPCWSTR)"RichEdit20W";
5217 if (!RegisterClassA((WNDCLASSA *)&wcW)) return FALSE;
5218 wcW.lpszClassName = (LPCWSTR)"RichEdit50W";
5219 if (!RegisterClassA((WNDCLASSA *)&wcW)) return FALSE;
5222 wcA.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS;
5223 wcA.lpfnWndProc = RichEditWndProcA;
5224 wcA.cbClsExtra = 0;
5225 wcA.cbWndExtra = sizeof(ME_TextEditor *);
5226 wcA.hInstance = NULL; /* hInstance would register DLL-local class */
5227 wcA.hIcon = NULL;
5228 wcA.hCursor = LoadCursorW(NULL, (LPWSTR)IDC_IBEAM);
5229 wcA.hbrBackground = GetStockObject(NULL_BRUSH);
5230 wcA.lpszMenuName = NULL;
5231 wcA.lpszClassName = RICHEDIT_CLASS20A;
5232 if (!RegisterClassA(&wcA)) return FALSE;
5233 wcA.lpszClassName = "RichEdit50A";
5234 if (!RegisterClassA(&wcA)) return FALSE;
5236 return TRUE;
5239 static LRESULT WINAPI REComboWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
5240 /* FIXME: Not implemented */
5241 TRACE("hWnd %p msg %04x (%s) %08lx %08lx\n",
5242 hWnd, msg, get_msg_name(msg), wParam, lParam);
5243 return DefWindowProcW(hWnd, msg, wParam, lParam);
5246 static LRESULT WINAPI REListWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
5247 /* FIXME: Not implemented */
5248 TRACE("hWnd %p msg %04x (%s) %08lx %08lx\n",
5249 hWnd, msg, get_msg_name(msg), wParam, lParam);
5250 return DefWindowProcW(hWnd, msg, wParam, lParam);
5253 /******************************************************************
5254 * REExtendedRegisterClass (RICHED20.8)
5256 * FIXME undocumented
5257 * Need to check for errors and implement controls and callbacks
5259 LRESULT WINAPI REExtendedRegisterClass(void)
5261 WNDCLASSW wcW;
5262 UINT result;
5264 FIXME("semi stub\n");
5266 wcW.cbClsExtra = 0;
5267 wcW.cbWndExtra = 4;
5268 wcW.hInstance = NULL;
5269 wcW.hIcon = NULL;
5270 wcW.hCursor = NULL;
5271 wcW.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
5272 wcW.lpszMenuName = NULL;
5274 if (!ME_ListBoxRegistered)
5276 wcW.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS;
5277 wcW.lpfnWndProc = REListWndProc;
5278 wcW.lpszClassName = REListBox20W;
5279 if (RegisterClassW(&wcW)) ME_ListBoxRegistered = TRUE;
5282 if (!ME_ComboBoxRegistered)
5284 wcW.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS | CS_VREDRAW | CS_HREDRAW;
5285 wcW.lpfnWndProc = REComboWndProc;
5286 wcW.lpszClassName = REComboBox20W;
5287 if (RegisterClassW(&wcW)) ME_ComboBoxRegistered = TRUE;
5290 result = 0;
5291 if (ME_ListBoxRegistered)
5292 result += 1;
5293 if (ME_ComboBoxRegistered)
5294 result += 2;
5296 return result;
5299 static int __cdecl wchar_comp( const void *key, const void *elem )
5301 return *(const WCHAR *)key - *(const WCHAR *)elem;
5304 /* neutral characters end the url if the next non-neutral character is a space character,
5305 otherwise they are included in the url. */
5306 static BOOL isurlneutral( WCHAR c )
5308 /* NB this list is sorted */
5309 static const WCHAR neutral_chars[] = {'!','\"','\'','(',')',',','-','.',':',';','<','>','?','[',']','{','}'};
5311 /* Some shortcuts */
5312 if (isalnum( c )) return FALSE;
5313 if (c > neutral_chars[ARRAY_SIZE( neutral_chars ) - 1]) return FALSE;
5315 return !!bsearch( &c, neutral_chars, ARRAY_SIZE( neutral_chars ), sizeof(c), wchar_comp );
5319 * This proc takes a selection, and scans it forward in order to select the span
5320 * of a possible URL candidate. A possible URL candidate must start with isalnum
5321 * or one of the following special characters: *|/\+%#@ and must consist entirely
5322 * of the characters allowed to start the URL, plus : (colon) which may occur
5323 * at most once, and not at either end.
5325 static BOOL ME_FindNextURLCandidate(ME_TextEditor *editor,
5326 const ME_Cursor *start,
5327 int nChars,
5328 ME_Cursor *candidate_min,
5329 ME_Cursor *candidate_max)
5331 ME_Cursor cursor = *start, neutral_end, space_end;
5332 BOOL candidateStarted = FALSE, quoted = FALSE;
5333 WCHAR c;
5335 while (nChars > 0)
5337 WCHAR *str = get_text( &cursor.pRun->member.run, 0 );
5338 int run_len = cursor.pRun->member.run.len;
5340 nChars -= run_len - cursor.nOffset;
5342 /* Find start of candidate */
5343 if (!candidateStarted)
5345 while (cursor.nOffset < run_len)
5347 c = str[cursor.nOffset];
5348 if (!iswspace( c ) && !isurlneutral( c ))
5350 *candidate_min = cursor;
5351 candidateStarted = TRUE;
5352 neutral_end.pPara = NULL;
5353 space_end.pPara = NULL;
5354 cursor.nOffset++;
5355 break;
5357 quoted = (c == '<');
5358 cursor.nOffset++;
5362 /* Find end of candidate */
5363 if (candidateStarted)
5365 while (cursor.nOffset < run_len)
5367 c = str[cursor.nOffset];
5368 if (iswspace( c ))
5370 if (quoted && c != '\r')
5372 if (!space_end.pPara)
5374 if (neutral_end.pPara)
5375 space_end = neutral_end;
5376 else
5377 space_end = cursor;
5380 else
5381 goto done;
5383 else if (isurlneutral( c ))
5385 if (quoted && c == '>')
5387 neutral_end.pPara = NULL;
5388 space_end.pPara = NULL;
5389 goto done;
5391 if (!neutral_end.pPara)
5392 neutral_end = cursor;
5394 else
5395 neutral_end.pPara = NULL;
5397 cursor.nOffset++;
5401 cursor.nOffset = 0;
5402 if (!ME_NextRun(&cursor.pPara, &cursor.pRun, TRUE))
5403 goto done;
5406 done:
5407 if (candidateStarted)
5409 if (space_end.pPara)
5410 *candidate_max = space_end;
5411 else if (neutral_end.pPara)
5412 *candidate_max = neutral_end;
5413 else
5414 *candidate_max = cursor;
5415 return TRUE;
5417 *candidate_max = *candidate_min = cursor;
5418 return FALSE;
5422 * This proc evaluates the selection and returns TRUE if it can be considered an URL
5424 static BOOL ME_IsCandidateAnURL(ME_TextEditor *editor, const ME_Cursor *start, int nChars)
5426 #define MAX_PREFIX_LEN 9
5427 struct prefix_s {
5428 const WCHAR text[MAX_PREFIX_LEN];
5429 int length;
5430 }prefixes[] = {
5431 {{'p','r','o','s','p','e','r','o',':'}, 9},
5432 {{'t','e','l','n','e','t',':'}, 7},
5433 {{'g','o','p','h','e','r',':'}, 7},
5434 {{'m','a','i','l','t','o',':'}, 7},
5435 {{'h','t','t','p','s',':'}, 6},
5436 {{'f','i','l','e',':'}, 5},
5437 {{'n','e','w','s',':'}, 5},
5438 {{'w','a','i','s',':'}, 5},
5439 {{'n','n','t','p',':'}, 5},
5440 {{'h','t','t','p',':'}, 5},
5441 {{'w','w','w','.'}, 4},
5442 {{'f','t','p',':'}, 4},
5444 WCHAR bufferW[MAX_PREFIX_LEN + 1];
5445 unsigned int i;
5447 ME_GetTextW(editor, bufferW, MAX_PREFIX_LEN, start, nChars, FALSE, FALSE);
5448 for (i = 0; i < ARRAY_SIZE(prefixes); i++)
5450 if (nChars < prefixes[i].length) continue;
5451 if (!memcmp(prefixes[i].text, bufferW, prefixes[i].length * sizeof(WCHAR)))
5452 return TRUE;
5454 return FALSE;
5455 #undef MAX_PREFIX_LEN
5459 * This proc walks through the indicated selection and evaluates whether each
5460 * section identified by ME_FindNextURLCandidate and in-between sections have
5461 * their proper CFE_LINK attributes set or unset. If the CFE_LINK attribute is
5462 * not what it is supposed to be, this proc sets or unsets it as appropriate.
5464 * Since this function can cause runs to be split, do not depend on the value
5465 * of the start cursor at the end of the function.
5467 * nChars may be set to INT_MAX to update to the end of the text.
5469 * Returns TRUE if at least one section was modified.
5471 static BOOL ME_UpdateLinkAttribute(ME_TextEditor *editor, ME_Cursor *start, int nChars)
5473 BOOL modified = FALSE;
5474 ME_Cursor startCur = *start;
5476 if (!editor->AutoURLDetect_bEnable) return FALSE;
5480 CHARFORMAT2W link;
5481 ME_Cursor candidateStart, candidateEnd;
5483 if (ME_FindNextURLCandidate(editor, &startCur, nChars,
5484 &candidateStart, &candidateEnd))
5486 /* Section before candidate is not an URL */
5487 int cMin = ME_GetCursorOfs(&candidateStart);
5488 int cMax = ME_GetCursorOfs(&candidateEnd);
5490 if (!ME_IsCandidateAnURL(editor, &candidateStart, cMax - cMin))
5491 candidateStart = candidateEnd;
5492 nChars -= cMax - ME_GetCursorOfs(&startCur);
5494 else
5496 /* No more candidates until end of selection */
5497 nChars = 0;
5500 if (startCur.pRun != candidateStart.pRun ||
5501 startCur.nOffset != candidateStart.nOffset)
5503 /* CFE_LINK effect should be consistently unset */
5504 link.cbSize = sizeof(link);
5505 ME_GetCharFormat(editor, &startCur, &candidateStart, &link);
5506 if (!(link.dwMask & CFM_LINK) || (link.dwEffects & CFE_LINK))
5508 /* CFE_LINK must be unset from this range */
5509 memset(&link, 0, sizeof(CHARFORMAT2W));
5510 link.cbSize = sizeof(link);
5511 link.dwMask = CFM_LINK;
5512 link.dwEffects = 0;
5513 ME_SetCharFormat(editor, &startCur, &candidateStart, &link);
5514 /* Update candidateEnd since setting character formats may split
5515 * runs, which can cause a cursor to be at an invalid offset within
5516 * a split run. */
5517 while (candidateEnd.nOffset >= candidateEnd.pRun->member.run.len)
5519 candidateEnd.nOffset -= candidateEnd.pRun->member.run.len;
5520 candidateEnd.pRun = ME_FindItemFwd(candidateEnd.pRun, diRun);
5522 modified = TRUE;
5525 if (candidateStart.pRun != candidateEnd.pRun ||
5526 candidateStart.nOffset != candidateEnd.nOffset)
5528 /* CFE_LINK effect should be consistently set */
5529 link.cbSize = sizeof(link);
5530 ME_GetCharFormat(editor, &candidateStart, &candidateEnd, &link);
5531 if (!(link.dwMask & CFM_LINK) || !(link.dwEffects & CFE_LINK))
5533 /* CFE_LINK must be set on this range */
5534 memset(&link, 0, sizeof(CHARFORMAT2W));
5535 link.cbSize = sizeof(link);
5536 link.dwMask = CFM_LINK;
5537 link.dwEffects = CFE_LINK;
5538 ME_SetCharFormat(editor, &candidateStart, &candidateEnd, &link);
5539 modified = TRUE;
5542 startCur = candidateEnd;
5543 } while (nChars > 0);
5544 return modified;