riched20: Pass a cursor ptr to the insert style retrieval function.
[wine.git] / dlls / riched20 / editor.c
blobdfb066d597693876b4045a1cc4ac0629cee72b3f
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;
283 ME_Paragraph *editor_first_para( ME_TextEditor *editor )
285 return para_next( &editor->pBuffer->pFirst->member.para );
288 /* Note, returns the diTextEnd sentinel paragraph */
289 ME_Paragraph *editor_end_para( ME_TextEditor *editor )
291 return &editor->pBuffer->pLast->member.para;
294 static LRESULT ME_StreamInText(ME_TextEditor *editor, DWORD dwFormat, ME_InStream *stream, ME_Style *style)
296 WCHAR *pText;
297 LRESULT total_bytes_read = 0;
298 BOOL is_read = FALSE;
299 DWORD cp = CP_ACP, copy = 0;
300 char conv_buf[4 + STREAMIN_BUFFER_SIZE]; /* up to 4 additional UTF-8 bytes */
302 static const char bom_utf8[] = {0xEF, 0xBB, 0xBF};
304 TRACE("%08x %p\n", dwFormat, stream);
306 do {
307 LONG nWideChars = 0;
308 WCHAR wszText[STREAMIN_BUFFER_SIZE+1];
310 if (!stream->dwSize)
312 ME_StreamInFill(stream);
313 if (stream->editstream->dwError)
314 break;
315 if (!stream->dwSize)
316 break;
317 total_bytes_read += stream->dwSize;
320 if (!(dwFormat & SF_UNICODE))
322 char * buf = stream->buffer;
323 DWORD size = stream->dwSize, end;
325 if (!is_read)
327 is_read = TRUE;
328 if (stream->dwSize >= 3 && !memcmp(stream->buffer, bom_utf8, 3))
330 cp = CP_UTF8;
331 buf += 3;
332 size -= 3;
336 if (cp == CP_UTF8)
338 if (copy)
340 memcpy(conv_buf + copy, buf, size);
341 buf = conv_buf;
342 size += copy;
344 end = size;
345 while ((buf[end-1] & 0xC0) == 0x80)
347 --end;
348 --total_bytes_read; /* strange, but seems to match windows */
350 if (buf[end-1] & 0x80)
352 DWORD need = 0;
353 if ((buf[end-1] & 0xE0) == 0xC0)
354 need = 1;
355 if ((buf[end-1] & 0xF0) == 0xE0)
356 need = 2;
357 if ((buf[end-1] & 0xF8) == 0xF0)
358 need = 3;
360 if (size - end >= need)
362 /* we have enough bytes for this sequence */
363 end = size;
365 else
367 /* need more bytes, so don't transcode this sequence */
368 --end;
372 else
373 end = size;
375 nWideChars = MultiByteToWideChar(cp, 0, buf, end, wszText, STREAMIN_BUFFER_SIZE);
376 pText = wszText;
378 if (cp == CP_UTF8)
380 if (end != size)
382 memcpy(conv_buf, buf + end, size - end);
383 copy = size - end;
387 else
389 nWideChars = stream->dwSize >> 1;
390 pText = (WCHAR *)stream->buffer;
393 ME_InsertTextFromCursor(editor, 0, pText, nWideChars, style);
394 if (stream->dwSize == 0)
395 break;
396 stream->dwSize = 0;
397 } while(1);
398 return total_bytes_read;
401 static void ME_ApplyBorderProperties(RTF_Info *info,
402 ME_BorderRect *borderRect,
403 RTFBorder *borderDef)
405 int i, colorNum;
406 ME_Border *pBorders[] = {&borderRect->top,
407 &borderRect->left,
408 &borderRect->bottom,
409 &borderRect->right};
410 for (i = 0; i < 4; i++)
412 RTFColor *colorDef = info->colorList;
413 pBorders[i]->width = borderDef[i].width;
414 colorNum = borderDef[i].color;
415 while (colorDef && colorDef->rtfCNum != colorNum)
416 colorDef = colorDef->rtfNextColor;
417 if (colorDef)
418 pBorders[i]->colorRef = RGB(
419 colorDef->rtfCRed >= 0 ? colorDef->rtfCRed : 0,
420 colorDef->rtfCGreen >= 0 ? colorDef->rtfCGreen : 0,
421 colorDef->rtfCBlue >= 0 ? colorDef->rtfCBlue : 0);
422 else
423 pBorders[i]->colorRef = RGB(0, 0, 0);
427 void ME_RTFCharAttrHook(RTF_Info *info)
429 CHARFORMAT2W fmt;
430 fmt.cbSize = sizeof(fmt);
431 fmt.dwMask = 0;
432 fmt.dwEffects = 0;
434 switch(info->rtfMinor)
436 case rtfPlain:
437 /* FIXME add more flags once they're implemented */
438 fmt.dwMask = CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE | CFM_UNDERLINETYPE | CFM_STRIKEOUT |
439 CFM_COLOR | CFM_BACKCOLOR | CFM_SIZE | CFM_WEIGHT;
440 fmt.dwEffects = CFE_AUTOCOLOR | CFE_AUTOBACKCOLOR;
441 fmt.yHeight = 12*20; /* 12pt */
442 fmt.wWeight = FW_NORMAL;
443 fmt.bUnderlineType = CFU_UNDERLINE;
444 break;
445 case rtfBold:
446 fmt.dwMask = CFM_BOLD | CFM_WEIGHT;
447 fmt.dwEffects = info->rtfParam ? CFE_BOLD : 0;
448 fmt.wWeight = info->rtfParam ? FW_BOLD : FW_NORMAL;
449 break;
450 case rtfItalic:
451 fmt.dwMask = CFM_ITALIC;
452 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
453 break;
454 case rtfUnderline:
455 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
456 fmt.bUnderlineType = CFU_UNDERLINE;
457 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
458 break;
459 case rtfDotUnderline:
460 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
461 fmt.bUnderlineType = CFU_UNDERLINEDOTTED;
462 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
463 break;
464 case rtfDbUnderline:
465 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
466 fmt.bUnderlineType = CFU_UNDERLINEDOUBLE;
467 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
468 break;
469 case rtfWordUnderline:
470 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
471 fmt.bUnderlineType = CFU_UNDERLINEWORD;
472 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
473 break;
474 case rtfNoUnderline:
475 fmt.dwMask = CFM_UNDERLINE;
476 fmt.dwEffects = 0;
477 break;
478 case rtfStrikeThru:
479 fmt.dwMask = CFM_STRIKEOUT;
480 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
481 break;
482 case rtfSubScript:
483 case rtfSuperScript:
484 case rtfSubScrShrink:
485 case rtfSuperScrShrink:
486 case rtfNoSuperSub:
487 fmt.dwMask = CFM_SUBSCRIPT|CFM_SUPERSCRIPT;
488 if (info->rtfMinor == rtfSubScrShrink) fmt.dwEffects = CFE_SUBSCRIPT;
489 if (info->rtfMinor == rtfSuperScrShrink) fmt.dwEffects = CFE_SUPERSCRIPT;
490 if (info->rtfMinor == rtfNoSuperSub) fmt.dwEffects = 0;
491 break;
492 case rtfInvisible:
493 fmt.dwMask = CFM_HIDDEN;
494 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
495 break;
496 case rtfBackColor:
497 fmt.dwMask = CFM_BACKCOLOR;
498 fmt.dwEffects = 0;
499 if (info->rtfParam == 0)
500 fmt.dwEffects = CFE_AUTOBACKCOLOR;
501 else if (info->rtfParam != rtfNoParam)
503 RTFColor *c = RTFGetColor(info, info->rtfParam);
504 if (c && c->rtfCBlue >= 0)
505 fmt.crBackColor = (c->rtfCBlue<<16)|(c->rtfCGreen<<8)|(c->rtfCRed);
506 else
507 fmt.dwEffects = CFE_AUTOBACKCOLOR;
509 break;
510 case rtfForeColor:
511 fmt.dwMask = CFM_COLOR;
512 fmt.dwEffects = 0;
513 if (info->rtfParam == 0)
514 fmt.dwEffects = CFE_AUTOCOLOR;
515 else if (info->rtfParam != rtfNoParam)
517 RTFColor *c = RTFGetColor(info, info->rtfParam);
518 if (c && c->rtfCBlue >= 0)
519 fmt.crTextColor = (c->rtfCBlue<<16)|(c->rtfCGreen<<8)|(c->rtfCRed);
520 else {
521 fmt.dwEffects = CFE_AUTOCOLOR;
524 break;
525 case rtfFontNum:
526 if (info->rtfParam != rtfNoParam)
528 RTFFont *f = RTFGetFont(info, info->rtfParam);
529 if (f)
531 MultiByteToWideChar(CP_ACP, 0, f->rtfFName, -1, fmt.szFaceName, ARRAY_SIZE(fmt.szFaceName));
532 fmt.szFaceName[ARRAY_SIZE(fmt.szFaceName)-1] = '\0';
533 fmt.bCharSet = f->rtfFCharSet;
534 fmt.dwMask = CFM_FACE | CFM_CHARSET;
535 fmt.bPitchAndFamily = f->rtfFPitch | (f->rtfFFamily << 4);
538 break;
539 case rtfFontSize:
540 fmt.dwMask = CFM_SIZE;
541 if (info->rtfParam != rtfNoParam)
542 fmt.yHeight = info->rtfParam*10;
543 break;
545 if (fmt.dwMask) {
546 ME_Style *style2;
547 RTFFlushOutputBuffer(info);
548 /* FIXME too slow ? how come ? */
549 style2 = ME_ApplyStyle(info->editor, info->style, &fmt);
550 ME_ReleaseStyle(info->style);
551 info->style = style2;
552 info->styleChanged = TRUE;
556 /* FIXME this function doesn't get any information about context of the RTF tag, which is very bad,
557 the same tags mean different things in different contexts */
558 void ME_RTFParAttrHook(RTF_Info *info)
560 switch(info->rtfMinor)
562 case rtfParDef: /* restores default paragraph attributes */
563 if (!info->editor->bEmulateVersion10) /* v4.1 */
564 info->borderType = RTFBorderParaLeft;
565 else /* v1.0 - 3.0 */
566 info->borderType = RTFBorderParaTop;
567 info->fmt.dwMask = PFM_ALIGNMENT | PFM_BORDER | PFM_LINESPACING | PFM_TABSTOPS |
568 PFM_OFFSET | PFM_RIGHTINDENT | PFM_SPACEAFTER | PFM_SPACEBEFORE |
569 PFM_STARTINDENT | PFM_RTLPARA | PFM_NUMBERING | PFM_NUMBERINGSTART |
570 PFM_NUMBERINGSTYLE | PFM_NUMBERINGTAB;
571 /* TODO: shading */
572 info->fmt.wAlignment = PFA_LEFT;
573 info->fmt.cTabCount = 0;
574 info->fmt.dxOffset = info->fmt.dxStartIndent = info->fmt.dxRightIndent = 0;
575 info->fmt.wBorderWidth = info->fmt.wBorders = 0;
576 info->fmt.wBorderSpace = 0;
577 info->fmt.bLineSpacingRule = 0;
578 info->fmt.dySpaceBefore = info->fmt.dySpaceAfter = 0;
579 info->fmt.dyLineSpacing = 0;
580 info->fmt.wEffects &= ~PFE_RTLPARA;
581 info->fmt.wNumbering = 0;
582 info->fmt.wNumberingStart = 0;
583 info->fmt.wNumberingStyle = 0;
584 info->fmt.wNumberingTab = 0;
586 if (!info->editor->bEmulateVersion10) /* v4.1 */
588 if (info->tableDef && info->tableDef->tableRowStart &&
589 info->tableDef->tableRowStart->member.para.nFlags & MEPF_ROWEND)
591 ME_Cursor cursor;
592 ME_DisplayItem *para;
593 /* We are just after a table row. */
594 RTFFlushOutputBuffer(info);
595 cursor = info->editor->pCursors[0];
596 para = cursor.pPara;
597 if (para == info->tableDef->tableRowStart->member.para.next_para
598 && !cursor.nOffset && !cursor.pRun->member.run.nCharOfs)
600 /* Since the table row end, no text has been inserted, and the \intbl
601 * control word has not be used. We can confirm that we are not in a
602 * table anymore.
604 info->tableDef->tableRowStart = NULL;
605 info->canInheritInTbl = FALSE;
608 } else { /* v1.0 - v3.0 */
609 info->fmt.dwMask |= PFM_TABLE;
610 info->fmt.wEffects &= ~PFE_TABLE;
612 break;
613 case rtfNestLevel:
614 if (!info->editor->bEmulateVersion10) /* v4.1 */
616 while (info->rtfParam > info->nestingLevel) {
617 RTFTable *tableDef = heap_alloc_zero(sizeof(*tableDef));
618 tableDef->parent = info->tableDef;
619 info->tableDef = tableDef;
621 RTFFlushOutputBuffer(info);
622 if (tableDef->tableRowStart &&
623 tableDef->tableRowStart->member.para.nFlags & MEPF_ROWEND)
625 ME_DisplayItem *para = tableDef->tableRowStart;
626 para = para->member.para.next_para;
627 para = ME_InsertTableRowStartAtParagraph(info->editor, para);
628 tableDef->tableRowStart = para;
629 } else {
630 ME_Cursor cursor;
631 WCHAR endl = '\r';
632 cursor = info->editor->pCursors[0];
633 if (cursor.nOffset || cursor.pRun->member.run.nCharOfs)
634 ME_InsertTextFromCursor(info->editor, 0, &endl, 1, info->style);
635 tableDef->tableRowStart = ME_InsertTableRowStartFromCursor(info->editor);
638 info->nestingLevel++;
640 info->canInheritInTbl = FALSE;
642 break;
643 case rtfInTable:
645 if (!info->editor->bEmulateVersion10) /* v4.1 */
647 if (info->nestingLevel < 1)
649 RTFTable *tableDef;
650 if (!info->tableDef)
651 info->tableDef = heap_alloc_zero(sizeof(*info->tableDef));
652 tableDef = info->tableDef;
653 RTFFlushOutputBuffer(info);
654 if (tableDef->tableRowStart &&
655 tableDef->tableRowStart->member.para.nFlags & MEPF_ROWEND)
657 ME_DisplayItem *para = tableDef->tableRowStart;
658 para = para->member.para.next_para;
659 para = ME_InsertTableRowStartAtParagraph(info->editor, para);
660 tableDef->tableRowStart = para;
662 else
663 tableDef->tableRowStart = ME_InsertTableRowStartAtParagraph( info->editor,
664 info->editor->pCursors[0].pPara );
665 info->nestingLevel = 1;
666 info->canInheritInTbl = TRUE;
668 return;
669 } else { /* v1.0 - v3.0 */
670 info->fmt.dwMask |= PFM_TABLE;
671 info->fmt.wEffects |= PFE_TABLE;
673 break;
675 case rtfFirstIndent:
676 case rtfLeftIndent:
677 if ((info->fmt.dwMask & (PFM_STARTINDENT | PFM_OFFSET)) != (PFM_STARTINDENT | PFM_OFFSET))
679 PARAFORMAT2 fmt;
680 fmt.cbSize = sizeof(fmt);
681 ME_GetSelectionParaFormat(info->editor, &fmt);
682 info->fmt.dwMask |= PFM_STARTINDENT | PFM_OFFSET;
683 info->fmt.dxStartIndent = fmt.dxStartIndent;
684 info->fmt.dxOffset = fmt.dxOffset;
686 if (info->rtfMinor == rtfFirstIndent)
688 info->fmt.dxStartIndent += info->fmt.dxOffset + info->rtfParam;
689 info->fmt.dxOffset = -info->rtfParam;
691 else
692 info->fmt.dxStartIndent = info->rtfParam - info->fmt.dxOffset;
693 break;
694 case rtfRightIndent:
695 info->fmt.dwMask |= PFM_RIGHTINDENT;
696 info->fmt.dxRightIndent = info->rtfParam;
697 break;
698 case rtfQuadLeft:
699 case rtfQuadJust:
700 info->fmt.dwMask |= PFM_ALIGNMENT;
701 info->fmt.wAlignment = PFA_LEFT;
702 break;
703 case rtfQuadRight:
704 info->fmt.dwMask |= PFM_ALIGNMENT;
705 info->fmt.wAlignment = PFA_RIGHT;
706 break;
707 case rtfQuadCenter:
708 info->fmt.dwMask |= PFM_ALIGNMENT;
709 info->fmt.wAlignment = PFA_CENTER;
710 break;
711 case rtfTabPos:
712 if (!(info->fmt.dwMask & PFM_TABSTOPS))
714 PARAFORMAT2 fmt;
715 fmt.cbSize = sizeof(fmt);
716 ME_GetSelectionParaFormat(info->editor, &fmt);
717 memcpy(info->fmt.rgxTabs, fmt.rgxTabs,
718 fmt.cTabCount * sizeof(fmt.rgxTabs[0]));
719 info->fmt.cTabCount = fmt.cTabCount;
720 info->fmt.dwMask |= PFM_TABSTOPS;
722 if (info->fmt.cTabCount < MAX_TAB_STOPS && info->rtfParam < 0x1000000)
723 info->fmt.rgxTabs[info->fmt.cTabCount++] = info->rtfParam;
724 break;
725 case rtfKeep:
726 info->fmt.dwMask |= PFM_KEEP;
727 info->fmt.wEffects |= PFE_KEEP;
728 break;
729 case rtfNoWidowControl:
730 info->fmt.dwMask |= PFM_NOWIDOWCONTROL;
731 info->fmt.wEffects |= PFE_NOWIDOWCONTROL;
732 break;
733 case rtfKeepNext:
734 info->fmt.dwMask |= PFM_KEEPNEXT;
735 info->fmt.wEffects |= PFE_KEEPNEXT;
736 break;
737 case rtfSpaceAfter:
738 info->fmt.dwMask |= PFM_SPACEAFTER;
739 info->fmt.dySpaceAfter = info->rtfParam;
740 break;
741 case rtfSpaceBefore:
742 info->fmt.dwMask |= PFM_SPACEBEFORE;
743 info->fmt.dySpaceBefore = info->rtfParam;
744 break;
745 case rtfSpaceBetween:
746 info->fmt.dwMask |= PFM_LINESPACING;
747 if ((int)info->rtfParam > 0)
749 info->fmt.dyLineSpacing = info->rtfParam;
750 info->fmt.bLineSpacingRule = 3;
752 else
754 info->fmt.dyLineSpacing = info->rtfParam;
755 info->fmt.bLineSpacingRule = 4;
757 break;
758 case rtfSpaceMultiply:
759 info->fmt.dwMask |= PFM_LINESPACING;
760 info->fmt.dyLineSpacing = info->rtfParam * 20;
761 info->fmt.bLineSpacingRule = 5;
762 break;
763 case rtfParBullet:
764 info->fmt.dwMask |= PFM_NUMBERING;
765 info->fmt.wNumbering = PFN_BULLET;
766 break;
767 case rtfParSimple:
768 info->fmt.dwMask |= PFM_NUMBERING;
769 info->fmt.wNumbering = 2; /* FIXME: MSDN says it's not used ?? */
770 break;
771 case rtfBorderLeft:
772 info->borderType = RTFBorderParaLeft;
773 info->fmt.wBorders |= 1;
774 info->fmt.dwMask |= PFM_BORDER;
775 break;
776 case rtfBorderRight:
777 info->borderType = RTFBorderParaRight;
778 info->fmt.wBorders |= 2;
779 info->fmt.dwMask |= PFM_BORDER;
780 break;
781 case rtfBorderTop:
782 info->borderType = RTFBorderParaTop;
783 info->fmt.wBorders |= 4;
784 info->fmt.dwMask |= PFM_BORDER;
785 break;
786 case rtfBorderBottom:
787 info->borderType = RTFBorderParaBottom;
788 info->fmt.wBorders |= 8;
789 info->fmt.dwMask |= PFM_BORDER;
790 break;
791 case rtfBorderSingle:
792 info->fmt.wBorders &= ~0x700;
793 info->fmt.wBorders |= 1 << 8;
794 info->fmt.dwMask |= PFM_BORDER;
795 break;
796 case rtfBorderThick:
797 info->fmt.wBorders &= ~0x700;
798 info->fmt.wBorders |= 2 << 8;
799 info->fmt.dwMask |= PFM_BORDER;
800 break;
801 case rtfBorderShadow:
802 info->fmt.wBorders &= ~0x700;
803 info->fmt.wBorders |= 10 << 8;
804 info->fmt.dwMask |= PFM_BORDER;
805 break;
806 case rtfBorderDouble:
807 info->fmt.wBorders &= ~0x700;
808 info->fmt.wBorders |= 7 << 8;
809 info->fmt.dwMask |= PFM_BORDER;
810 break;
811 case rtfBorderDot:
812 info->fmt.wBorders &= ~0x700;
813 info->fmt.wBorders |= 11 << 8;
814 info->fmt.dwMask |= PFM_BORDER;
815 break;
816 case rtfBorderWidth:
818 int borderSide = info->borderType & RTFBorderSideMask;
819 RTFTable *tableDef = info->tableDef;
820 if ((info->borderType & RTFBorderTypeMask) == RTFBorderTypeCell)
822 RTFBorder *border;
823 if (!tableDef || tableDef->numCellsDefined >= MAX_TABLE_CELLS)
824 break;
825 border = &tableDef->cells[tableDef->numCellsDefined].border[borderSide];
826 border->width = info->rtfParam;
827 break;
829 info->fmt.wBorderWidth = info->rtfParam;
830 info->fmt.dwMask |= PFM_BORDER;
831 break;
833 case rtfBorderSpace:
834 info->fmt.wBorderSpace = info->rtfParam;
835 info->fmt.dwMask |= PFM_BORDER;
836 break;
837 case rtfBorderColor:
839 RTFTable *tableDef = info->tableDef;
840 int borderSide = info->borderType & RTFBorderSideMask;
841 int borderType = info->borderType & RTFBorderTypeMask;
842 switch(borderType)
844 case RTFBorderTypePara:
845 if (!info->editor->bEmulateVersion10) /* v4.1 */
846 break;
847 /* v1.0 - 3.0 treat paragraph and row borders the same. */
848 case RTFBorderTypeRow:
849 if (tableDef) {
850 tableDef->border[borderSide].color = info->rtfParam;
852 break;
853 case RTFBorderTypeCell:
854 if (tableDef && tableDef->numCellsDefined < MAX_TABLE_CELLS) {
855 tableDef->cells[tableDef->numCellsDefined].border[borderSide].color = info->rtfParam;
857 break;
859 break;
861 case rtfRTLPar:
862 info->fmt.dwMask |= PFM_RTLPARA;
863 info->fmt.wEffects |= PFE_RTLPARA;
864 break;
865 case rtfLTRPar:
866 info->fmt.dwMask |= PFM_RTLPARA;
867 info->fmt.wEffects &= ~PFE_RTLPARA;
868 break;
872 void ME_RTFTblAttrHook(RTF_Info *info)
874 switch (info->rtfMinor)
876 case rtfRowDef:
878 if (!info->editor->bEmulateVersion10) /* v4.1 */
879 info->borderType = 0; /* Not sure */
880 else /* v1.0 - 3.0 */
881 info->borderType = RTFBorderRowTop;
882 if (!info->tableDef) {
883 info->tableDef = ME_MakeTableDef(info->editor);
884 } else {
885 ME_InitTableDef(info->editor, info->tableDef);
887 break;
889 case rtfCellPos:
891 int cellNum;
892 if (!info->tableDef)
894 info->tableDef = ME_MakeTableDef(info->editor);
896 cellNum = info->tableDef->numCellsDefined;
897 if (cellNum >= MAX_TABLE_CELLS)
898 break;
899 info->tableDef->cells[cellNum].rightBoundary = info->rtfParam;
900 if (cellNum < MAX_TAB_STOPS) {
901 /* Tab stops were used to store cell positions before v4.1 but v4.1
902 * still seems to set the tabstops without using them. */
903 ME_DisplayItem *para = info->editor->pCursors[0].pPara;
904 PARAFORMAT2 *pFmt = &para->member.para.fmt;
905 pFmt->rgxTabs[cellNum] &= ~0x00FFFFFF;
906 pFmt->rgxTabs[cellNum] |= 0x00FFFFFF & info->rtfParam;
908 info->tableDef->numCellsDefined++;
909 break;
911 case rtfRowBordTop:
912 info->borderType = RTFBorderRowTop;
913 break;
914 case rtfRowBordLeft:
915 info->borderType = RTFBorderRowLeft;
916 break;
917 case rtfRowBordBottom:
918 info->borderType = RTFBorderRowBottom;
919 break;
920 case rtfRowBordRight:
921 info->borderType = RTFBorderRowRight;
922 break;
923 case rtfCellBordTop:
924 info->borderType = RTFBorderCellTop;
925 break;
926 case rtfCellBordLeft:
927 info->borderType = RTFBorderCellLeft;
928 break;
929 case rtfCellBordBottom:
930 info->borderType = RTFBorderCellBottom;
931 break;
932 case rtfCellBordRight:
933 info->borderType = RTFBorderCellRight;
934 break;
935 case rtfRowGapH:
936 if (info->tableDef)
937 info->tableDef->gapH = info->rtfParam;
938 break;
939 case rtfRowLeftEdge:
940 if (info->tableDef)
941 info->tableDef->leftEdge = info->rtfParam;
942 break;
946 void ME_RTFSpecialCharHook(RTF_Info *info)
948 RTFTable *tableDef = info->tableDef;
949 switch (info->rtfMinor)
951 case rtfNestCell:
952 if (info->editor->bEmulateVersion10) /* v1.0 - v3.0 */
953 break;
954 /* else fall through since v4.1 treats rtfNestCell and rtfCell the same */
955 case rtfCell:
956 if (!tableDef)
957 break;
958 RTFFlushOutputBuffer(info);
959 if (!info->editor->bEmulateVersion10) { /* v4.1 */
960 if (tableDef->tableRowStart)
962 if (!info->nestingLevel &&
963 tableDef->tableRowStart->member.para.nFlags & MEPF_ROWEND)
965 ME_DisplayItem *para = tableDef->tableRowStart;
966 para = para->member.para.next_para;
967 para = ME_InsertTableRowStartAtParagraph(info->editor, para);
968 tableDef->tableRowStart = para;
969 info->nestingLevel = 1;
971 ME_InsertTableCellFromCursor(info->editor);
973 } else { /* v1.0 - v3.0 */
974 ME_DisplayItem *para = info->editor->pCursors[0].pPara;
975 PARAFORMAT2 *pFmt = &para->member.para.fmt;
976 if (pFmt->dwMask & PFM_TABLE && pFmt->wEffects & PFE_TABLE &&
977 tableDef->numCellsInserted < tableDef->numCellsDefined)
979 WCHAR tab = '\t';
980 ME_InsertTextFromCursor(info->editor, 0, &tab, 1, info->style);
981 tableDef->numCellsInserted++;
984 break;
985 case rtfNestRow:
986 if (info->editor->bEmulateVersion10) /* v1.0 - v3.0 */
987 break;
988 /* else fall through since v4.1 treats rtfNestRow and rtfRow the same */
989 case rtfRow:
991 ME_DisplayItem *para, *cell, *run;
992 int i;
994 if (!tableDef)
995 break;
996 RTFFlushOutputBuffer(info);
997 if (!info->editor->bEmulateVersion10) { /* v4.1 */
998 if (!tableDef->tableRowStart)
999 break;
1000 if (!info->nestingLevel &&
1001 tableDef->tableRowStart->member.para.nFlags & MEPF_ROWEND)
1003 para = tableDef->tableRowStart;
1004 para = para->member.para.next_para;
1005 para = ME_InsertTableRowStartAtParagraph(info->editor, para);
1006 tableDef->tableRowStart = para;
1007 info->nestingLevel++;
1009 para = tableDef->tableRowStart;
1010 cell = ME_FindItemFwd(para, diCell);
1011 assert(cell && !cell->member.cell.prev_cell);
1012 if (tableDef->numCellsDefined < 1)
1014 /* 2000 twips appears to be the cell size that native richedit uses
1015 * when no cell sizes are specified. */
1016 const int defaultCellSize = 2000;
1017 int nRightBoundary = defaultCellSize;
1018 cell->member.cell.nRightBoundary = nRightBoundary;
1019 while (cell->member.cell.next_cell) {
1020 cell = cell->member.cell.next_cell;
1021 nRightBoundary += defaultCellSize;
1022 cell->member.cell.nRightBoundary = nRightBoundary;
1024 para = ME_InsertTableCellFromCursor(info->editor);
1025 cell = para->member.para.pCell;
1026 cell->member.cell.nRightBoundary = nRightBoundary;
1027 } else {
1028 for (i = 0; i < tableDef->numCellsDefined; i++)
1030 RTFCell *cellDef = &tableDef->cells[i];
1031 cell->member.cell.nRightBoundary = cellDef->rightBoundary;
1032 ME_ApplyBorderProperties(info, &cell->member.cell.border,
1033 cellDef->border);
1034 cell = cell->member.cell.next_cell;
1035 if (!cell)
1037 para = ME_InsertTableCellFromCursor(info->editor);
1038 cell = para->member.para.pCell;
1041 /* Cell for table row delimiter is empty */
1042 cell->member.cell.nRightBoundary = tableDef->cells[i-1].rightBoundary;
1045 run = ME_FindItemFwd(cell, diRun);
1046 if (info->editor->pCursors[0].pRun != run ||
1047 info->editor->pCursors[0].nOffset)
1049 int nOfs, nChars;
1050 /* Delete inserted cells that aren't defined. */
1051 info->editor->pCursors[1].pRun = run;
1052 info->editor->pCursors[1].pPara = ME_GetParagraph(run);
1053 info->editor->pCursors[1].nOffset = 0;
1054 nOfs = ME_GetCursorOfs(&info->editor->pCursors[1]);
1055 nChars = ME_GetCursorOfs(&info->editor->pCursors[0]) - nOfs;
1056 ME_InternalDeleteText(info->editor, &info->editor->pCursors[1],
1057 nChars, TRUE);
1060 para = ME_InsertTableRowEndFromCursor(info->editor);
1061 para->member.para.fmt.dxOffset = abs(info->tableDef->gapH);
1062 para->member.para.fmt.dxStartIndent = info->tableDef->leftEdge;
1063 ME_ApplyBorderProperties(info, &para->member.para.border,
1064 tableDef->border);
1065 info->nestingLevel--;
1066 if (!info->nestingLevel)
1068 if (info->canInheritInTbl) {
1069 tableDef->tableRowStart = para;
1070 } else {
1071 while (info->tableDef) {
1072 tableDef = info->tableDef;
1073 info->tableDef = tableDef->parent;
1074 heap_free(tableDef);
1077 } else {
1078 info->tableDef = tableDef->parent;
1079 heap_free(tableDef);
1081 } else { /* v1.0 - v3.0 */
1082 WCHAR endl = '\r';
1083 ME_DisplayItem *para = info->editor->pCursors[0].pPara;
1084 PARAFORMAT2 *pFmt = &para->member.para.fmt;
1085 pFmt->dxOffset = info->tableDef->gapH;
1086 pFmt->dxStartIndent = info->tableDef->leftEdge;
1088 ME_ApplyBorderProperties(info, &para->member.para.border,
1089 tableDef->border);
1090 while (tableDef->numCellsInserted < tableDef->numCellsDefined)
1092 WCHAR tab = '\t';
1093 ME_InsertTextFromCursor(info->editor, 0, &tab, 1, info->style);
1094 tableDef->numCellsInserted++;
1096 pFmt->cTabCount = min(tableDef->numCellsDefined, MAX_TAB_STOPS);
1097 if (!tableDef->numCellsDefined)
1098 pFmt->wEffects &= ~PFE_TABLE;
1099 ME_InsertTextFromCursor(info->editor, 0, &endl, 1, info->style);
1100 tableDef->numCellsInserted = 0;
1102 break;
1104 case rtfTab:
1105 case rtfPar:
1106 if (info->editor->bEmulateVersion10) { /* v1.0 - 3.0 */
1107 ME_DisplayItem *para;
1108 PARAFORMAT2 *pFmt;
1109 RTFFlushOutputBuffer(info);
1110 para = info->editor->pCursors[0].pPara;
1111 pFmt = &para->member.para.fmt;
1112 if (pFmt->dwMask & PFM_TABLE && pFmt->wEffects & PFE_TABLE)
1114 /* rtfPar is treated like a space within a table. */
1115 info->rtfClass = rtfText;
1116 info->rtfMajor = ' ';
1118 else if (info->rtfMinor == rtfPar && tableDef)
1119 tableDef->numCellsInserted = 0;
1121 break;
1125 static HRESULT insert_static_object(ME_TextEditor *editor, HENHMETAFILE hemf, HBITMAP hbmp,
1126 const SIZEL* sz)
1128 LPOLEOBJECT lpObject = NULL;
1129 LPSTORAGE lpStorage = NULL;
1130 LPOLECLIENTSITE lpClientSite = NULL;
1131 LPDATAOBJECT lpDataObject = NULL;
1132 LPOLECACHE lpOleCache = NULL;
1133 LPRICHEDITOLE lpReOle = NULL;
1134 STGMEDIUM stgm;
1135 FORMATETC fm;
1136 CLSID clsid;
1137 HRESULT hr = E_FAIL;
1138 DWORD conn;
1140 if (hemf)
1142 stgm.tymed = TYMED_ENHMF;
1143 stgm.u.hEnhMetaFile = hemf;
1144 fm.cfFormat = CF_ENHMETAFILE;
1146 else if (hbmp)
1148 stgm.tymed = TYMED_GDI;
1149 stgm.u.hBitmap = hbmp;
1150 fm.cfFormat = CF_BITMAP;
1152 else return E_FAIL;
1154 stgm.pUnkForRelease = NULL;
1156 fm.ptd = NULL;
1157 fm.dwAspect = DVASPECT_CONTENT;
1158 fm.lindex = -1;
1159 fm.tymed = stgm.tymed;
1161 if (!editor->reOle)
1163 if (!CreateIRichEditOle(NULL, editor, (LPVOID *)&editor->reOle))
1164 return hr;
1167 if (OleCreateDefaultHandler(&CLSID_NULL, NULL, &IID_IOleObject, (void**)&lpObject) == S_OK &&
1168 IUnknown_QueryInterface(editor->reOle, &IID_IRichEditOle, (void**)&lpReOle) == S_OK &&
1169 IRichEditOle_GetClientSite(lpReOle, &lpClientSite) == S_OK &&
1170 IOleObject_SetClientSite(lpObject, lpClientSite) == S_OK &&
1171 IOleObject_GetUserClassID(lpObject, &clsid) == S_OK &&
1172 IOleObject_QueryInterface(lpObject, &IID_IOleCache, (void**)&lpOleCache) == S_OK &&
1173 IOleCache_Cache(lpOleCache, &fm, 0, &conn) == S_OK &&
1174 IOleObject_QueryInterface(lpObject, &IID_IDataObject, (void**)&lpDataObject) == S_OK &&
1175 IDataObject_SetData(lpDataObject, &fm, &stgm, TRUE) == S_OK)
1177 REOBJECT reobject;
1179 reobject.cbStruct = sizeof(reobject);
1180 reobject.cp = REO_CP_SELECTION;
1181 reobject.clsid = clsid;
1182 reobject.poleobj = lpObject;
1183 reobject.pstg = lpStorage;
1184 reobject.polesite = lpClientSite;
1185 /* convert from twips to .01 mm */
1186 reobject.sizel.cx = MulDiv(sz->cx, 254, 144);
1187 reobject.sizel.cy = MulDiv(sz->cy, 254, 144);
1188 reobject.dvaspect = DVASPECT_CONTENT;
1189 reobject.dwFlags = 0; /* FIXME */
1190 reobject.dwUser = 0;
1192 ME_InsertOLEFromCursor(editor, &reobject, 0);
1193 hr = S_OK;
1196 if (lpObject) IOleObject_Release(lpObject);
1197 if (lpClientSite) IOleClientSite_Release(lpClientSite);
1198 if (lpStorage) IStorage_Release(lpStorage);
1199 if (lpDataObject) IDataObject_Release(lpDataObject);
1200 if (lpOleCache) IOleCache_Release(lpOleCache);
1201 if (lpReOle) IRichEditOle_Release(lpReOle);
1203 return hr;
1206 static void ME_RTFReadShpPictGroup( RTF_Info *info )
1208 int level = 1;
1210 for (;;)
1212 RTFGetToken (info);
1214 if (info->rtfClass == rtfEOF) return;
1215 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1217 if (--level == 0) break;
1219 else if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1221 level++;
1223 else
1225 RTFRouteToken( info );
1226 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1227 level--;
1231 RTFRouteToken( info ); /* feed "}" back to router */
1232 return;
1235 static DWORD read_hex_data( RTF_Info *info, BYTE **out )
1237 DWORD read = 0, size = 1024;
1238 BYTE *buf, val;
1239 BOOL flip;
1241 *out = NULL;
1243 if (info->rtfClass != rtfText)
1245 ERR("Called with incorrect token\n");
1246 return 0;
1249 buf = HeapAlloc( GetProcessHeap(), 0, size );
1250 if (!buf) return 0;
1252 val = info->rtfMajor;
1253 for (flip = TRUE;; flip = !flip)
1255 RTFGetToken( info );
1256 if (info->rtfClass == rtfEOF)
1258 HeapFree( GetProcessHeap(), 0, buf );
1259 return 0;
1261 if (info->rtfClass != rtfText) break;
1262 if (flip)
1264 if (read >= size)
1266 size *= 2;
1267 buf = HeapReAlloc( GetProcessHeap(), 0, buf, size );
1268 if (!buf) return 0;
1270 buf[read++] = RTFCharToHex(val) * 16 + RTFCharToHex(info->rtfMajor);
1272 else
1273 val = info->rtfMajor;
1275 if (flip) FIXME("wrong hex string\n");
1277 *out = buf;
1278 return read;
1281 static void ME_RTFReadPictGroup(RTF_Info *info)
1283 SIZEL sz;
1284 BYTE *buffer = NULL;
1285 DWORD size = 0;
1286 METAFILEPICT mfp;
1287 HENHMETAFILE hemf;
1288 HBITMAP hbmp;
1289 enum gfxkind {gfx_unknown = 0, gfx_enhmetafile, gfx_metafile, gfx_dib} gfx = gfx_unknown;
1290 int level = 1;
1292 mfp.mm = MM_TEXT;
1293 sz.cx = sz.cy = 0;
1295 for (;;)
1297 RTFGetToken( info );
1299 if (info->rtfClass == rtfText)
1301 if (level == 1)
1303 if (!buffer)
1304 size = read_hex_data( info, &buffer );
1306 else
1308 RTFSkipGroup( info );
1310 } /* We potentially have a new token so fall through. */
1312 if (info->rtfClass == rtfEOF) return;
1314 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1316 if (--level == 0) break;
1317 continue;
1319 if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1321 level++;
1322 continue;
1324 if (!RTFCheckCM( info, rtfControl, rtfPictAttr ))
1326 RTFRouteToken( info );
1327 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1328 level--;
1329 continue;
1332 if (RTFCheckMM( info, rtfPictAttr, rtfWinMetafile ))
1334 mfp.mm = info->rtfParam;
1335 gfx = gfx_metafile;
1337 else if (RTFCheckMM( info, rtfPictAttr, rtfDevIndBitmap ))
1339 if (info->rtfParam != 0) FIXME("dibitmap should be 0 (%d)\n", info->rtfParam);
1340 gfx = gfx_dib;
1342 else if (RTFCheckMM( info, rtfPictAttr, rtfEmfBlip ))
1343 gfx = gfx_enhmetafile;
1344 else if (RTFCheckMM( info, rtfPictAttr, rtfPicWid ))
1345 mfp.xExt = info->rtfParam;
1346 else if (RTFCheckMM( info, rtfPictAttr, rtfPicHt ))
1347 mfp.yExt = info->rtfParam;
1348 else if (RTFCheckMM( info, rtfPictAttr, rtfPicGoalWid ))
1349 sz.cx = info->rtfParam;
1350 else if (RTFCheckMM( info, rtfPictAttr, rtfPicGoalHt ))
1351 sz.cy = info->rtfParam;
1352 else
1353 FIXME("Non supported attribute: %d %d %d\n", info->rtfClass, info->rtfMajor, info->rtfMinor);
1356 if (buffer)
1358 switch (gfx)
1360 case gfx_enhmetafile:
1361 if ((hemf = SetEnhMetaFileBits( size, buffer )))
1362 insert_static_object( info->editor, hemf, NULL, &sz );
1363 break;
1364 case gfx_metafile:
1365 if ((hemf = SetWinMetaFileBits( size, buffer, NULL, &mfp )))
1366 insert_static_object( info->editor, hemf, NULL, &sz );
1367 break;
1368 case gfx_dib:
1370 BITMAPINFO *bi = (BITMAPINFO*)buffer;
1371 HDC hdc = GetDC(0);
1372 unsigned nc = bi->bmiHeader.biClrUsed;
1374 /* not quite right, especially for bitfields type of compression */
1375 if (!nc && bi->bmiHeader.biBitCount <= 8)
1376 nc = 1 << bi->bmiHeader.biBitCount;
1377 if ((hbmp = CreateDIBitmap( hdc, &bi->bmiHeader,
1378 CBM_INIT, (char*)(bi + 1) + nc * sizeof(RGBQUAD),
1379 bi, DIB_RGB_COLORS)) )
1380 insert_static_object( info->editor, NULL, hbmp, &sz );
1381 ReleaseDC( 0, hdc );
1382 break;
1384 default:
1385 break;
1388 HeapFree( GetProcessHeap(), 0, buffer );
1389 RTFRouteToken( info ); /* feed "}" back to router */
1390 return;
1393 /* for now, lookup the \result part and use it, whatever the object */
1394 static void ME_RTFReadObjectGroup(RTF_Info *info)
1396 for (;;)
1398 RTFGetToken (info);
1399 if (info->rtfClass == rtfEOF)
1400 return;
1401 if (RTFCheckCM(info, rtfGroup, rtfEndGroup))
1402 break;
1403 if (RTFCheckCM(info, rtfGroup, rtfBeginGroup))
1405 RTFGetToken (info);
1406 if (info->rtfClass == rtfEOF)
1407 return;
1408 if (RTFCheckCMM(info, rtfControl, rtfDestination, rtfObjResult))
1410 int level = 1;
1412 while (RTFGetToken (info) != rtfEOF)
1414 if (info->rtfClass == rtfGroup)
1416 if (info->rtfMajor == rtfBeginGroup) level++;
1417 else if (info->rtfMajor == rtfEndGroup && --level < 0) break;
1419 RTFRouteToken(info);
1422 else RTFSkipGroup(info);
1423 continue;
1425 if (!RTFCheckCM (info, rtfControl, rtfObjAttr))
1427 FIXME("Non supported attribute: %d %d %d\n", info->rtfClass, info->rtfMajor, info->rtfMinor);
1428 return;
1431 RTFRouteToken(info); /* feed "}" back to router */
1434 static void ME_RTFReadParnumGroup( RTF_Info *info )
1436 int level = 1, type = -1;
1437 WORD indent = 0, start = 1;
1438 WCHAR txt_before = 0, txt_after = 0;
1440 for (;;)
1442 RTFGetToken( info );
1444 if (RTFCheckCMM( info, rtfControl, rtfDestination, rtfParNumTextBefore ) ||
1445 RTFCheckCMM( info, rtfControl, rtfDestination, rtfParNumTextAfter ))
1447 int loc = info->rtfMinor;
1449 RTFGetToken( info );
1450 if (info->rtfClass == rtfText)
1452 if (loc == rtfParNumTextBefore)
1453 txt_before = info->rtfMajor;
1454 else
1455 txt_after = info->rtfMajor;
1456 continue;
1458 /* falling through to catch EOFs and group level changes */
1461 if (info->rtfClass == rtfEOF)
1462 return;
1464 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1466 if (--level == 0) break;
1467 continue;
1470 if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1472 level++;
1473 continue;
1476 /* Ignore non para-attr */
1477 if (!RTFCheckCM( info, rtfControl, rtfParAttr ))
1478 continue;
1480 switch (info->rtfMinor)
1482 case rtfParLevel: /* Para level is ignored */
1483 case rtfParSimple:
1484 break;
1485 case rtfParBullet:
1486 type = PFN_BULLET;
1487 break;
1489 case rtfParNumDecimal:
1490 type = PFN_ARABIC;
1491 break;
1492 case rtfParNumULetter:
1493 type = PFN_UCLETTER;
1494 break;
1495 case rtfParNumURoman:
1496 type = PFN_UCROMAN;
1497 break;
1498 case rtfParNumLLetter:
1499 type = PFN_LCLETTER;
1500 break;
1501 case rtfParNumLRoman:
1502 type = PFN_LCROMAN;
1503 break;
1505 case rtfParNumIndent:
1506 indent = info->rtfParam;
1507 break;
1508 case rtfParNumStartAt:
1509 start = info->rtfParam;
1510 break;
1514 if (type != -1)
1516 info->fmt.dwMask |= (PFM_NUMBERING | PFM_NUMBERINGSTART | PFM_NUMBERINGSTYLE | PFM_NUMBERINGTAB);
1517 info->fmt.wNumbering = type;
1518 info->fmt.wNumberingStart = start;
1519 info->fmt.wNumberingStyle = PFNS_PAREN;
1520 if (type != PFN_BULLET)
1522 if (txt_before == 0 && txt_after == 0)
1523 info->fmt.wNumberingStyle = PFNS_PLAIN;
1524 else if (txt_after == '.')
1525 info->fmt.wNumberingStyle = PFNS_PERIOD;
1526 else if (txt_before == '(' && txt_after == ')')
1527 info->fmt.wNumberingStyle = PFNS_PARENS;
1529 info->fmt.wNumberingTab = indent;
1532 TRACE("type %d indent %d start %d txt before %04x txt after %04x\n",
1533 type, indent, start, txt_before, txt_after);
1535 RTFRouteToken( info ); /* feed "}" back to router */
1538 static void ME_RTFReadHook(RTF_Info *info)
1540 switch(info->rtfClass)
1542 case rtfGroup:
1543 switch(info->rtfMajor)
1545 case rtfBeginGroup:
1546 if (info->stackTop < maxStack) {
1547 info->stack[info->stackTop].style = info->style;
1548 ME_AddRefStyle(info->style);
1549 info->stack[info->stackTop].codePage = info->codePage;
1550 info->stack[info->stackTop].unicodeLength = info->unicodeLength;
1552 info->stackTop++;
1553 info->styleChanged = FALSE;
1554 break;
1555 case rtfEndGroup:
1557 RTFFlushOutputBuffer(info);
1558 info->stackTop--;
1559 if (info->stackTop <= 0)
1560 info->rtfClass = rtfEOF;
1561 if (info->stackTop < 0)
1562 return;
1564 ME_ReleaseStyle(info->style);
1565 info->style = info->stack[info->stackTop].style;
1566 info->codePage = info->stack[info->stackTop].codePage;
1567 info->unicodeLength = info->stack[info->stackTop].unicodeLength;
1568 break;
1571 break;
1575 void
1576 ME_StreamInFill(ME_InStream *stream)
1578 stream->editstream->dwError = stream->editstream->pfnCallback(stream->editstream->dwCookie,
1579 (BYTE *)stream->buffer,
1580 sizeof(stream->buffer),
1581 (LONG *)&stream->dwSize);
1582 stream->dwUsed = 0;
1585 static LRESULT ME_StreamIn(ME_TextEditor *editor, DWORD format, EDITSTREAM *stream, BOOL stripLastCR)
1587 RTF_Info parser;
1588 ME_Style *style;
1589 int from, to, nUndoMode;
1590 int nEventMask = editor->nEventMask;
1591 ME_InStream inStream;
1592 BOOL invalidRTF = FALSE;
1593 ME_Cursor *selStart, *selEnd;
1594 LRESULT num_read = 0; /* bytes read for SF_TEXT, non-control chars inserted for SF_RTF */
1596 TRACE("stream==%p editor==%p format==0x%X\n", stream, editor, format);
1597 editor->nEventMask = 0;
1599 ME_GetSelectionOfs(editor, &from, &to);
1600 if (format & SFF_SELECTION && editor->mode & TM_RICHTEXT)
1602 ME_GetSelection(editor, &selStart, &selEnd);
1603 style = ME_GetSelectionInsertStyle(editor);
1605 ME_InternalDeleteText(editor, selStart, to - from, FALSE);
1607 /* Don't insert text at the end of the table row */
1608 if (!editor->bEmulateVersion10) { /* v4.1 */
1609 ME_DisplayItem *para = editor->pCursors->pPara;
1610 if (para->member.para.nFlags & MEPF_ROWEND)
1612 para = para->member.para.next_para;
1613 editor->pCursors[0].pPara = para;
1614 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
1615 editor->pCursors[0].nOffset = 0;
1617 if (para->member.para.nFlags & MEPF_ROWSTART)
1619 para = para->member.para.next_para;
1620 editor->pCursors[0].pPara = para;
1621 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
1622 editor->pCursors[0].nOffset = 0;
1624 editor->pCursors[1] = editor->pCursors[0];
1625 } else { /* v1.0 - 3.0 */
1626 if (editor->pCursors[0].pRun->member.run.nFlags & MERF_ENDPARA &&
1627 ME_IsInTable(editor->pCursors[0].pRun))
1628 return 0;
1630 } else {
1631 style = editor->pBuffer->pDefaultStyle;
1632 ME_AddRefStyle(style);
1633 set_selection_cursors(editor, 0, 0);
1634 ME_InternalDeleteText(editor, &editor->pCursors[1],
1635 ME_GetTextLength(editor), FALSE);
1636 from = to = 0;
1637 ME_ClearTempStyle(editor);
1638 ME_SetDefaultParaFormat(editor, &editor->pCursors[0].pPara->member.para.fmt);
1642 /* Back up undo mode to a local variable */
1643 nUndoMode = editor->nUndoMode;
1645 /* Only create an undo if SFF_SELECTION is set */
1646 if (!(format & SFF_SELECTION))
1647 editor->nUndoMode = umIgnore;
1649 inStream.editstream = stream;
1650 inStream.editstream->dwError = 0;
1651 inStream.dwSize = 0;
1652 inStream.dwUsed = 0;
1654 if (format & SF_RTF)
1656 /* Check if it's really RTF, and if it is not, use plain text */
1657 ME_StreamInFill(&inStream);
1658 if (!inStream.editstream->dwError)
1660 if ((!editor->bEmulateVersion10 && strncmp(inStream.buffer, "{\\rtf", 5) && strncmp(inStream.buffer, "{\\urtf", 6))
1661 || (editor->bEmulateVersion10 && *inStream.buffer != '{'))
1663 invalidRTF = TRUE;
1664 inStream.editstream->dwError = -16;
1669 if (!invalidRTF && !inStream.editstream->dwError)
1671 ME_Cursor start;
1672 from = ME_GetCursorOfs(&editor->pCursors[0]);
1673 if (format & SF_RTF) {
1675 /* setup the RTF parser */
1676 memset(&parser, 0, sizeof parser);
1677 RTFSetEditStream(&parser, &inStream);
1678 parser.rtfFormat = format&(SF_TEXT|SF_RTF);
1679 parser.editor = editor;
1680 parser.style = style;
1681 WriterInit(&parser);
1682 RTFInit(&parser);
1683 RTFSetReadHook(&parser, ME_RTFReadHook);
1684 RTFSetDestinationCallback(&parser, rtfShpPict, ME_RTFReadShpPictGroup);
1685 RTFSetDestinationCallback(&parser, rtfPict, ME_RTFReadPictGroup);
1686 RTFSetDestinationCallback(&parser, rtfObject, ME_RTFReadObjectGroup);
1687 RTFSetDestinationCallback(&parser, rtfParNumbering, ME_RTFReadParnumGroup);
1688 if (!parser.editor->bEmulateVersion10) /* v4.1 */
1690 RTFSetDestinationCallback(&parser, rtfNoNestTables, RTFSkipGroup);
1691 RTFSetDestinationCallback(&parser, rtfNestTableProps, RTFReadGroup);
1693 BeginFile(&parser);
1695 /* do the parsing */
1696 RTFRead(&parser);
1697 RTFFlushOutputBuffer(&parser);
1698 if (!editor->bEmulateVersion10) { /* v4.1 */
1699 if (parser.tableDef && parser.tableDef->tableRowStart &&
1700 (parser.nestingLevel > 0 || parser.canInheritInTbl))
1702 /* Delete any incomplete table row at the end of the rich text. */
1703 int nOfs, nChars;
1704 ME_DisplayItem *para;
1706 parser.rtfMinor = rtfRow;
1707 /* Complete the table row before deleting it.
1708 * By doing it this way we will have the current paragraph format set
1709 * properly to reflect that is not in the complete table, and undo items
1710 * will be added for this change to the current paragraph format. */
1711 if (parser.nestingLevel > 0)
1713 while (parser.nestingLevel > 1)
1714 ME_RTFSpecialCharHook(&parser); /* Decrements nestingLevel */
1715 para = parser.tableDef->tableRowStart;
1716 ME_RTFSpecialCharHook(&parser);
1717 } else {
1718 para = parser.tableDef->tableRowStart;
1719 ME_RTFSpecialCharHook(&parser);
1720 assert(para->member.para.nFlags & MEPF_ROWEND);
1721 para = para->member.para.next_para;
1724 editor->pCursors[1].pPara = para;
1725 editor->pCursors[1].pRun = ME_FindItemFwd(para, diRun);
1726 editor->pCursors[1].nOffset = 0;
1727 nOfs = ME_GetCursorOfs(&editor->pCursors[1]);
1728 nChars = ME_GetCursorOfs(&editor->pCursors[0]) - nOfs;
1729 ME_InternalDeleteText(editor, &editor->pCursors[1], nChars, TRUE);
1730 if (parser.tableDef)
1731 parser.tableDef->tableRowStart = NULL;
1734 ME_CheckTablesForCorruption(editor);
1735 RTFDestroy(&parser);
1737 if (parser.stackTop > 0)
1739 while (--parser.stackTop >= 0)
1741 ME_ReleaseStyle(parser.style);
1742 parser.style = parser.stack[parser.stackTop].style;
1744 if (!inStream.editstream->dwError)
1745 inStream.editstream->dwError = HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
1748 /* Remove last line break, as mandated by tests. This is not affected by
1749 CR/LF counters, since RTF streaming presents only \para tokens, which
1750 are converted according to the standard rules: \r for 2.0, \r\n for 1.0
1752 if (stripLastCR && !(format & SFF_SELECTION)) {
1753 int newto;
1754 ME_GetSelection(editor, &selStart, &selEnd);
1755 newto = ME_GetCursorOfs(selEnd);
1756 if (newto > to + (editor->bEmulateVersion10 ? 1 : 0)) {
1757 WCHAR lastchar[3] = {'\0', '\0'};
1758 int linebreakSize = editor->bEmulateVersion10 ? 2 : 1;
1759 ME_Cursor linebreakCursor = *selEnd, lastcharCursor = *selEnd;
1760 CHARFORMAT2W cf;
1762 /* Set the final eop to the char fmt of the last char */
1763 cf.cbSize = sizeof(cf);
1764 cf.dwMask = CFM_ALL2;
1765 ME_MoveCursorChars(editor, &lastcharCursor, -1, FALSE);
1766 ME_GetCharFormat(editor, &lastcharCursor, &linebreakCursor, &cf);
1767 set_selection_cursors(editor, newto, -1);
1768 ME_SetSelectionCharFormat(editor, &cf);
1769 set_selection_cursors(editor, newto, newto);
1771 ME_MoveCursorChars(editor, &linebreakCursor, -linebreakSize, FALSE);
1772 ME_GetTextW(editor, lastchar, 2, &linebreakCursor, linebreakSize, FALSE, FALSE);
1773 if (lastchar[0] == '\r' && (lastchar[1] == '\n' || lastchar[1] == '\0')) {
1774 ME_InternalDeleteText(editor, &linebreakCursor, linebreakSize, FALSE);
1778 to = ME_GetCursorOfs(&editor->pCursors[0]);
1779 num_read = to - from;
1781 style = parser.style;
1783 else if (format & SF_TEXT)
1785 num_read = ME_StreamInText(editor, format, &inStream, style);
1786 to = ME_GetCursorOfs(&editor->pCursors[0]);
1788 else
1789 ERR("EM_STREAMIN without SF_TEXT or SF_RTF\n");
1790 /* put the cursor at the top */
1791 if (!(format & SFF_SELECTION))
1792 set_selection_cursors(editor, 0, 0);
1793 cursor_from_char_ofs( editor, from, &start );
1794 ME_UpdateLinkAttribute(editor, &start, to - from);
1797 /* Restore saved undo mode */
1798 editor->nUndoMode = nUndoMode;
1800 /* even if we didn't add an undo, we need to commit anything on the stack */
1801 ME_CommitUndo(editor);
1803 /* If SFF_SELECTION isn't set, delete any undos from before we started too */
1804 if (!(format & SFF_SELECTION))
1805 ME_EmptyUndoStack(editor);
1807 ME_ReleaseStyle(style);
1808 editor->nEventMask = nEventMask;
1809 ME_UpdateRepaint(editor, FALSE);
1810 if (!(format & SFF_SELECTION)) {
1811 ME_ClearTempStyle(editor);
1813 update_caret(editor);
1814 ME_SendSelChange(editor);
1815 ME_SendRequestResize(editor, FALSE);
1817 return num_read;
1821 typedef struct tagME_RTFStringStreamStruct
1823 char *string;
1824 int pos;
1825 int length;
1826 } ME_RTFStringStreamStruct;
1828 static DWORD CALLBACK ME_ReadFromRTFString(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
1830 ME_RTFStringStreamStruct *pStruct = (ME_RTFStringStreamStruct *)dwCookie;
1831 int count;
1833 count = min(cb, pStruct->length - pStruct->pos);
1834 memmove(lpBuff, pStruct->string + pStruct->pos, count);
1835 pStruct->pos += count;
1836 *pcb = count;
1837 return 0;
1840 static void
1841 ME_StreamInRTFString(ME_TextEditor *editor, BOOL selection, char *string)
1843 EDITSTREAM es;
1844 ME_RTFStringStreamStruct data;
1846 data.string = string;
1847 data.length = strlen(string);
1848 data.pos = 0;
1849 es.dwCookie = (DWORD_PTR)&data;
1850 es.pfnCallback = ME_ReadFromRTFString;
1851 ME_StreamIn(editor, SF_RTF | (selection ? SFF_SELECTION : 0), &es, TRUE);
1855 static int
1856 ME_FindText(ME_TextEditor *editor, DWORD flags, const CHARRANGE *chrg, const WCHAR *text, CHARRANGE *chrgText)
1858 const int nLen = lstrlenW(text);
1859 const int nTextLen = ME_GetTextLength(editor);
1860 int nMin, nMax;
1861 ME_Cursor cursor;
1862 WCHAR wLastChar = ' ';
1864 TRACE("flags==0x%08x, chrg->cpMin==%d, chrg->cpMax==%d text==%s\n",
1865 flags, chrg->cpMin, chrg->cpMax, debugstr_w(text));
1867 if (flags & ~(FR_DOWN | FR_MATCHCASE | FR_WHOLEWORD))
1868 FIXME("Flags 0x%08x not implemented\n",
1869 flags & ~(FR_DOWN | FR_MATCHCASE | FR_WHOLEWORD));
1871 nMin = chrg->cpMin;
1872 if (chrg->cpMax == -1)
1873 nMax = nTextLen;
1874 else
1875 nMax = chrg->cpMax > nTextLen ? nTextLen : chrg->cpMax;
1877 /* In 1.0 emulation, if cpMax reaches end of text, add the FR_DOWN flag */
1878 if (editor->bEmulateVersion10 && nMax == nTextLen)
1880 flags |= FR_DOWN;
1883 /* In 1.0 emulation, cpMin must always be no greater than cpMax */
1884 if (editor->bEmulateVersion10 && nMax < nMin)
1886 if (chrgText)
1888 chrgText->cpMin = -1;
1889 chrgText->cpMax = -1;
1891 return -1;
1894 /* when searching up, if cpMin < cpMax, then instead of searching
1895 * on [cpMin,cpMax], we search on [0,cpMin], otherwise, search on
1896 * [cpMax, cpMin]. The exception is when cpMax is -1, in which
1897 * case, it is always bigger than cpMin.
1899 if (!editor->bEmulateVersion10 && !(flags & FR_DOWN))
1901 int nSwap = nMax;
1903 nMax = nMin > nTextLen ? nTextLen : nMin;
1904 if (nMin < nSwap || chrg->cpMax == -1)
1905 nMin = 0;
1906 else
1907 nMin = nSwap;
1910 if (!nLen || nMin < 0 || nMax < 0 || nMax < nMin)
1912 if (chrgText)
1913 chrgText->cpMin = chrgText->cpMax = -1;
1914 return -1;
1917 if (flags & FR_DOWN) /* Forward search */
1919 /* If possible, find the character before where the search starts */
1920 if ((flags & FR_WHOLEWORD) && nMin)
1922 cursor_from_char_ofs( editor, nMin - 1, &cursor );
1923 wLastChar = *get_text( &cursor.pRun->member.run, cursor.nOffset );
1924 ME_MoveCursorChars(editor, &cursor, 1, FALSE);
1926 else cursor_from_char_ofs( editor, nMin, &cursor );
1928 while (cursor.pRun && ME_GetCursorOfs(&cursor) + nLen <= nMax)
1930 ME_DisplayItem *pCurItem = cursor.pRun;
1931 int nCurStart = cursor.nOffset;
1932 int nMatched = 0;
1934 while (pCurItem && ME_CharCompare( *get_text( &pCurItem->member.run, nCurStart + nMatched ), text[nMatched], (flags & FR_MATCHCASE)))
1936 if ((flags & FR_WHOLEWORD) && iswalnum(wLastChar))
1937 break;
1939 nMatched++;
1940 if (nMatched == nLen)
1942 ME_DisplayItem *pNextItem = pCurItem;
1943 int nNextStart = nCurStart;
1944 WCHAR wNextChar;
1946 /* Check to see if next character is a whitespace */
1947 if (flags & FR_WHOLEWORD)
1949 if (nCurStart + nMatched == pCurItem->member.run.len)
1951 pNextItem = ME_FindItemFwd(pCurItem, diRun);
1952 nNextStart = -nMatched;
1955 if (pNextItem)
1956 wNextChar = *get_text( &pNextItem->member.run, nNextStart + nMatched );
1957 else
1958 wNextChar = ' ';
1960 if (iswalnum(wNextChar))
1961 break;
1964 cursor.nOffset += cursor.pPara->member.para.nCharOfs + cursor.pRun->member.run.nCharOfs;
1965 if (chrgText)
1967 chrgText->cpMin = cursor.nOffset;
1968 chrgText->cpMax = cursor.nOffset + nLen;
1970 TRACE("found at %d-%d\n", cursor.nOffset, cursor.nOffset + nLen);
1971 return cursor.nOffset;
1973 if (nCurStart + nMatched == pCurItem->member.run.len)
1975 pCurItem = ME_FindItemFwd(pCurItem, diRun);
1976 nCurStart = -nMatched;
1979 if (pCurItem)
1980 wLastChar = *get_text( &pCurItem->member.run, nCurStart + nMatched );
1981 else
1982 wLastChar = ' ';
1984 cursor.nOffset++;
1985 if (cursor.nOffset == cursor.pRun->member.run.len)
1987 ME_NextRun(&cursor.pPara, &cursor.pRun, TRUE);
1988 cursor.nOffset = 0;
1992 else /* Backward search */
1994 /* If possible, find the character after where the search ends */
1995 if ((flags & FR_WHOLEWORD) && nMax < nTextLen - 1)
1997 cursor_from_char_ofs( editor, nMax + 1, &cursor );
1998 wLastChar = *get_text( &cursor.pRun->member.run, cursor.nOffset );
1999 ME_MoveCursorChars(editor, &cursor, -1, FALSE);
2001 else cursor_from_char_ofs( editor, nMax, &cursor );
2003 while (cursor.pRun && ME_GetCursorOfs(&cursor) - nLen >= nMin)
2005 ME_DisplayItem *pCurItem = cursor.pRun;
2006 ME_DisplayItem *pCurPara = cursor.pPara;
2007 int nCurEnd = cursor.nOffset;
2008 int nMatched = 0;
2010 if (nCurEnd == 0)
2012 ME_PrevRun(&pCurPara, &pCurItem, TRUE);
2013 nCurEnd = pCurItem->member.run.len;
2016 while (pCurItem && ME_CharCompare( *get_text( &pCurItem->member.run, nCurEnd - nMatched - 1 ),
2017 text[nLen - nMatched - 1], (flags & FR_MATCHCASE) ))
2019 if ((flags & FR_WHOLEWORD) && iswalnum(wLastChar))
2020 break;
2022 nMatched++;
2023 if (nMatched == nLen)
2025 ME_DisplayItem *pPrevItem = pCurItem;
2026 int nPrevEnd = nCurEnd;
2027 WCHAR wPrevChar;
2028 int nStart;
2030 /* Check to see if previous character is a whitespace */
2031 if (flags & FR_WHOLEWORD)
2033 if (nPrevEnd - nMatched == 0)
2035 pPrevItem = ME_FindItemBack(pCurItem, diRun);
2036 if (pPrevItem)
2037 nPrevEnd = pPrevItem->member.run.len + nMatched;
2040 if (pPrevItem)
2041 wPrevChar = *get_text( &pPrevItem->member.run, nPrevEnd - nMatched - 1 );
2042 else
2043 wPrevChar = ' ';
2045 if (iswalnum(wPrevChar))
2046 break;
2049 nStart = pCurPara->member.para.nCharOfs
2050 + pCurItem->member.run.nCharOfs + nCurEnd - nMatched;
2051 if (chrgText)
2053 chrgText->cpMin = nStart;
2054 chrgText->cpMax = nStart + nLen;
2056 TRACE("found at %d-%d\n", nStart, nStart + nLen);
2057 return nStart;
2059 if (nCurEnd - nMatched == 0)
2061 ME_PrevRun(&pCurPara, &pCurItem, TRUE);
2062 /* Don't care about pCurItem becoming NULL here; it's already taken
2063 * care of in the exterior loop condition */
2064 nCurEnd = pCurItem->member.run.len + nMatched;
2067 if (pCurItem)
2068 wLastChar = *get_text( &pCurItem->member.run, nCurEnd - nMatched - 1 );
2069 else
2070 wLastChar = ' ';
2072 cursor.nOffset--;
2073 if (cursor.nOffset < 0)
2075 ME_PrevRun(&cursor.pPara, &cursor.pRun, TRUE);
2076 cursor.nOffset = cursor.pRun->member.run.len;
2080 TRACE("not found\n");
2081 if (chrgText)
2082 chrgText->cpMin = chrgText->cpMax = -1;
2083 return -1;
2086 static int ME_GetTextEx(ME_TextEditor *editor, GETTEXTEX *ex, LPARAM pText)
2088 int nChars;
2089 ME_Cursor start;
2091 if (!ex->cb || !pText) return 0;
2093 if (ex->flags & ~(GT_SELECTION | GT_USECRLF))
2094 FIXME("GETTEXTEX flags 0x%08x not supported\n", ex->flags & ~(GT_SELECTION | GT_USECRLF));
2096 if (ex->flags & GT_SELECTION)
2098 int from, to;
2099 int nStartCur = ME_GetSelectionOfs(editor, &from, &to);
2100 start = editor->pCursors[nStartCur];
2101 nChars = to - from;
2103 else
2105 ME_SetCursorToStart(editor, &start);
2106 nChars = INT_MAX;
2108 if (ex->codepage == CP_UNICODE)
2110 return ME_GetTextW(editor, (LPWSTR)pText, ex->cb / sizeof(WCHAR) - 1,
2111 &start, nChars, ex->flags & GT_USECRLF, FALSE);
2113 else
2115 /* potentially each char may be a CR, why calculate the exact value with O(N) when
2116 we can just take a bigger buffer? :)
2117 The above assumption still holds with CR/LF counters, since CR->CRLF expansion
2118 occurs only in richedit 2.0 mode, in which line breaks have only one CR
2120 int crlfmul = (ex->flags & GT_USECRLF) ? 2 : 1;
2121 DWORD buflen;
2122 LPWSTR buffer;
2123 LRESULT rc;
2125 buflen = min(crlfmul * nChars, ex->cb - 1);
2126 buffer = heap_alloc((buflen + 1) * sizeof(WCHAR));
2128 nChars = ME_GetTextW(editor, buffer, buflen, &start, nChars, ex->flags & GT_USECRLF, FALSE);
2129 rc = WideCharToMultiByte(ex->codepage, 0, buffer, nChars + 1,
2130 (LPSTR)pText, ex->cb, ex->lpDefaultChar, ex->lpUsedDefChar);
2131 if (rc) rc--; /* do not count 0 terminator */
2133 heap_free(buffer);
2134 return rc;
2138 static int ME_GetTextRange(ME_TextEditor *editor, WCHAR *strText,
2139 const ME_Cursor *start, int nLen, BOOL unicode)
2141 if (!strText) return 0;
2142 if (unicode) {
2143 return ME_GetTextW(editor, strText, INT_MAX, start, nLen, FALSE, FALSE);
2144 } else {
2145 int nChars;
2146 WCHAR *p = heap_alloc((nLen+1) * sizeof(*p));
2147 if (!p) return 0;
2148 nChars = ME_GetTextW(editor, p, nLen, start, nLen, FALSE, FALSE);
2149 WideCharToMultiByte(CP_ACP, 0, p, nChars+1, (char *)strText,
2150 nLen+1, NULL, NULL);
2151 heap_free(p);
2152 return nChars;
2156 int set_selection( ME_TextEditor *editor, int to, int from )
2158 int end;
2160 TRACE("%d - %d\n", to, from );
2162 if (!editor->bHideSelection) ME_InvalidateSelection( editor );
2163 end = set_selection_cursors( editor, to, from );
2164 if (!editor->bHideSelection) ME_InvalidateSelection( editor );
2165 update_caret( editor );
2166 ME_SendSelChange( editor );
2168 return end;
2171 typedef struct tagME_GlobalDestStruct
2173 HGLOBAL hData;
2174 int nLength;
2175 } ME_GlobalDestStruct;
2177 static DWORD CALLBACK ME_ReadFromHGLOBALUnicode(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
2179 ME_GlobalDestStruct *pData = (ME_GlobalDestStruct *)dwCookie;
2180 int i;
2181 WORD *pSrc, *pDest;
2183 cb = cb >> 1;
2184 pDest = (WORD *)lpBuff;
2185 pSrc = GlobalLock(pData->hData);
2186 for (i = 0; i<cb && pSrc[pData->nLength+i]; i++) {
2187 pDest[i] = pSrc[pData->nLength+i];
2189 pData->nLength += i;
2190 *pcb = 2*i;
2191 GlobalUnlock(pData->hData);
2192 return 0;
2195 static DWORD CALLBACK ME_ReadFromHGLOBALRTF(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
2197 ME_GlobalDestStruct *pData = (ME_GlobalDestStruct *)dwCookie;
2198 int i;
2199 BYTE *pSrc, *pDest;
2201 pDest = lpBuff;
2202 pSrc = GlobalLock(pData->hData);
2203 for (i = 0; i<cb && pSrc[pData->nLength+i]; i++) {
2204 pDest[i] = pSrc[pData->nLength+i];
2206 pData->nLength += i;
2207 *pcb = i;
2208 GlobalUnlock(pData->hData);
2209 return 0;
2212 static const WCHAR rtfW[] = {'R','i','c','h',' ','T','e','x','t',' ','F','o','r','m','a','t',0};
2214 static HRESULT paste_rtf(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2216 EDITSTREAM es;
2217 ME_GlobalDestStruct gds;
2218 HRESULT hr;
2220 gds.hData = med->u.hGlobal;
2221 gds.nLength = 0;
2222 es.dwCookie = (DWORD_PTR)&gds;
2223 es.pfnCallback = ME_ReadFromHGLOBALRTF;
2224 hr = ME_StreamIn( editor, SF_RTF | SFF_SELECTION, &es, FALSE ) == 0 ? E_FAIL : S_OK;
2225 ReleaseStgMedium( med );
2226 return hr;
2229 static HRESULT paste_text(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2231 EDITSTREAM es;
2232 ME_GlobalDestStruct gds;
2233 HRESULT hr;
2235 gds.hData = med->u.hGlobal;
2236 gds.nLength = 0;
2237 es.dwCookie = (DWORD_PTR)&gds;
2238 es.pfnCallback = ME_ReadFromHGLOBALUnicode;
2239 hr = ME_StreamIn( editor, SF_TEXT | SF_UNICODE | SFF_SELECTION, &es, FALSE ) == 0 ? E_FAIL : S_OK;
2240 ReleaseStgMedium( med );
2241 return hr;
2244 static HRESULT paste_emf(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2246 HRESULT hr;
2247 SIZEL sz = {0, 0};
2249 hr = insert_static_object( editor, med->u.hEnhMetaFile, NULL, &sz );
2250 if (SUCCEEDED(hr))
2252 ME_CommitUndo( editor );
2253 ME_UpdateRepaint( editor, FALSE );
2255 else
2256 ReleaseStgMedium( med );
2258 return hr;
2261 static struct paste_format
2263 FORMATETC fmt;
2264 HRESULT (*paste)(ME_TextEditor *, FORMATETC *, STGMEDIUM *);
2265 const WCHAR *name;
2266 } paste_formats[] =
2268 {{ -1, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, paste_rtf, rtfW },
2269 {{ CF_UNICODETEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, paste_text },
2270 {{ CF_ENHMETAFILE, NULL, DVASPECT_CONTENT, -1, TYMED_ENHMF }, paste_emf },
2271 {{ 0 }}
2274 static void init_paste_formats(void)
2276 struct paste_format *format;
2277 static int done;
2279 if (!done)
2281 for (format = paste_formats; format->fmt.cfFormat; format++)
2283 if (format->name)
2284 format->fmt.cfFormat = RegisterClipboardFormatW( format->name );
2286 done = 1;
2290 static BOOL paste_special(ME_TextEditor *editor, UINT cf, REPASTESPECIAL *ps, BOOL check_only)
2292 HRESULT hr;
2293 STGMEDIUM med;
2294 struct paste_format *format;
2295 IDataObject *data;
2297 /* Protect read-only edit control from modification */
2298 if (editor->styleFlags & ES_READONLY)
2300 if (!check_only)
2301 MessageBeep(MB_ICONERROR);
2302 return FALSE;
2305 init_paste_formats();
2307 if (ps && ps->dwAspect != DVASPECT_CONTENT)
2308 FIXME("Ignoring aspect %x\n", ps->dwAspect);
2310 hr = OleGetClipboard( &data );
2311 if (hr != S_OK) return FALSE;
2313 if (cf == CF_TEXT) cf = CF_UNICODETEXT;
2315 hr = S_FALSE;
2316 for (format = paste_formats; format->fmt.cfFormat; format++)
2318 if (cf && cf != format->fmt.cfFormat) continue;
2319 hr = IDataObject_QueryGetData( data, &format->fmt );
2320 if (hr == S_OK)
2322 if (!check_only)
2324 hr = IDataObject_GetData( data, &format->fmt, &med );
2325 if (hr != S_OK) goto done;
2326 hr = format->paste( editor, &format->fmt, &med );
2328 break;
2332 done:
2333 IDataObject_Release( data );
2335 return hr == S_OK;
2338 static HRESULT editor_copy( ME_TextEditor *editor, ME_Cursor *start, int chars, IDataObject **data_out )
2340 IDataObject *data = NULL;
2341 HRESULT hr = S_OK;
2343 if (editor->lpOleCallback)
2345 CHARRANGE range;
2346 range.cpMin = ME_GetCursorOfs( start );
2347 range.cpMax = range.cpMin + chars;
2348 hr = IRichEditOleCallback_GetClipboardData( editor->lpOleCallback, &range, RECO_COPY, &data );
2351 if (FAILED( hr ) || !data)
2352 hr = ME_GetDataObject( editor, start, chars, &data );
2354 if (SUCCEEDED( hr ))
2356 if (data_out)
2357 *data_out = data;
2358 else
2360 hr = OleSetClipboard( data );
2361 IDataObject_Release( data );
2365 return hr;
2368 HRESULT editor_copy_or_cut( ME_TextEditor *editor, BOOL cut, ME_Cursor *start, int count,
2369 IDataObject **data_out )
2371 HRESULT hr;
2373 if (cut && (editor->styleFlags & ES_READONLY))
2375 return E_ACCESSDENIED;
2378 hr = editor_copy( editor, start, count, data_out );
2379 if (SUCCEEDED(hr) && cut)
2381 ME_InternalDeleteText( editor, start, count, FALSE );
2382 ME_CommitUndo( editor );
2383 ME_UpdateRepaint( editor, TRUE );
2385 return hr;
2388 static BOOL copy_or_cut( ME_TextEditor *editor, BOOL cut )
2390 HRESULT hr;
2391 int offs, count;
2392 int start_cursor = ME_GetSelectionOfs( editor, &offs, &count );
2393 ME_Cursor *sel_start = &editor->pCursors[start_cursor];
2395 if (editor->cPasswordMask) return FALSE;
2397 count -= offs;
2398 hr = editor_copy_or_cut( editor, cut, sel_start, count, NULL );
2399 if (FAILED( hr )) MessageBeep( MB_ICONERROR );
2401 return SUCCEEDED( hr );
2404 /* helper to send a msg filter notification */
2405 static BOOL
2406 ME_FilterEvent(ME_TextEditor *editor, UINT msg, WPARAM* wParam, LPARAM* lParam)
2408 MSGFILTER msgf;
2410 if (!editor->hWnd || !editor->hwndParent) return FALSE;
2411 msgf.nmhdr.hwndFrom = editor->hWnd;
2412 msgf.nmhdr.idFrom = GetWindowLongW(editor->hWnd, GWLP_ID);
2413 msgf.nmhdr.code = EN_MSGFILTER;
2414 msgf.msg = msg;
2415 msgf.wParam = *wParam;
2416 msgf.lParam = *lParam;
2417 if (SendMessageW(editor->hwndParent, WM_NOTIFY, msgf.nmhdr.idFrom, (LPARAM)&msgf))
2418 return FALSE;
2419 *wParam = msgf.wParam;
2420 *lParam = msgf.lParam;
2421 msgf.wParam = *wParam;
2423 return TRUE;
2426 static void ME_UpdateSelectionLinkAttribute(ME_TextEditor *editor)
2428 ME_DisplayItem *startPara, *endPara;
2429 ME_DisplayItem *prev_para;
2430 ME_Cursor *from, *to;
2431 ME_Cursor start;
2432 int nChars;
2434 if (!editor->AutoURLDetect_bEnable) return;
2436 ME_GetSelection(editor, &from, &to);
2438 /* Find paragraph previous to the one that contains start cursor */
2439 startPara = from->pPara;
2440 prev_para = startPara->member.para.prev_para;
2441 if (prev_para->type == diParagraph) startPara = prev_para;
2443 /* Find paragraph that contains end cursor */
2444 endPara = to->pPara->member.para.next_para;
2446 start.pPara = startPara;
2447 start.pRun = ME_FindItemFwd(startPara, diRun);
2448 start.nOffset = 0;
2449 nChars = endPara->member.para.nCharOfs - startPara->member.para.nCharOfs;
2451 ME_UpdateLinkAttribute(editor, &start, nChars);
2454 static BOOL handle_enter(ME_TextEditor *editor)
2456 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2457 BOOL shift_is_down = GetKeyState(VK_SHIFT) & 0x8000;
2459 if (editor->bDialogMode)
2461 if (ctrl_is_down)
2462 return TRUE;
2464 if (!(editor->styleFlags & ES_WANTRETURN))
2466 if (editor->hwndParent)
2468 DWORD dw;
2469 dw = SendMessageW(editor->hwndParent, DM_GETDEFID, 0, 0);
2470 if (HIWORD(dw) == DC_HASDEFID)
2472 HWND hwDefCtrl = GetDlgItem(editor->hwndParent, LOWORD(dw));
2473 if (hwDefCtrl)
2475 SendMessageW(editor->hwndParent, WM_NEXTDLGCTL, (WPARAM)hwDefCtrl, TRUE);
2476 PostMessageW(hwDefCtrl, WM_KEYDOWN, VK_RETURN, 0);
2480 return TRUE;
2484 if (editor->styleFlags & ES_MULTILINE)
2486 static const WCHAR endl = '\r';
2487 static const WCHAR endlv10[] = {'\r','\n'};
2488 ME_Cursor cursor = editor->pCursors[0];
2489 ME_DisplayItem *para = cursor.pPara;
2490 int from, to;
2491 ME_Style *style, *eop_style;
2493 if (editor->styleFlags & ES_READONLY)
2495 MessageBeep(MB_ICONERROR);
2496 return TRUE;
2499 ME_GetSelectionOfs(editor, &from, &to);
2500 if (editor->nTextLimit > ME_GetTextLength(editor) - (to-from))
2502 if (!editor->bEmulateVersion10) /* v4.1 */
2504 if (para->member.para.nFlags & MEPF_ROWEND)
2506 /* Add a new table row after this row. */
2507 para = ME_AppendTableRow(editor, para);
2508 para = para->member.para.next_para;
2509 editor->pCursors[0].pPara = para;
2510 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2511 editor->pCursors[0].nOffset = 0;
2512 editor->pCursors[1] = editor->pCursors[0];
2513 ME_CommitUndo(editor);
2514 ME_CheckTablesForCorruption(editor);
2515 ME_UpdateRepaint(editor, FALSE);
2516 return TRUE;
2518 else if (para == editor->pCursors[1].pPara &&
2519 cursor.nOffset + cursor.pRun->member.run.nCharOfs == 0 &&
2520 para->member.para.prev_para->member.para.nFlags & MEPF_ROWSTART &&
2521 !para->member.para.prev_para->member.para.nCharOfs)
2523 /* Insert a newline before the table. */
2524 para = para->member.para.prev_para;
2525 para->member.para.nFlags &= ~MEPF_ROWSTART;
2526 editor->pCursors[0].pPara = para;
2527 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2528 editor->pCursors[1] = editor->pCursors[0];
2529 ME_InsertTextFromCursor(editor, 0, &endl, 1,
2530 editor->pCursors[0].pRun->member.run.style);
2531 para = editor->pBuffer->pFirst->member.para.next_para;
2532 ME_SetDefaultParaFormat(editor, &para->member.para.fmt);
2533 para->member.para.nFlags = 0;
2534 para_mark_rewrap( editor, &para->member.para );
2535 editor->pCursors[0].pPara = para;
2536 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2537 editor->pCursors[1] = editor->pCursors[0];
2538 para->member.para.next_para->member.para.nFlags |= MEPF_ROWSTART;
2539 ME_CommitCoalescingUndo(editor);
2540 ME_CheckTablesForCorruption(editor);
2541 ME_UpdateRepaint(editor, FALSE);
2542 return TRUE;
2545 else /* v1.0 - 3.0 */
2547 ME_DisplayItem *para = cursor.pPara;
2548 if (ME_IsInTable(para))
2550 if (cursor.pRun->member.run.nFlags & MERF_ENDPARA)
2552 if (from == to)
2554 ME_ContinueCoalescingTransaction(editor);
2555 para = ME_AppendTableRow(editor, para);
2556 editor->pCursors[0].pPara = para;
2557 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2558 editor->pCursors[0].nOffset = 0;
2559 editor->pCursors[1] = editor->pCursors[0];
2560 ME_CommitCoalescingUndo(editor);
2561 ME_UpdateRepaint(editor, FALSE);
2562 return TRUE;
2565 else
2567 ME_ContinueCoalescingTransaction(editor);
2568 if (cursor.pRun->member.run.nCharOfs + cursor.nOffset == 0 &&
2569 !ME_IsInTable(para->member.para.prev_para))
2571 /* Insert newline before table */
2572 cursor.pRun = ME_FindItemBack(para, diRun);
2573 if (cursor.pRun)
2575 editor->pCursors[0].pRun = cursor.pRun;
2576 editor->pCursors[0].pPara = para->member.para.prev_para;
2578 editor->pCursors[0].nOffset = 0;
2579 editor->pCursors[1] = editor->pCursors[0];
2580 ME_InsertTextFromCursor(editor, 0, &endl, 1,
2581 editor->pCursors[0].pRun->member.run.style);
2583 else
2585 editor->pCursors[1] = editor->pCursors[0];
2586 para = ME_AppendTableRow(editor, para);
2587 editor->pCursors[0].pPara = para;
2588 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2589 editor->pCursors[0].nOffset = 0;
2590 editor->pCursors[1] = editor->pCursors[0];
2592 ME_CommitCoalescingUndo(editor);
2593 ME_UpdateRepaint(editor, FALSE);
2594 return TRUE;
2599 style = style_get_insert_style( editor, editor->pCursors );
2601 /* Normally the new eop style is the insert style, however in a list it is copied from the existing
2602 eop style (this prevents the list label style changing when the new eop is inserted).
2603 No extra ref is taken here on eop_style. */
2604 if (para->member.para.fmt.wNumbering)
2605 eop_style = para->member.para.eop_run->style;
2606 else
2607 eop_style = style;
2608 ME_ContinueCoalescingTransaction(editor);
2609 if (shift_is_down)
2610 ME_InsertEndRowFromCursor(editor, 0);
2611 else
2612 if (!editor->bEmulateVersion10)
2613 ME_InsertTextFromCursor(editor, 0, &endl, 1, eop_style);
2614 else
2615 ME_InsertTextFromCursor(editor, 0, endlv10, 2, eop_style);
2616 ME_CommitCoalescingUndo(editor);
2617 SetCursor(NULL);
2619 ME_UpdateSelectionLinkAttribute(editor);
2620 ME_UpdateRepaint(editor, FALSE);
2621 ME_SaveTempStyle(editor, style); /* set the temp insert style for the new para */
2622 ME_ReleaseStyle(style);
2624 return TRUE;
2626 return FALSE;
2629 static BOOL
2630 ME_KeyDown(ME_TextEditor *editor, WORD nKey)
2632 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2633 BOOL shift_is_down = GetKeyState(VK_SHIFT) & 0x8000;
2635 if (editor->bMouseCaptured)
2636 return FALSE;
2637 if (nKey != VK_SHIFT && nKey != VK_CONTROL && nKey != VK_MENU)
2638 editor->nSelectionType = stPosition;
2640 switch (nKey)
2642 case VK_LEFT:
2643 case VK_RIGHT:
2644 case VK_HOME:
2645 case VK_END:
2646 editor->nUDArrowX = -1;
2647 /* fall through */
2648 case VK_UP:
2649 case VK_DOWN:
2650 case VK_PRIOR:
2651 case VK_NEXT:
2652 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
2653 ME_ArrowKey(editor, nKey, shift_is_down, ctrl_is_down);
2654 return TRUE;
2655 case VK_BACK:
2656 case VK_DELETE:
2657 editor->nUDArrowX = -1;
2658 /* FIXME backspace and delete aren't the same, they act different wrt paragraph style of the merged paragraph */
2659 if (editor->styleFlags & ES_READONLY)
2660 return FALSE;
2661 if (ME_IsSelection(editor))
2663 ME_DeleteSelection(editor);
2664 ME_CommitUndo(editor);
2666 else if (nKey == VK_DELETE)
2668 /* Delete stops group typing.
2669 * (See MSDN remarks on EM_STOPGROUPTYPING message) */
2670 ME_DeleteTextAtCursor(editor, 1, 1);
2671 ME_CommitUndo(editor);
2673 else if (ME_ArrowKey(editor, VK_LEFT, FALSE, FALSE))
2675 BOOL bDeletionSucceeded;
2676 /* Backspace can be grouped for a single undo */
2677 ME_ContinueCoalescingTransaction(editor);
2678 bDeletionSucceeded = ME_DeleteTextAtCursor(editor, 1, 1);
2679 if (!bDeletionSucceeded && !editor->bEmulateVersion10) { /* v4.1 */
2680 /* Deletion was prevented so the cursor is moved back to where it was.
2681 * (e.g. this happens when trying to delete cell boundaries)
2683 ME_ArrowKey(editor, VK_RIGHT, FALSE, FALSE);
2685 ME_CommitCoalescingUndo(editor);
2687 else
2688 return TRUE;
2689 ME_MoveCursorFromTableRowStartParagraph(editor);
2690 ME_UpdateSelectionLinkAttribute(editor);
2691 ME_UpdateRepaint(editor, FALSE);
2692 ME_SendRequestResize(editor, FALSE);
2693 return TRUE;
2694 case VK_RETURN:
2695 if (!editor->bEmulateVersion10)
2696 return handle_enter(editor);
2697 break;
2698 case VK_ESCAPE:
2699 if (editor->bDialogMode && editor->hwndParent)
2700 PostMessageW(editor->hwndParent, WM_CLOSE, 0, 0);
2701 return TRUE;
2702 case VK_TAB:
2703 if (editor->bDialogMode && editor->hwndParent)
2704 SendMessageW(editor->hwndParent, WM_NEXTDLGCTL, shift_is_down, 0);
2705 return TRUE;
2706 case 'A':
2707 if (ctrl_is_down)
2709 set_selection( editor, 0, -1 );
2710 return TRUE;
2712 break;
2713 case 'V':
2714 if (ctrl_is_down)
2715 return paste_special( editor, 0, NULL, FALSE );
2716 break;
2717 case 'C':
2718 case 'X':
2719 if (ctrl_is_down)
2720 return copy_or_cut(editor, nKey == 'X');
2721 break;
2722 case 'Z':
2723 if (ctrl_is_down)
2725 ME_Undo(editor);
2726 return TRUE;
2728 break;
2729 case 'Y':
2730 if (ctrl_is_down)
2732 ME_Redo(editor);
2733 return TRUE;
2735 break;
2737 default:
2738 if (nKey != VK_SHIFT && nKey != VK_CONTROL && nKey && nKey != VK_MENU)
2739 editor->nUDArrowX = -1;
2740 if (ctrl_is_down)
2742 if (nKey == 'W')
2744 CHARFORMAT2W chf;
2745 char buf[2048];
2746 chf.cbSize = sizeof(chf);
2748 ME_GetSelectionCharFormat(editor, &chf);
2749 ME_DumpStyleToBuf(&chf, buf);
2750 MessageBoxA(NULL, buf, "Style dump", MB_OK);
2752 if (nKey == 'Q')
2754 ME_CheckCharOffsets(editor);
2758 return FALSE;
2761 static LRESULT ME_Char(ME_TextEditor *editor, WPARAM charCode,
2762 LPARAM flags, BOOL unicode)
2764 WCHAR wstr;
2766 if (editor->bMouseCaptured)
2767 return 0;
2769 if (editor->styleFlags & ES_READONLY)
2771 MessageBeep(MB_ICONERROR);
2772 return 0; /* FIXME really 0 ? */
2775 if (unicode)
2776 wstr = (WCHAR)charCode;
2777 else
2779 CHAR charA = charCode;
2780 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &wstr, 1);
2783 if (editor->bEmulateVersion10 && wstr == '\r')
2784 handle_enter(editor);
2786 if ((unsigned)wstr >= ' ' || wstr == '\t')
2788 ME_Cursor cursor = editor->pCursors[0];
2789 ME_DisplayItem *para = cursor.pPara;
2790 int from, to;
2791 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2792 ME_GetSelectionOfs(editor, &from, &to);
2793 if (wstr == '\t' &&
2794 /* v4.1 allows tabs to be inserted with ctrl key down */
2795 !(ctrl_is_down && !editor->bEmulateVersion10))
2797 ME_DisplayItem *para;
2798 BOOL bSelectedRow = FALSE;
2800 para = cursor.pPara;
2801 if (ME_IsSelection(editor) &&
2802 cursor.pRun->member.run.nCharOfs + cursor.nOffset == 0 &&
2803 to == ME_GetCursorOfs(&editor->pCursors[0]) &&
2804 para->member.para.prev_para->type == diParagraph)
2806 para = para->member.para.prev_para;
2807 bSelectedRow = TRUE;
2809 if (ME_IsInTable(para))
2811 ME_TabPressedInTable(editor, bSelectedRow);
2812 ME_CommitUndo(editor);
2813 return 0;
2815 } else if (!editor->bEmulateVersion10) { /* v4.1 */
2816 if (para->member.para.nFlags & MEPF_ROWEND) {
2817 if (from == to) {
2818 para = para->member.para.next_para;
2819 if (para->member.para.nFlags & MEPF_ROWSTART)
2820 para = para->member.para.next_para;
2821 editor->pCursors[0].pPara = para;
2822 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2823 editor->pCursors[0].nOffset = 0;
2824 editor->pCursors[1] = editor->pCursors[0];
2827 } else { /* v1.0 - 3.0 */
2828 if (ME_IsInTable(cursor.pRun) &&
2829 cursor.pRun->member.run.nFlags & MERF_ENDPARA &&
2830 from == to)
2832 /* Text should not be inserted at the end of the table. */
2833 MessageBeep(-1);
2834 return 0;
2837 /* FIXME maybe it would make sense to call EM_REPLACESEL instead ? */
2838 /* WM_CHAR is restricted to nTextLimit */
2839 if(editor->nTextLimit > ME_GetTextLength(editor) - (to-from))
2841 ME_Style *style = style_get_insert_style( editor, editor->pCursors );
2842 ME_ContinueCoalescingTransaction(editor);
2843 ME_InsertTextFromCursor(editor, 0, &wstr, 1, style);
2844 ME_ReleaseStyle(style);
2845 ME_CommitCoalescingUndo(editor);
2846 ITextHost_TxSetCursor(editor->texthost, NULL, FALSE);
2849 ME_UpdateSelectionLinkAttribute(editor);
2850 ME_UpdateRepaint(editor, FALSE);
2852 return 0;
2855 /* Process the message and calculate the new click count.
2857 * returns: The click count if it is mouse down event, else returns 0. */
2858 static int ME_CalculateClickCount(ME_TextEditor *editor, UINT msg, WPARAM wParam,
2859 LPARAM lParam)
2861 static int clickNum = 0;
2862 if (msg < WM_MOUSEFIRST || msg > WM_MOUSELAST)
2863 return 0;
2865 if ((msg == WM_LBUTTONDBLCLK) ||
2866 (msg == WM_RBUTTONDBLCLK) ||
2867 (msg == WM_MBUTTONDBLCLK) ||
2868 (msg == WM_XBUTTONDBLCLK))
2870 msg -= (WM_LBUTTONDBLCLK - WM_LBUTTONDOWN);
2873 if ((msg == WM_LBUTTONDOWN) ||
2874 (msg == WM_RBUTTONDOWN) ||
2875 (msg == WM_MBUTTONDOWN) ||
2876 (msg == WM_XBUTTONDOWN))
2878 static MSG prevClickMsg;
2879 MSG clickMsg;
2880 /* Compare the editor instead of the hwnd so that the this
2881 * can still be done for windowless richedit controls. */
2882 clickMsg.hwnd = (HWND)editor;
2883 clickMsg.message = msg;
2884 clickMsg.wParam = wParam;
2885 clickMsg.lParam = lParam;
2886 clickMsg.time = GetMessageTime();
2887 clickMsg.pt.x = (short)LOWORD(lParam);
2888 clickMsg.pt.y = (short)HIWORD(lParam);
2889 if ((clickNum != 0) &&
2890 (clickMsg.message == prevClickMsg.message) &&
2891 (clickMsg.hwnd == prevClickMsg.hwnd) &&
2892 (clickMsg.wParam == prevClickMsg.wParam) &&
2893 (clickMsg.time - prevClickMsg.time < GetDoubleClickTime()) &&
2894 (abs(clickMsg.pt.x - prevClickMsg.pt.x) < GetSystemMetrics(SM_CXDOUBLECLK)/2) &&
2895 (abs(clickMsg.pt.y - prevClickMsg.pt.y) < GetSystemMetrics(SM_CYDOUBLECLK)/2))
2897 clickNum++;
2898 } else {
2899 clickNum = 1;
2901 prevClickMsg = clickMsg;
2902 } else {
2903 return 0;
2905 return clickNum;
2908 static BOOL is_link( ME_Run *run )
2910 return (run->style->fmt.dwMask & CFM_LINK) && (run->style->fmt.dwEffects & CFE_LINK);
2913 static BOOL ME_SetCursor(ME_TextEditor *editor)
2915 ME_Cursor cursor;
2916 POINT pt;
2917 BOOL isExact;
2918 SCROLLBARINFO sbi;
2919 DWORD messagePos = GetMessagePos();
2920 pt.x = (short)LOWORD(messagePos);
2921 pt.y = (short)HIWORD(messagePos);
2923 if (editor->hWnd)
2925 sbi.cbSize = sizeof(sbi);
2926 GetScrollBarInfo(editor->hWnd, OBJID_HSCROLL, &sbi);
2927 if (!(sbi.rgstate[0] & (STATE_SYSTEM_INVISIBLE|STATE_SYSTEM_OFFSCREEN)) &&
2928 PtInRect(&sbi.rcScrollBar, pt))
2930 ITextHost_TxSetCursor(editor->texthost,
2931 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2932 return TRUE;
2934 sbi.cbSize = sizeof(sbi);
2935 GetScrollBarInfo(editor->hWnd, OBJID_VSCROLL, &sbi);
2936 if (!(sbi.rgstate[0] & (STATE_SYSTEM_INVISIBLE|STATE_SYSTEM_OFFSCREEN)) &&
2937 PtInRect(&sbi.rcScrollBar, pt))
2939 ITextHost_TxSetCursor(editor->texthost,
2940 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2941 return TRUE;
2944 ITextHost_TxScreenToClient(editor->texthost, &pt);
2946 if (editor->nSelectionType == stLine && editor->bMouseCaptured) {
2947 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2948 return TRUE;
2950 if (!editor->bEmulateVersion10 /* v4.1 */ &&
2951 pt.y < editor->rcFormat.top &&
2952 pt.x < editor->rcFormat.left)
2954 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2955 return TRUE;
2957 if (pt.y < editor->rcFormat.top || pt.y > editor->rcFormat.bottom)
2959 if (editor->bEmulateVersion10) /* v1.0 - 3.0 */
2960 ITextHost_TxSetCursor(editor->texthost,
2961 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2962 else /* v4.1 */
2963 ITextHost_TxSetCursor(editor->texthost,
2964 LoadCursorW(NULL, (WCHAR*)IDC_IBEAM), TRUE);
2965 return TRUE;
2967 if (pt.x < editor->rcFormat.left)
2969 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2970 return TRUE;
2972 ME_CharFromPos(editor, pt.x, pt.y, &cursor, &isExact);
2973 if (isExact)
2975 ME_Run *run;
2977 run = &cursor.pRun->member.run;
2978 if (is_link( run ))
2980 ITextHost_TxSetCursor(editor->texthost,
2981 LoadCursorW(NULL, (WCHAR*)IDC_HAND),
2982 FALSE);
2983 return TRUE;
2986 if (ME_IsSelection(editor))
2988 int selStart, selEnd;
2989 int offset = ME_GetCursorOfs(&cursor);
2991 ME_GetSelectionOfs(editor, &selStart, &selEnd);
2992 if (selStart <= offset && selEnd >= offset) {
2993 ITextHost_TxSetCursor(editor->texthost,
2994 LoadCursorW(NULL, (WCHAR*)IDC_ARROW),
2995 FALSE);
2996 return TRUE;
3000 ITextHost_TxSetCursor(editor->texthost,
3001 LoadCursorW(NULL, (WCHAR*)IDC_IBEAM), TRUE);
3002 return TRUE;
3005 static void ME_SetDefaultFormatRect(ME_TextEditor *editor)
3007 ITextHost_TxGetClientRect(editor->texthost, &editor->rcFormat);
3008 editor->rcFormat.top += editor->exStyleFlags & WS_EX_CLIENTEDGE ? 1 : 0;
3009 editor->rcFormat.left += 1 + editor->selofs;
3010 editor->rcFormat.right -= 1;
3013 static LONG ME_GetSelectionType(ME_TextEditor *editor)
3015 LONG sel_type = SEL_EMPTY;
3016 LONG start, end;
3018 ME_GetSelectionOfs(editor, &start, &end);
3019 if (start == end)
3020 sel_type = SEL_EMPTY;
3021 else
3023 LONG object_count = 0, character_count = 0;
3024 int i;
3026 for (i = 0; i < end - start; i++)
3028 ME_Cursor cursor;
3030 cursor_from_char_ofs( editor, start + i, &cursor );
3031 if (cursor.pRun->member.run.reobj)
3032 object_count++;
3033 else
3034 character_count++;
3035 if (character_count >= 2 && object_count >= 2)
3036 return (SEL_TEXT | SEL_MULTICHAR | SEL_OBJECT | SEL_MULTIOBJECT);
3038 if (character_count)
3040 sel_type |= SEL_TEXT;
3041 if (character_count >= 2)
3042 sel_type |= SEL_MULTICHAR;
3044 if (object_count)
3046 sel_type |= SEL_OBJECT;
3047 if (object_count >= 2)
3048 sel_type |= SEL_MULTIOBJECT;
3051 return sel_type;
3054 static BOOL ME_ShowContextMenu(ME_TextEditor *editor, int x, int y)
3056 CHARRANGE selrange;
3057 HMENU menu;
3058 int seltype;
3060 if(!editor->lpOleCallback || !editor->hWnd)
3061 return FALSE;
3062 ME_GetSelectionOfs(editor, &selrange.cpMin, &selrange.cpMax);
3063 seltype = ME_GetSelectionType(editor);
3064 if(SUCCEEDED(IRichEditOleCallback_GetContextMenu(editor->lpOleCallback, seltype, NULL, &selrange, &menu)))
3066 TrackPopupMenu(menu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, x, y, 0, editor->hwndParent, NULL);
3067 DestroyMenu(menu);
3069 return TRUE;
3072 ME_TextEditor *ME_MakeEditor(ITextHost *texthost, BOOL bEmulateVersion10)
3074 ME_TextEditor *ed = heap_alloc(sizeof(*ed));
3075 int i;
3076 DWORD props;
3077 LONG selbarwidth;
3079 ed->hWnd = NULL;
3080 ed->hwndParent = NULL;
3081 ed->sizeWindow.cx = ed->sizeWindow.cy = 0;
3082 ed->texthost = texthost;
3083 ed->reOle = NULL;
3084 ed->bEmulateVersion10 = bEmulateVersion10;
3085 ed->styleFlags = 0;
3086 ed->exStyleFlags = 0;
3087 ed->total_rows = 0;
3088 ITextHost_TxGetPropertyBits(texthost,
3089 (TXTBIT_RICHTEXT|TXTBIT_MULTILINE|
3090 TXTBIT_READONLY|TXTBIT_USEPASSWORD|
3091 TXTBIT_HIDESELECTION|TXTBIT_SAVESELECTION|
3092 TXTBIT_AUTOWORDSEL|TXTBIT_VERTICAL|
3093 TXTBIT_WORDWRAP|TXTBIT_DISABLEDRAG),
3094 &props);
3095 ITextHost_TxGetScrollBars(texthost, &ed->styleFlags);
3096 ed->styleFlags &= (WS_VSCROLL|WS_HSCROLL|ES_AUTOVSCROLL|
3097 ES_AUTOHSCROLL|ES_DISABLENOSCROLL);
3098 ed->pBuffer = ME_MakeText();
3099 ed->nZoomNumerator = ed->nZoomDenominator = 0;
3100 ed->nAvailWidth = 0; /* wrap to client area */
3101 list_init( &ed->style_list );
3102 ME_MakeFirstParagraph(ed);
3103 /* The four cursors are for:
3104 * 0 - The position where the caret is shown
3105 * 1 - The anchored end of the selection (for normal selection)
3106 * 2 & 3 - The anchored start and end respectively for word, line,
3107 * or paragraph selection.
3109 ed->nCursors = 4;
3110 ed->pCursors = heap_alloc(ed->nCursors * sizeof(*ed->pCursors));
3111 ME_SetCursorToStart(ed, &ed->pCursors[0]);
3112 ed->pCursors[1] = ed->pCursors[0];
3113 ed->pCursors[2] = ed->pCursors[0];
3114 ed->pCursors[3] = ed->pCursors[1];
3115 ed->nLastTotalLength = ed->nTotalLength = 0;
3116 ed->nLastTotalWidth = ed->nTotalWidth = 0;
3117 ed->nUDArrowX = -1;
3118 ed->rgbBackColor = -1;
3119 ed->hbrBackground = GetSysColorBrush(COLOR_WINDOW);
3120 ed->bCaretAtEnd = FALSE;
3121 ed->nEventMask = 0;
3122 ed->nModifyStep = 0;
3123 ed->nTextLimit = TEXT_LIMIT_DEFAULT;
3124 list_init( &ed->undo_stack );
3125 list_init( &ed->redo_stack );
3126 ed->nUndoStackSize = 0;
3127 ed->nUndoLimit = STACK_SIZE_DEFAULT;
3128 ed->nUndoMode = umAddToUndo;
3129 ed->nParagraphs = 1;
3130 ed->nLastSelStart = ed->nLastSelEnd = 0;
3131 ed->pLastSelStartPara = ed->pLastSelEndPara = ed->pCursors[0].pPara;
3132 ed->bHideSelection = FALSE;
3133 ed->pfnWordBreak = NULL;
3134 ed->lpOleCallback = NULL;
3135 ed->mode = TM_MULTILEVELUNDO | TM_MULTICODEPAGE;
3136 ed->mode |= (props & TXTBIT_RICHTEXT) ? TM_RICHTEXT : TM_PLAINTEXT;
3137 ed->AutoURLDetect_bEnable = FALSE;
3138 ed->bHaveFocus = FALSE;
3139 ed->bDialogMode = FALSE;
3140 ed->bMouseCaptured = FALSE;
3141 ed->caret_hidden = FALSE;
3142 ed->caret_height = 0;
3143 for (i=0; i<HFONT_CACHE_SIZE; i++)
3145 ed->pFontCache[i].nRefs = 0;
3146 ed->pFontCache[i].nAge = 0;
3147 ed->pFontCache[i].hFont = NULL;
3150 ME_CheckCharOffsets(ed);
3151 SetRectEmpty(&ed->rcFormat);
3152 ed->bDefaultFormatRect = TRUE;
3153 ITextHost_TxGetSelectionBarWidth(ed->texthost, &selbarwidth);
3154 if (selbarwidth) {
3155 /* FIXME: Convert selbarwidth from HIMETRIC to pixels */
3156 ed->selofs = SELECTIONBAR_WIDTH;
3157 ed->styleFlags |= ES_SELECTIONBAR;
3158 } else {
3159 ed->selofs = 0;
3161 ed->nSelectionType = stPosition;
3163 ed->cPasswordMask = 0;
3164 if (props & TXTBIT_USEPASSWORD)
3165 ITextHost_TxGetPasswordChar(texthost, &ed->cPasswordMask);
3167 if (props & TXTBIT_AUTOWORDSEL)
3168 ed->styleFlags |= ECO_AUTOWORDSELECTION;
3169 if (props & TXTBIT_MULTILINE) {
3170 ed->styleFlags |= ES_MULTILINE;
3171 ed->bWordWrap = (props & TXTBIT_WORDWRAP) != 0;
3172 } else {
3173 ed->bWordWrap = FALSE;
3175 if (props & TXTBIT_READONLY)
3176 ed->styleFlags |= ES_READONLY;
3177 if (!(props & TXTBIT_HIDESELECTION))
3178 ed->styleFlags |= ES_NOHIDESEL;
3179 if (props & TXTBIT_SAVESELECTION)
3180 ed->styleFlags |= ES_SAVESEL;
3181 if (props & TXTBIT_VERTICAL)
3182 ed->styleFlags |= ES_VERTICAL;
3183 if (props & TXTBIT_DISABLEDRAG)
3184 ed->styleFlags |= ES_NOOLEDRAGDROP;
3186 ed->notified_cr.cpMin = ed->notified_cr.cpMax = 0;
3188 /* Default scrollbar information */
3189 ed->vert_si.cbSize = sizeof(SCROLLINFO);
3190 ed->vert_si.nMin = 0;
3191 ed->vert_si.nMax = 0;
3192 ed->vert_si.nPage = 0;
3193 ed->vert_si.nPos = 0;
3195 ed->horz_si.cbSize = sizeof(SCROLLINFO);
3196 ed->horz_si.nMin = 0;
3197 ed->horz_si.nMax = 0;
3198 ed->horz_si.nPage = 0;
3199 ed->horz_si.nPos = 0;
3201 ed->wheel_remain = 0;
3203 list_init( &ed->reobj_list );
3204 OleInitialize(NULL);
3206 return ed;
3209 void ME_DestroyEditor(ME_TextEditor *editor)
3211 ME_DisplayItem *p = editor->pBuffer->pFirst, *pNext = NULL;
3212 ME_Style *s, *cursor2;
3213 int i;
3215 ME_ClearTempStyle(editor);
3216 ME_EmptyUndoStack(editor);
3217 editor->pBuffer->pFirst = NULL;
3218 while(p)
3220 pNext = p->next;
3221 if (p->type == diParagraph)
3222 para_destroy( editor, &p->member.para );
3223 else
3224 ME_DestroyDisplayItem(p);
3225 p = pNext;
3228 LIST_FOR_EACH_ENTRY_SAFE( s, cursor2, &editor->style_list, ME_Style, entry )
3229 ME_DestroyStyle( s );
3231 ME_ReleaseStyle(editor->pBuffer->pDefaultStyle);
3232 for (i=0; i<HFONT_CACHE_SIZE; i++)
3234 if (editor->pFontCache[i].hFont)
3235 DeleteObject(editor->pFontCache[i].hFont);
3237 if (editor->rgbBackColor != -1)
3238 DeleteObject(editor->hbrBackground);
3239 if(editor->lpOleCallback)
3240 IRichEditOleCallback_Release(editor->lpOleCallback);
3241 ITextHost_Release(editor->texthost);
3242 if (editor->reOle)
3244 IUnknown_Release(editor->reOle);
3245 editor->reOle = NULL;
3247 OleUninitialize();
3249 heap_free(editor->pBuffer);
3250 heap_free(editor->pCursors);
3251 heap_free(editor);
3254 BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
3256 TRACE("\n");
3257 switch (fdwReason)
3259 case DLL_PROCESS_ATTACH:
3260 DisableThreadLibraryCalls(hinstDLL);
3261 me_heap = HeapCreate (0, 0x10000, 0);
3262 if (!ME_RegisterEditorClass(hinstDLL)) return FALSE;
3263 hLeft = LoadCursorW(hinstDLL, MAKEINTRESOURCEW(OCR_REVERSE));
3264 LookupInit();
3265 break;
3267 case DLL_PROCESS_DETACH:
3268 if (lpvReserved) break;
3269 UnregisterClassW(RICHEDIT_CLASS20W, 0);
3270 UnregisterClassW(MSFTEDIT_CLASS, 0);
3271 UnregisterClassA(RICHEDIT_CLASS20A, 0);
3272 UnregisterClassA("RichEdit50A", 0);
3273 if (ME_ListBoxRegistered)
3274 UnregisterClassW(REListBox20W, 0);
3275 if (ME_ComboBoxRegistered)
3276 UnregisterClassW(REComboBox20W, 0);
3277 LookupCleanup();
3278 HeapDestroy (me_heap);
3279 release_typelib();
3280 break;
3282 return TRUE;
3285 static inline int get_default_line_height( ME_TextEditor *editor )
3287 int height = 0;
3289 if (editor->pBuffer && editor->pBuffer->pDefaultStyle)
3290 height = editor->pBuffer->pDefaultStyle->tm.tmHeight;
3291 if (height <= 0) height = 24;
3293 return height;
3296 static inline int calc_wheel_change( int *remain, int amount_per_click )
3298 int change = amount_per_click * (float)*remain / WHEEL_DELTA;
3299 *remain -= WHEEL_DELTA * change / amount_per_click;
3300 return change;
3303 static const char * const edit_messages[] = {
3304 "EM_GETSEL",
3305 "EM_SETSEL",
3306 "EM_GETRECT",
3307 "EM_SETRECT",
3308 "EM_SETRECTNP",
3309 "EM_SCROLL",
3310 "EM_LINESCROLL",
3311 "EM_SCROLLCARET",
3312 "EM_GETMODIFY",
3313 "EM_SETMODIFY",
3314 "EM_GETLINECOUNT",
3315 "EM_LINEINDEX",
3316 "EM_SETHANDLE",
3317 "EM_GETHANDLE",
3318 "EM_GETTHUMB",
3319 "EM_UNKNOWN_BF",
3320 "EM_UNKNOWN_C0",
3321 "EM_LINELENGTH",
3322 "EM_REPLACESEL",
3323 "EM_UNKNOWN_C3",
3324 "EM_GETLINE",
3325 "EM_LIMITTEXT",
3326 "EM_CANUNDO",
3327 "EM_UNDO",
3328 "EM_FMTLINES",
3329 "EM_LINEFROMCHAR",
3330 "EM_UNKNOWN_CA",
3331 "EM_SETTABSTOPS",
3332 "EM_SETPASSWORDCHAR",
3333 "EM_EMPTYUNDOBUFFER",
3334 "EM_GETFIRSTVISIBLELINE",
3335 "EM_SETREADONLY",
3336 "EM_SETWORDBREAKPROC",
3337 "EM_GETWORDBREAKPROC",
3338 "EM_GETPASSWORDCHAR",
3339 "EM_SETMARGINS",
3340 "EM_GETMARGINS",
3341 "EM_GETLIMITTEXT",
3342 "EM_POSFROMCHAR",
3343 "EM_CHARFROMPOS",
3344 "EM_SETIMESTATUS",
3345 "EM_GETIMESTATUS"
3348 static const char * const richedit_messages[] = {
3349 "EM_CANPASTE",
3350 "EM_DISPLAYBAND",
3351 "EM_EXGETSEL",
3352 "EM_EXLIMITTEXT",
3353 "EM_EXLINEFROMCHAR",
3354 "EM_EXSETSEL",
3355 "EM_FINDTEXT",
3356 "EM_FORMATRANGE",
3357 "EM_GETCHARFORMAT",
3358 "EM_GETEVENTMASK",
3359 "EM_GETOLEINTERFACE",
3360 "EM_GETPARAFORMAT",
3361 "EM_GETSELTEXT",
3362 "EM_HIDESELECTION",
3363 "EM_PASTESPECIAL",
3364 "EM_REQUESTRESIZE",
3365 "EM_SELECTIONTYPE",
3366 "EM_SETBKGNDCOLOR",
3367 "EM_SETCHARFORMAT",
3368 "EM_SETEVENTMASK",
3369 "EM_SETOLECALLBACK",
3370 "EM_SETPARAFORMAT",
3371 "EM_SETTARGETDEVICE",
3372 "EM_STREAMIN",
3373 "EM_STREAMOUT",
3374 "EM_GETTEXTRANGE",
3375 "EM_FINDWORDBREAK",
3376 "EM_SETOPTIONS",
3377 "EM_GETOPTIONS",
3378 "EM_FINDTEXTEX",
3379 "EM_GETWORDBREAKPROCEX",
3380 "EM_SETWORDBREAKPROCEX",
3381 "EM_SETUNDOLIMIT",
3382 "EM_UNKNOWN_USER_83",
3383 "EM_REDO",
3384 "EM_CANREDO",
3385 "EM_GETUNDONAME",
3386 "EM_GETREDONAME",
3387 "EM_STOPGROUPTYPING",
3388 "EM_SETTEXTMODE",
3389 "EM_GETTEXTMODE",
3390 "EM_AUTOURLDETECT",
3391 "EM_GETAUTOURLDETECT",
3392 "EM_SETPALETTE",
3393 "EM_GETTEXTEX",
3394 "EM_GETTEXTLENGTHEX",
3395 "EM_SHOWSCROLLBAR",
3396 "EM_SETTEXTEX",
3397 "EM_UNKNOWN_USER_98",
3398 "EM_UNKNOWN_USER_99",
3399 "EM_SETPUNCTUATION",
3400 "EM_GETPUNCTUATION",
3401 "EM_SETWORDWRAPMODE",
3402 "EM_GETWORDWRAPMODE",
3403 "EM_SETIMECOLOR",
3404 "EM_GETIMECOLOR",
3405 "EM_SETIMEOPTIONS",
3406 "EM_GETIMEOPTIONS",
3407 "EM_CONVPOSITION",
3408 "EM_UNKNOWN_USER_109",
3409 "EM_UNKNOWN_USER_110",
3410 "EM_UNKNOWN_USER_111",
3411 "EM_UNKNOWN_USER_112",
3412 "EM_UNKNOWN_USER_113",
3413 "EM_UNKNOWN_USER_114",
3414 "EM_UNKNOWN_USER_115",
3415 "EM_UNKNOWN_USER_116",
3416 "EM_UNKNOWN_USER_117",
3417 "EM_UNKNOWN_USER_118",
3418 "EM_UNKNOWN_USER_119",
3419 "EM_SETLANGOPTIONS",
3420 "EM_GETLANGOPTIONS",
3421 "EM_GETIMECOMPMODE",
3422 "EM_FINDTEXTW",
3423 "EM_FINDTEXTEXW",
3424 "EM_RECONVERSION",
3425 "EM_SETIMEMODEBIAS",
3426 "EM_GETIMEMODEBIAS"
3429 static const char *
3430 get_msg_name(UINT msg)
3432 if (msg >= EM_GETSEL && msg <= EM_CHARFROMPOS)
3433 return edit_messages[msg - EM_GETSEL];
3434 if (msg >= EM_CANPASTE && msg <= EM_GETIMEMODEBIAS)
3435 return richedit_messages[msg - EM_CANPASTE];
3436 return "";
3439 static void ME_LinkNotify(ME_TextEditor *editor, UINT msg, WPARAM wParam, LPARAM lParam)
3441 int x,y;
3442 BOOL isExact;
3443 ME_Cursor cursor; /* The start of the clicked text. */
3445 ENLINK info;
3446 x = (short)LOWORD(lParam);
3447 y = (short)HIWORD(lParam);
3448 ME_CharFromPos(editor, x, y, &cursor, &isExact);
3449 if (!isExact) return;
3451 if (is_link( &cursor.pRun->member.run ))
3452 { /* The clicked run has CFE_LINK set */
3453 ME_DisplayItem *di;
3455 info.nmhdr.hwndFrom = NULL;
3456 info.nmhdr.idFrom = 0;
3457 info.nmhdr.code = EN_LINK;
3458 info.msg = msg;
3459 info.wParam = wParam;
3460 info.lParam = lParam;
3461 cursor.nOffset = 0;
3463 /* find the first contiguous run with CFE_LINK set */
3464 info.chrg.cpMin = ME_GetCursorOfs(&cursor);
3465 di = cursor.pRun;
3466 while (ME_PrevRun( NULL, &di, FALSE ) && is_link( &di->member.run ))
3467 info.chrg.cpMin -= di->member.run.len;
3469 /* find the last contiguous run with CFE_LINK set */
3470 info.chrg.cpMax = ME_GetCursorOfs(&cursor) + cursor.pRun->member.run.len;
3471 di = cursor.pRun;
3472 while (ME_NextRun( NULL, &di, FALSE ) && is_link( &di->member.run ))
3473 info.chrg.cpMax += di->member.run.len;
3475 ITextHost_TxNotify(editor->texthost, info.nmhdr.code, &info);
3479 void ME_ReplaceSel(ME_TextEditor *editor, BOOL can_undo, const WCHAR *str, int len)
3481 int from, to, nStartCursor;
3482 ME_Style *style;
3484 nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3485 style = ME_GetSelectionInsertStyle(editor);
3486 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to-from, FALSE);
3487 ME_InsertTextFromCursor(editor, 0, str, len, style);
3488 ME_ReleaseStyle(style);
3489 /* drop temporary style if line end */
3491 * FIXME question: does abc\n mean: put abc,
3492 * clear temp style, put \n? (would require a change)
3494 if (len>0 && str[len-1] == '\n')
3495 ME_ClearTempStyle(editor);
3496 ME_CommitUndo(editor);
3497 ME_UpdateSelectionLinkAttribute(editor);
3498 if (!can_undo)
3499 ME_EmptyUndoStack(editor);
3500 ME_UpdateRepaint(editor, FALSE);
3503 static void ME_SetText(ME_TextEditor *editor, void *text, BOOL unicode)
3505 LONG codepage = unicode ? CP_UNICODE : CP_ACP;
3506 int textLen;
3508 LPWSTR wszText = ME_ToUnicode(codepage, text, &textLen);
3509 ME_InsertTextFromCursor(editor, 0, wszText, textLen, editor->pBuffer->pDefaultStyle);
3510 ME_EndToUnicode(codepage, wszText);
3513 static LRESULT ME_WmCreate(ME_TextEditor *editor, LPARAM lParam, BOOL unicode)
3515 CREATESTRUCTW *createW = (CREATESTRUCTW*)lParam;
3516 CREATESTRUCTA *createA = (CREATESTRUCTA*)lParam;
3517 void *text = NULL;
3518 INT max;
3520 if (lParam)
3521 text = unicode ? (void*)createW->lpszName : (void*)createA->lpszName;
3523 ME_SetDefaultFormatRect(editor);
3525 max = (editor->styleFlags & ES_DISABLENOSCROLL) ? 1 : 0;
3526 if (~editor->styleFlags & ES_DISABLENOSCROLL || editor->styleFlags & WS_VSCROLL)
3527 ITextHost_TxSetScrollRange(editor->texthost, SB_VERT, 0, max, TRUE);
3529 if (~editor->styleFlags & ES_DISABLENOSCROLL || editor->styleFlags & WS_HSCROLL)
3530 ITextHost_TxSetScrollRange(editor->texthost, SB_HORZ, 0, max, TRUE);
3532 if (editor->styleFlags & ES_DISABLENOSCROLL)
3534 if (editor->styleFlags & WS_VSCROLL)
3536 ITextHost_TxEnableScrollBar(editor->texthost, SB_VERT, ESB_DISABLE_BOTH);
3537 ITextHost_TxShowScrollBar(editor->texthost, SB_VERT, TRUE);
3539 if (editor->styleFlags & WS_HSCROLL)
3541 ITextHost_TxEnableScrollBar(editor->texthost, SB_HORZ, ESB_DISABLE_BOTH);
3542 ITextHost_TxShowScrollBar(editor->texthost, SB_HORZ, TRUE);
3546 if (text)
3548 ME_SetText(editor, text, unicode);
3549 ME_SetCursorToStart(editor, &editor->pCursors[0]);
3550 ME_SetCursorToStart(editor, &editor->pCursors[1]);
3553 ME_CommitUndo(editor);
3554 ME_WrapMarkedParagraphs(editor);
3555 update_caret(editor);
3556 return 0;
3559 static LRESULT handle_EM_SETCHARFORMAT( ME_TextEditor *editor, WPARAM flags, const CHARFORMAT2W *fmt_in )
3561 CHARFORMAT2W fmt;
3562 BOOL changed = TRUE;
3563 ME_Cursor start, end;
3565 if (!cfany_to_cf2w( &fmt, fmt_in )) return 0;
3567 if (flags & SCF_ALL)
3569 if (editor->mode & TM_PLAINTEXT)
3571 ME_SetDefaultCharFormat( editor, &fmt );
3573 else
3575 ME_SetCursorToStart( editor, &start );
3576 ME_SetCharFormat( editor, &start, NULL, &fmt );
3577 editor->nModifyStep = 1;
3580 else if (flags & SCF_SELECTION)
3582 if (editor->mode & TM_PLAINTEXT) return 0;
3583 if (flags & SCF_WORD)
3585 end = editor->pCursors[0];
3586 ME_MoveCursorWords( editor, &end, +1 );
3587 start = end;
3588 ME_MoveCursorWords( editor, &start, -1 );
3589 ME_SetCharFormat( editor, &start, &end, &fmt );
3591 changed = ME_IsSelection( editor );
3592 ME_SetSelectionCharFormat( editor, &fmt );
3593 if (changed) editor->nModifyStep = 1;
3595 else /* SCF_DEFAULT */
3597 ME_SetDefaultCharFormat( editor, &fmt );
3600 ME_CommitUndo( editor );
3601 if (changed)
3603 ME_WrapMarkedParagraphs( editor );
3604 ME_UpdateScrollBar( editor );
3606 return 1;
3609 #define UNSUPPORTED_MSG(e) \
3610 case e: \
3611 FIXME(#e ": stub\n"); \
3612 *phresult = S_FALSE; \
3613 return 0;
3615 /* Handle messages for windowless and windowed richedit controls.
3617 * The LRESULT that is returned is a return value for window procs,
3618 * and the phresult parameter is the COM return code needed by the
3619 * text services interface. */
3620 LRESULT ME_HandleMessage(ME_TextEditor *editor, UINT msg, WPARAM wParam,
3621 LPARAM lParam, BOOL unicode, HRESULT* phresult)
3623 *phresult = S_OK;
3625 switch(msg) {
3627 UNSUPPORTED_MSG(EM_DISPLAYBAND)
3628 UNSUPPORTED_MSG(EM_FINDWORDBREAK)
3629 UNSUPPORTED_MSG(EM_FMTLINES)
3630 UNSUPPORTED_MSG(EM_FORMATRANGE)
3631 UNSUPPORTED_MSG(EM_GETBIDIOPTIONS)
3632 UNSUPPORTED_MSG(EM_GETEDITSTYLE)
3633 UNSUPPORTED_MSG(EM_GETIMECOMPMODE)
3634 UNSUPPORTED_MSG(EM_GETIMESTATUS)
3635 UNSUPPORTED_MSG(EM_SETIMESTATUS)
3636 UNSUPPORTED_MSG(EM_GETLANGOPTIONS)
3637 UNSUPPORTED_MSG(EM_GETREDONAME)
3638 UNSUPPORTED_MSG(EM_GETTYPOGRAPHYOPTIONS)
3639 UNSUPPORTED_MSG(EM_GETUNDONAME)
3640 UNSUPPORTED_MSG(EM_GETWORDBREAKPROCEX)
3641 UNSUPPORTED_MSG(EM_SETBIDIOPTIONS)
3642 UNSUPPORTED_MSG(EM_SETEDITSTYLE)
3643 UNSUPPORTED_MSG(EM_SETLANGOPTIONS)
3644 UNSUPPORTED_MSG(EM_SETMARGINS)
3645 UNSUPPORTED_MSG(EM_SETPALETTE)
3646 UNSUPPORTED_MSG(EM_SETTABSTOPS)
3647 UNSUPPORTED_MSG(EM_SETTYPOGRAPHYOPTIONS)
3648 UNSUPPORTED_MSG(EM_SETWORDBREAKPROCEX)
3650 /* Messages specific to Richedit controls */
3652 case EM_STREAMIN:
3653 return ME_StreamIn(editor, wParam, (EDITSTREAM*)lParam, TRUE);
3654 case EM_STREAMOUT:
3655 return ME_StreamOut(editor, wParam, (EDITSTREAM *)lParam);
3656 case WM_GETDLGCODE:
3658 UINT code = DLGC_WANTCHARS|DLGC_WANTTAB|DLGC_WANTARROWS;
3660 if (lParam)
3661 editor->bDialogMode = TRUE;
3662 if (editor->styleFlags & ES_MULTILINE)
3663 code |= DLGC_WANTMESSAGE;
3664 if (!(editor->styleFlags & ES_SAVESEL))
3665 code |= DLGC_HASSETSEL;
3666 return code;
3668 case EM_EMPTYUNDOBUFFER:
3669 ME_EmptyUndoStack(editor);
3670 return 0;
3671 case EM_GETSEL:
3673 /* Note: wParam/lParam can be NULL */
3674 UINT from, to;
3675 PUINT pfrom = wParam ? (PUINT)wParam : &from;
3676 PUINT pto = lParam ? (PUINT)lParam : &to;
3677 ME_GetSelectionOfs(editor, (int *)pfrom, (int *)pto);
3678 if ((*pfrom|*pto) & 0xFFFF0000)
3679 return -1;
3680 return MAKELONG(*pfrom,*pto);
3682 case EM_EXGETSEL:
3684 CHARRANGE *pRange = (CHARRANGE *)lParam;
3685 ME_GetSelectionOfs(editor, &pRange->cpMin, &pRange->cpMax);
3686 TRACE("EM_EXGETSEL = (%d,%d)\n", pRange->cpMin, pRange->cpMax);
3687 return 0;
3689 case EM_SETUNDOLIMIT:
3691 if ((int)wParam < 0)
3692 editor->nUndoLimit = STACK_SIZE_DEFAULT;
3693 else
3694 editor->nUndoLimit = min(wParam, STACK_SIZE_MAX);
3695 /* Setting a max stack size keeps wine from getting killed
3696 for hogging memory. Windows allocates all this memory at once, so
3697 no program would realistically set a value above our maximum. */
3698 return editor->nUndoLimit;
3700 case EM_CANUNDO:
3701 return !list_empty( &editor->undo_stack );
3702 case EM_CANREDO:
3703 return !list_empty( &editor->redo_stack );
3704 case WM_UNDO: /* FIXME: actually not the same */
3705 case EM_UNDO:
3706 return ME_Undo(editor);
3707 case EM_REDO:
3708 return ME_Redo(editor);
3709 case EM_GETOPTIONS:
3711 /* these flags are equivalent to the ES_* counterparts */
3712 DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
3713 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN | ECO_SELECTIONBAR;
3714 DWORD settings = editor->styleFlags & mask;
3716 return settings;
3718 case EM_SETFONTSIZE:
3720 CHARFORMAT2W cf;
3721 LONG tmp_size, size;
3722 BOOL is_increase = ((LONG)wParam > 0);
3724 if (editor->mode & TM_PLAINTEXT)
3725 return FALSE;
3727 cf.cbSize = sizeof(cf);
3728 cf.dwMask = CFM_SIZE;
3729 ME_GetSelectionCharFormat(editor, &cf);
3730 tmp_size = (cf.yHeight / 20) + wParam;
3732 if (tmp_size <= 1)
3733 size = 1;
3734 else if (tmp_size > 12 && tmp_size < 28 && tmp_size % 2)
3735 size = tmp_size + (is_increase ? 1 : -1);
3736 else if (tmp_size > 28 && tmp_size < 36)
3737 size = is_increase ? 36 : 28;
3738 else if (tmp_size > 36 && tmp_size < 48)
3739 size = is_increase ? 48 : 36;
3740 else if (tmp_size > 48 && tmp_size < 72)
3741 size = is_increase ? 72 : 48;
3742 else if (tmp_size > 72 && tmp_size < 80)
3743 size = is_increase ? 80 : 72;
3744 else if (tmp_size > 80 && tmp_size < 1638)
3745 size = 10 * (is_increase ? (tmp_size / 10 + 1) : (tmp_size / 10));
3746 else if (tmp_size >= 1638)
3747 size = 1638;
3748 else
3749 size = tmp_size;
3751 cf.yHeight = size * 20; /* convert twips to points */
3752 ME_SetSelectionCharFormat(editor, &cf);
3753 ME_CommitUndo(editor);
3754 ME_WrapMarkedParagraphs(editor);
3755 ME_UpdateScrollBar(editor);
3757 return TRUE;
3759 case EM_SETOPTIONS:
3761 /* these flags are equivalent to ES_* counterparts, except for
3762 * ECO_AUTOWORDSELECTION that doesn't have an ES_* counterpart,
3763 * but is still stored in editor->styleFlags. */
3764 const DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
3765 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN |
3766 ECO_SELECTIONBAR | ECO_AUTOWORDSELECTION;
3767 DWORD settings = mask & editor->styleFlags;
3768 DWORD oldSettings = settings;
3769 DWORD changedSettings;
3771 switch(wParam)
3773 case ECOOP_SET:
3774 settings = lParam;
3775 break;
3776 case ECOOP_OR:
3777 settings |= lParam;
3778 break;
3779 case ECOOP_AND:
3780 settings &= lParam;
3781 break;
3782 case ECOOP_XOR:
3783 settings ^= lParam;
3785 changedSettings = oldSettings ^ settings;
3787 if (changedSettings) {
3788 editor->styleFlags = (editor->styleFlags & ~mask) | (settings & mask);
3790 if (changedSettings & ECO_SELECTIONBAR)
3792 ITextHost_TxInvalidateRect(editor->texthost, &editor->rcFormat, TRUE);
3793 if (settings & ECO_SELECTIONBAR) {
3794 assert(!editor->selofs);
3795 editor->selofs = SELECTIONBAR_WIDTH;
3796 editor->rcFormat.left += editor->selofs;
3797 } else {
3798 editor->rcFormat.left -= editor->selofs;
3799 editor->selofs = 0;
3801 ME_RewrapRepaint(editor);
3804 if ((changedSettings & settings & ES_NOHIDESEL) && !editor->bHaveFocus)
3805 ME_InvalidateSelection( editor );
3807 if (changedSettings & settings & ECO_VERTICAL)
3808 FIXME("ECO_VERTICAL not implemented yet!\n");
3809 if (changedSettings & settings & ECO_AUTOHSCROLL)
3810 FIXME("ECO_AUTOHSCROLL not implemented yet!\n");
3811 if (changedSettings & settings & ECO_AUTOVSCROLL)
3812 FIXME("ECO_AUTOVSCROLL not implemented yet!\n");
3813 if (changedSettings & settings & ECO_WANTRETURN)
3814 FIXME("ECO_WANTRETURN not implemented yet!\n");
3815 if (changedSettings & settings & ECO_AUTOWORDSELECTION)
3816 FIXME("ECO_AUTOWORDSELECTION not implemented yet!\n");
3819 return settings;
3821 case EM_SETSEL:
3823 return set_selection( editor, wParam, lParam );
3825 case EM_SETSCROLLPOS:
3827 POINT *point = (POINT *)lParam;
3828 ME_ScrollAbs(editor, point->x, point->y);
3829 return 0;
3831 case EM_AUTOURLDETECT:
3833 if (wParam==1 || wParam ==0)
3835 editor->AutoURLDetect_bEnable = (BOOL)wParam;
3836 return 0;
3838 return E_INVALIDARG;
3840 case EM_GETAUTOURLDETECT:
3842 return editor->AutoURLDetect_bEnable;
3844 case EM_EXSETSEL:
3846 CHARRANGE range = *(CHARRANGE *)lParam;
3848 return set_selection( editor, range.cpMin, range.cpMax );
3850 case EM_SHOWSCROLLBAR:
3852 DWORD flags;
3854 switch (wParam)
3856 case SB_HORZ:
3857 flags = WS_HSCROLL;
3858 break;
3859 case SB_VERT:
3860 flags = WS_VSCROLL;
3861 break;
3862 case SB_BOTH:
3863 flags = WS_HSCROLL|WS_VSCROLL;
3864 break;
3865 default:
3866 return 0;
3869 if (lParam) {
3870 editor->styleFlags |= flags;
3871 if (flags & WS_HSCROLL)
3872 ITextHost_TxShowScrollBar(editor->texthost, SB_HORZ,
3873 editor->nTotalWidth > editor->sizeWindow.cx);
3874 if (flags & WS_VSCROLL)
3875 ITextHost_TxShowScrollBar(editor->texthost, SB_VERT,
3876 editor->nTotalLength > editor->sizeWindow.cy);
3877 } else {
3878 editor->styleFlags &= ~flags;
3879 ITextHost_TxShowScrollBar(editor->texthost, wParam, FALSE);
3881 return 0;
3883 case EM_SETTEXTEX:
3885 LPWSTR wszText;
3886 SETTEXTEX *pStruct = (SETTEXTEX *)wParam;
3887 int from, to, len;
3888 ME_Style *style;
3889 BOOL bRtf, bUnicode, bSelection, bUTF8;
3890 int oldModify = editor->nModifyStep;
3891 static const char utf8_bom[] = {0xef, 0xbb, 0xbf};
3893 if (!pStruct) return 0;
3895 /* If we detect ascii rtf at the start of the string,
3896 * we know it isn't unicode. */
3897 bRtf = (lParam && (!strncmp((char *)lParam, "{\\rtf", 5) ||
3898 !strncmp((char *)lParam, "{\\urtf", 6)));
3899 bUnicode = !bRtf && pStruct->codepage == CP_UNICODE;
3900 bUTF8 = (lParam && (!strncmp((char *)lParam, utf8_bom, 3)));
3902 TRACE("EM_SETTEXTEX - %s, flags %d, cp %d\n",
3903 bUnicode ? debugstr_w((LPCWSTR)lParam) : debugstr_a((LPCSTR)lParam),
3904 pStruct->flags, pStruct->codepage);
3906 bSelection = (pStruct->flags & ST_SELECTION) != 0;
3907 if (bSelection) {
3908 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3909 style = ME_GetSelectionInsertStyle(editor);
3910 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to - from, FALSE);
3911 } else {
3912 ME_Cursor start;
3913 ME_SetCursorToStart(editor, &start);
3914 ME_InternalDeleteText(editor, &start, ME_GetTextLength(editor), FALSE);
3915 style = editor->pBuffer->pDefaultStyle;
3918 if (bRtf) {
3919 ME_StreamInRTFString(editor, bSelection, (char *)lParam);
3920 if (bSelection) {
3921 /* FIXME: The length returned doesn't include the rtf control
3922 * characters, only the actual text. */
3923 len = lParam ? strlen((char *)lParam) : 0;
3925 } else {
3926 if (bUTF8 && !bUnicode) {
3927 wszText = ME_ToUnicode(CP_UTF8, (void *)(lParam+3), &len);
3928 ME_InsertTextFromCursor(editor, 0, wszText, len, style);
3929 ME_EndToUnicode(CP_UTF8, wszText);
3930 } else {
3931 wszText = ME_ToUnicode(pStruct->codepage, (void *)lParam, &len);
3932 ME_InsertTextFromCursor(editor, 0, wszText, len, style);
3933 ME_EndToUnicode(pStruct->codepage, wszText);
3937 if (bSelection) {
3938 ME_ReleaseStyle(style);
3939 ME_UpdateSelectionLinkAttribute(editor);
3940 } else {
3941 ME_Cursor cursor;
3942 len = 1;
3943 ME_SetCursorToStart(editor, &cursor);
3944 ME_UpdateLinkAttribute(editor, &cursor, INT_MAX);
3946 ME_CommitUndo(editor);
3947 if (!(pStruct->flags & ST_KEEPUNDO))
3949 editor->nModifyStep = oldModify;
3950 ME_EmptyUndoStack(editor);
3952 ME_UpdateRepaint(editor, FALSE);
3953 return len;
3955 case EM_SELECTIONTYPE:
3956 return ME_GetSelectionType(editor);
3957 case EM_SETBKGNDCOLOR:
3959 LRESULT lColor;
3960 if (editor->rgbBackColor != -1) {
3961 DeleteObject(editor->hbrBackground);
3962 lColor = editor->rgbBackColor;
3964 else lColor = ITextHost_TxGetSysColor(editor->texthost, COLOR_WINDOW);
3966 if (wParam)
3968 editor->rgbBackColor = -1;
3969 editor->hbrBackground = GetSysColorBrush(COLOR_WINDOW);
3971 else
3973 editor->rgbBackColor = lParam;
3974 editor->hbrBackground = CreateSolidBrush(editor->rgbBackColor);
3976 ITextHost_TxInvalidateRect(editor->texthost, NULL, TRUE);
3977 return lColor;
3979 case EM_GETMODIFY:
3980 return editor->nModifyStep == 0 ? 0 : -1;
3981 case EM_SETMODIFY:
3983 if (wParam)
3984 editor->nModifyStep = 1;
3985 else
3986 editor->nModifyStep = 0;
3988 return 0;
3990 case EM_SETREADONLY:
3992 if (wParam)
3993 editor->styleFlags |= ES_READONLY;
3994 else
3995 editor->styleFlags &= ~ES_READONLY;
3996 return 1;
3998 case EM_SETEVENTMASK:
4000 DWORD nOldMask = editor->nEventMask;
4002 editor->nEventMask = lParam;
4003 return nOldMask;
4005 case EM_GETEVENTMASK:
4006 return editor->nEventMask;
4007 case EM_SETCHARFORMAT:
4008 return handle_EM_SETCHARFORMAT( editor, wParam, (CHARFORMAT2W *)lParam );
4009 case EM_GETCHARFORMAT:
4011 CHARFORMAT2W tmp, *dst = (CHARFORMAT2W *)lParam;
4012 if (dst->cbSize != sizeof(CHARFORMATA) &&
4013 dst->cbSize != sizeof(CHARFORMATW) &&
4014 dst->cbSize != sizeof(CHARFORMAT2A) &&
4015 dst->cbSize != sizeof(CHARFORMAT2W))
4016 return 0;
4017 tmp.cbSize = sizeof(tmp);
4018 if (!wParam)
4019 ME_GetDefaultCharFormat(editor, &tmp);
4020 else
4021 ME_GetSelectionCharFormat(editor, &tmp);
4022 cf2w_to_cfany(dst, &tmp);
4023 return tmp.dwMask;
4025 case EM_SETPARAFORMAT:
4027 BOOL result = ME_SetSelectionParaFormat(editor, (PARAFORMAT2 *)lParam);
4028 ME_WrapMarkedParagraphs(editor);
4029 ME_UpdateScrollBar(editor);
4030 ME_CommitUndo(editor);
4031 return result;
4033 case EM_GETPARAFORMAT:
4034 ME_GetSelectionParaFormat(editor, (PARAFORMAT2 *)lParam);
4035 return ((PARAFORMAT2 *)lParam)->dwMask;
4036 case EM_GETFIRSTVISIBLELINE:
4038 ME_DisplayItem *p = editor->pBuffer->pFirst;
4039 int y = editor->vert_si.nPos;
4040 int ypara = 0;
4041 int count = 0;
4042 int ystart, yend;
4043 while(p) {
4044 p = ME_FindItemFwd(p, diStartRowOrParagraphOrEnd);
4045 if (p->type == diTextEnd)
4046 break;
4047 if (p->type == diParagraph) {
4048 ypara = p->member.para.pt.y;
4049 continue;
4051 ystart = ypara + p->member.row.pt.y;
4052 yend = ystart + p->member.row.nHeight;
4053 if (y < yend) {
4054 break;
4056 count++;
4058 return count;
4060 case EM_HIDESELECTION:
4062 editor->bHideSelection = (wParam != 0);
4063 ME_InvalidateSelection(editor);
4064 return 0;
4066 case EM_LINESCROLL:
4068 if (!(editor->styleFlags & ES_MULTILINE))
4069 return FALSE;
4070 ME_ScrollDown( editor, lParam * get_default_line_height( editor ) );
4071 return TRUE;
4073 case WM_CLEAR:
4075 int from, to;
4076 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
4077 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to-from, FALSE);
4078 ME_CommitUndo(editor);
4079 ME_UpdateRepaint(editor, TRUE);
4080 return 0;
4082 case EM_REPLACESEL:
4084 int len = 0;
4085 LONG codepage = unicode ? CP_UNICODE : CP_ACP;
4086 LPWSTR wszText = ME_ToUnicode(codepage, (void *)lParam, &len);
4088 TRACE("EM_REPLACESEL - %s\n", debugstr_w(wszText));
4090 ME_ReplaceSel(editor, !!wParam, wszText, len);
4091 ME_EndToUnicode(codepage, wszText);
4092 return len;
4094 case EM_SCROLLCARET:
4095 ME_EnsureVisible(editor, &editor->pCursors[0]);
4096 return 0;
4097 case WM_SETFONT:
4099 LOGFONTW lf;
4100 CHARFORMAT2W fmt;
4101 HDC hDC;
4102 BOOL bRepaint = LOWORD(lParam);
4104 if (!wParam)
4105 wParam = (WPARAM)GetStockObject(SYSTEM_FONT);
4107 if (!GetObjectW((HGDIOBJ)wParam, sizeof(LOGFONTW), &lf))
4108 return 0;
4110 hDC = ITextHost_TxGetDC(editor->texthost);
4111 ME_CharFormatFromLogFont(hDC, &lf, &fmt);
4112 ITextHost_TxReleaseDC(editor->texthost, hDC);
4113 if (editor->mode & TM_RICHTEXT) {
4114 ME_Cursor start;
4115 ME_SetCursorToStart(editor, &start);
4116 ME_SetCharFormat(editor, &start, NULL, &fmt);
4118 ME_SetDefaultCharFormat(editor, &fmt);
4120 ME_CommitUndo(editor);
4121 ME_MarkAllForWrapping(editor);
4122 ME_WrapMarkedParagraphs(editor);
4123 ME_UpdateScrollBar(editor);
4124 if (bRepaint)
4125 ME_Repaint(editor);
4126 return 0;
4128 case WM_SETTEXT:
4130 ME_Cursor cursor;
4131 ME_SetCursorToStart(editor, &cursor);
4132 ME_InternalDeleteText(editor, &cursor, ME_GetTextLength(editor), FALSE);
4133 if (lParam)
4135 TRACE("WM_SETTEXT lParam==%lx\n",lParam);
4136 if (!strncmp((char *)lParam, "{\\rtf", 5) ||
4137 !strncmp((char *)lParam, "{\\urtf", 6))
4139 /* Undocumented: WM_SETTEXT supports RTF text */
4140 ME_StreamInRTFString(editor, 0, (char *)lParam);
4142 else
4143 ME_SetText(editor, (void*)lParam, unicode);
4145 else
4146 TRACE("WM_SETTEXT - NULL\n");
4147 ME_SetCursorToStart(editor, &cursor);
4148 ME_UpdateLinkAttribute(editor, &cursor, INT_MAX);
4149 set_selection_cursors(editor, 0, 0);
4150 editor->nModifyStep = 0;
4151 ME_CommitUndo(editor);
4152 ME_EmptyUndoStack(editor);
4153 ME_UpdateRepaint(editor, FALSE);
4154 return 1;
4156 case EM_CANPASTE:
4157 return paste_special( editor, 0, NULL, TRUE );
4158 case WM_PASTE:
4159 case WM_MBUTTONDOWN:
4160 wParam = 0;
4161 lParam = 0;
4162 /* fall through */
4163 case EM_PASTESPECIAL:
4164 paste_special( editor, wParam, (REPASTESPECIAL *)lParam, FALSE );
4165 return 0;
4166 case WM_CUT:
4167 case WM_COPY:
4168 copy_or_cut(editor, msg == WM_CUT);
4169 return 0;
4170 case WM_GETTEXTLENGTH:
4172 GETTEXTLENGTHEX how;
4174 /* CR/LF conversion required in 2.0 mode, verbatim in 1.0 mode */
4175 how.flags = GTL_CLOSE | (editor->bEmulateVersion10 ? 0 : GTL_USECRLF) | GTL_NUMCHARS;
4176 how.codepage = unicode ? CP_UNICODE : CP_ACP;
4177 return ME_GetTextLengthEx(editor, &how);
4179 case EM_GETTEXTLENGTHEX:
4180 return ME_GetTextLengthEx(editor, (GETTEXTLENGTHEX *)wParam);
4181 case WM_GETTEXT:
4183 GETTEXTEX ex;
4184 ex.cb = wParam * (unicode ? sizeof(WCHAR) : sizeof(CHAR));
4185 ex.flags = GT_USECRLF;
4186 ex.codepage = unicode ? CP_UNICODE : CP_ACP;
4187 ex.lpDefaultChar = NULL;
4188 ex.lpUsedDefChar = NULL;
4189 return ME_GetTextEx(editor, &ex, lParam);
4191 case EM_GETTEXTEX:
4192 return ME_GetTextEx(editor, (GETTEXTEX*)wParam, lParam);
4193 case EM_GETSELTEXT:
4195 int nFrom, nTo, nStartCur = ME_GetSelectionOfs(editor, &nFrom, &nTo);
4196 ME_Cursor *from = &editor->pCursors[nStartCur];
4197 return ME_GetTextRange(editor, (WCHAR *)lParam, from,
4198 nTo - nFrom, unicode);
4200 case EM_GETSCROLLPOS:
4202 POINT *point = (POINT *)lParam;
4203 point->x = editor->horz_si.nPos;
4204 point->y = editor->vert_si.nPos;
4205 /* 16-bit scaled value is returned as stored in scrollinfo */
4206 if (editor->horz_si.nMax > 0xffff)
4207 point->x = MulDiv(point->x, 0xffff, editor->horz_si.nMax);
4208 if (editor->vert_si.nMax > 0xffff)
4209 point->y = MulDiv(point->y, 0xffff, editor->vert_si.nMax);
4210 return 1;
4212 case EM_GETTEXTRANGE:
4214 TEXTRANGEW *rng = (TEXTRANGEW *)lParam;
4215 ME_Cursor start;
4216 int nStart = rng->chrg.cpMin;
4217 int nEnd = rng->chrg.cpMax;
4218 int textlength = ME_GetTextLength(editor);
4220 TRACE("EM_GETTEXTRANGE min=%d max=%d unicode=%d textlength=%d\n",
4221 rng->chrg.cpMin, rng->chrg.cpMax, unicode, textlength);
4222 if (nStart < 0) return 0;
4223 if ((nStart == 0 && nEnd == -1) || nEnd > textlength)
4224 nEnd = textlength;
4225 if (nStart >= nEnd) return 0;
4227 cursor_from_char_ofs( editor, nStart, &start );
4228 return ME_GetTextRange(editor, rng->lpstrText, &start, nEnd - nStart, unicode);
4230 case EM_GETLINE:
4232 ME_DisplayItem *run;
4233 const unsigned int nMaxChars = *(WORD *) lParam;
4234 unsigned int nCharsLeft = nMaxChars;
4235 char *dest = (char *) lParam;
4236 BOOL wroteNull = FALSE;
4238 TRACE("EM_GETLINE: row=%d, nMaxChars=%d (%s)\n", (int) wParam, nMaxChars,
4239 unicode ? "Unicode" : "Ansi");
4241 run = ME_FindRowWithNumber(editor, wParam);
4242 if (run == NULL)
4243 return 0;
4245 while (nCharsLeft && (run = ME_FindItemFwd(run, diRunOrStartRow))
4246 && run->type == diRun)
4248 WCHAR *str = get_text( &run->member.run, 0 );
4249 unsigned int nCopy;
4251 nCopy = min(nCharsLeft, run->member.run.len);
4253 if (unicode)
4254 memcpy(dest, str, nCopy * sizeof(WCHAR));
4255 else
4256 nCopy = WideCharToMultiByte(CP_ACP, 0, str, nCopy, dest,
4257 nCharsLeft, NULL, NULL);
4258 dest += nCopy * (unicode ? sizeof(WCHAR) : 1);
4259 nCharsLeft -= nCopy;
4262 /* append line termination, space allowing */
4263 if (nCharsLeft > 0)
4265 if (unicode)
4266 *((WCHAR *)dest) = '\0';
4267 else
4268 *dest = '\0';
4269 nCharsLeft--;
4270 wroteNull = TRUE;
4273 TRACE("EM_GETLINE: got %u characters\n", nMaxChars - nCharsLeft);
4274 return nMaxChars - nCharsLeft - (wroteNull ? 1 : 0);
4276 case EM_GETLINECOUNT:
4278 ME_DisplayItem *item = editor->pBuffer->pLast;
4279 int nRows = editor->total_rows;
4280 ME_DisplayItem *prev_para = NULL, *last_para = NULL;
4282 last_para = ME_FindItemBack(item, diRun);
4283 prev_para = ME_FindItemBack(last_para, diRun);
4284 assert(last_para);
4285 assert(last_para->member.run.nFlags & MERF_ENDPARA);
4286 if (editor->bEmulateVersion10 && prev_para &&
4287 last_para->member.run.nCharOfs == 0 &&
4288 prev_para->member.run.len == 1 &&
4289 *get_text( &prev_para->member.run, 0 ) == '\r')
4291 /* In 1.0 emulation, the last solitary \r at the very end of the text
4292 (if one exists) is NOT a line break.
4293 FIXME: this is an ugly hack. This should have a more regular model. */
4294 nRows--;
4297 TRACE("EM_GETLINECOUNT: nRows==%d\n", nRows);
4298 return max(1, nRows);
4300 case EM_LINEFROMCHAR:
4302 if (wParam == -1)
4303 return ME_RowNumberFromCharOfs(editor, ME_GetCursorOfs(&editor->pCursors[1]));
4304 else
4305 return ME_RowNumberFromCharOfs(editor, wParam);
4307 case EM_EXLINEFROMCHAR:
4309 if (lParam == -1)
4310 return ME_RowNumberFromCharOfs(editor, ME_GetCursorOfs(&editor->pCursors[1]));
4311 else
4312 return ME_RowNumberFromCharOfs(editor, lParam);
4314 case EM_LINEINDEX:
4316 ME_DisplayItem *item, *para;
4317 int nCharOfs;
4319 if (wParam == -1)
4320 item = ME_FindItemBack(editor->pCursors[0].pRun, diStartRow);
4321 else
4322 item = ME_FindRowWithNumber(editor, wParam);
4323 if (!item)
4324 return -1;
4325 para = ME_GetParagraph(item);
4326 item = ME_FindItemFwd(item, diRun);
4327 nCharOfs = para->member.para.nCharOfs + item->member.run.nCharOfs;
4328 TRACE("EM_LINEINDEX: nCharOfs==%d\n", nCharOfs);
4329 return nCharOfs;
4331 case EM_LINELENGTH:
4333 ME_DisplayItem *item, *item_end;
4334 int nChars = 0, nThisLineOfs = 0, nNextLineOfs = 0;
4335 ME_Cursor cursor;
4337 if (wParam > ME_GetTextLength(editor))
4338 return 0;
4339 if (wParam == -1)
4341 FIXME("EM_LINELENGTH: returning number of unselected characters on lines with selection unsupported.\n");
4342 return 0;
4344 cursor_from_char_ofs( editor, wParam, &cursor );
4345 item = ME_RowStart( cursor.pRun );
4346 nThisLineOfs = run_char_ofs( &ME_FindItemFwd( item, diRun )->member.run, 0 );
4347 item_end = ME_FindItemFwd(item, diStartRowOrParagraphOrEnd);
4348 if (item_end->type == diStartRow)
4349 nNextLineOfs = run_char_ofs( &ME_FindItemFwd( item_end, diRun )->member.run, 0 );
4350 else
4352 ME_DisplayItem *endRun = ME_FindItemBack(item_end, diRun);
4353 assert(endRun && endRun->member.run.nFlags & MERF_ENDPARA);
4354 nNextLineOfs = run_char_ofs( &endRun->member.run, 0 );
4356 nChars = nNextLineOfs - nThisLineOfs;
4357 TRACE("EM_LINELENGTH(%ld)==%d\n", wParam, nChars);
4358 return nChars;
4360 case EM_EXLIMITTEXT:
4362 if ((int)lParam < 0)
4363 return 0;
4364 if (lParam == 0)
4365 editor->nTextLimit = 65536;
4366 else
4367 editor->nTextLimit = (int) lParam;
4368 return 0;
4370 case EM_LIMITTEXT:
4372 if (wParam == 0)
4373 editor->nTextLimit = 65536;
4374 else
4375 editor->nTextLimit = (int) wParam;
4376 return 0;
4378 case EM_GETLIMITTEXT:
4380 return editor->nTextLimit;
4382 case EM_FINDTEXT:
4384 LRESULT r;
4385 if(!unicode){
4386 FINDTEXTA *ft = (FINDTEXTA *)lParam;
4387 int nChars = MultiByteToWideChar(CP_ACP, 0, ft->lpstrText, -1, NULL, 0);
4388 WCHAR *tmp;
4390 if ((tmp = heap_alloc(nChars * sizeof(*tmp))) != NULL)
4391 MultiByteToWideChar(CP_ACP, 0, ft->lpstrText, -1, tmp, nChars);
4392 r = ME_FindText(editor, wParam, &ft->chrg, tmp, NULL);
4393 heap_free(tmp);
4394 }else{
4395 FINDTEXTW *ft = (FINDTEXTW *)lParam;
4396 r = ME_FindText(editor, wParam, &ft->chrg, ft->lpstrText, NULL);
4398 return r;
4400 case EM_FINDTEXTEX:
4402 LRESULT r;
4403 if(!unicode){
4404 FINDTEXTEXA *ex = (FINDTEXTEXA *)lParam;
4405 int nChars = MultiByteToWideChar(CP_ACP, 0, ex->lpstrText, -1, NULL, 0);
4406 WCHAR *tmp;
4408 if ((tmp = heap_alloc(nChars * sizeof(*tmp))) != NULL)
4409 MultiByteToWideChar(CP_ACP, 0, ex->lpstrText, -1, tmp, nChars);
4410 r = ME_FindText(editor, wParam, &ex->chrg, tmp, &ex->chrgText);
4411 heap_free(tmp);
4412 }else{
4413 FINDTEXTEXW *ex = (FINDTEXTEXW *)lParam;
4414 r = ME_FindText(editor, wParam, &ex->chrg, ex->lpstrText, &ex->chrgText);
4416 return r;
4418 case EM_FINDTEXTW:
4420 FINDTEXTW *ft = (FINDTEXTW *)lParam;
4421 return ME_FindText(editor, wParam, &ft->chrg, ft->lpstrText, NULL);
4423 case EM_FINDTEXTEXW:
4425 FINDTEXTEXW *ex = (FINDTEXTEXW *)lParam;
4426 return ME_FindText(editor, wParam, &ex->chrg, ex->lpstrText, &ex->chrgText);
4428 case EM_GETZOOM:
4429 if (!wParam || !lParam)
4430 return FALSE;
4431 *(int *)wParam = editor->nZoomNumerator;
4432 *(int *)lParam = editor->nZoomDenominator;
4433 return TRUE;
4434 case EM_SETZOOM:
4435 return ME_SetZoom(editor, wParam, lParam);
4436 case EM_CHARFROMPOS:
4438 ME_Cursor cursor;
4439 if (ME_CharFromPos(editor, ((POINTL *)lParam)->x, ((POINTL *)lParam)->y,
4440 &cursor, NULL))
4441 return ME_GetCursorOfs(&cursor);
4442 else
4443 return -1;
4445 case EM_POSFROMCHAR:
4447 ME_Cursor cursor;
4448 int nCharOfs, nLength;
4449 POINTL pt = {0,0};
4451 nCharOfs = wParam;
4452 /* detect which API version we're dealing with */
4453 if (wParam >= 0x40000)
4454 nCharOfs = lParam;
4455 nLength = ME_GetTextLength(editor);
4456 nCharOfs = min(nCharOfs, nLength);
4457 nCharOfs = max(nCharOfs, 0);
4459 cursor_from_char_ofs( editor, nCharOfs, &cursor );
4460 pt.y = cursor.pRun->member.run.pt.y;
4461 pt.x = cursor.pRun->member.run.pt.x +
4462 ME_PointFromChar( editor, &cursor.pRun->member.run, cursor.nOffset, TRUE );
4463 pt.y += cursor.pPara->member.para.pt.y + editor->rcFormat.top;
4464 pt.x += editor->rcFormat.left;
4466 pt.x -= editor->horz_si.nPos;
4467 pt.y -= editor->vert_si.nPos;
4469 if (wParam >= 0x40000) {
4470 *(POINTL *)wParam = pt;
4472 return (wParam >= 0x40000) ? 0 : MAKELONG( pt.x, pt.y );
4474 case WM_CREATE:
4475 return ME_WmCreate(editor, lParam, unicode);
4476 case WM_DESTROY:
4477 ME_DestroyEditor(editor);
4478 return 0;
4479 case WM_SETCURSOR:
4481 POINT cursor_pos;
4482 if (wParam == (WPARAM)editor->hWnd && GetCursorPos(&cursor_pos) &&
4483 ScreenToClient(editor->hWnd, &cursor_pos))
4484 ME_LinkNotify(editor, msg, 0, MAKELPARAM(cursor_pos.x, cursor_pos.y));
4485 return ME_SetCursor(editor);
4487 case WM_LBUTTONDBLCLK:
4488 case WM_LBUTTONDOWN:
4490 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4491 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4492 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4493 return 0;
4494 ITextHost_TxSetFocus(editor->texthost);
4495 ME_LButtonDown(editor, (short)LOWORD(lParam), (short)HIWORD(lParam),
4496 ME_CalculateClickCount(editor, msg, wParam, lParam));
4497 ITextHost_TxSetCapture(editor->texthost, TRUE);
4498 editor->bMouseCaptured = TRUE;
4499 ME_LinkNotify(editor, msg, wParam, lParam);
4500 if (!ME_SetCursor(editor)) goto do_default;
4501 break;
4503 case WM_MOUSEMOVE:
4504 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4505 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4506 return 0;
4507 if (editor->bMouseCaptured)
4508 ME_MouseMove(editor, (short)LOWORD(lParam), (short)HIWORD(lParam));
4509 else
4510 ME_LinkNotify(editor, msg, wParam, lParam);
4511 /* Set cursor if mouse is captured, since WM_SETCURSOR won't be received. */
4512 if (editor->bMouseCaptured)
4513 ME_SetCursor(editor);
4514 break;
4515 case WM_LBUTTONUP:
4516 if (editor->bMouseCaptured) {
4517 ITextHost_TxSetCapture(editor->texthost, FALSE);
4518 editor->bMouseCaptured = FALSE;
4520 if (editor->nSelectionType == stDocument)
4521 editor->nSelectionType = stPosition;
4522 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4523 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4524 return 0;
4525 else
4527 ME_SetCursor(editor);
4528 ME_LinkNotify(editor, msg, wParam, lParam);
4530 break;
4531 case WM_RBUTTONUP:
4532 case WM_RBUTTONDOWN:
4533 case WM_RBUTTONDBLCLK:
4534 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4535 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4536 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4537 return 0;
4538 ME_LinkNotify(editor, msg, wParam, lParam);
4539 goto do_default;
4540 case WM_CONTEXTMENU:
4541 if (!ME_ShowContextMenu(editor, (short)LOWORD(lParam), (short)HIWORD(lParam)))
4542 goto do_default;
4543 break;
4544 case WM_SETFOCUS:
4545 editor->bHaveFocus = TRUE;
4546 create_caret(editor);
4547 update_caret(editor);
4548 ME_SendOldNotify(editor, EN_SETFOCUS);
4549 if (!editor->bHideSelection && !(editor->styleFlags & ES_NOHIDESEL))
4550 ME_InvalidateSelection( editor );
4551 return 0;
4552 case WM_KILLFOCUS:
4553 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4554 editor->bHaveFocus = FALSE;
4555 editor->wheel_remain = 0;
4556 hide_caret(editor);
4557 DestroyCaret();
4558 ME_SendOldNotify(editor, EN_KILLFOCUS);
4559 if (!editor->bHideSelection && !(editor->styleFlags & ES_NOHIDESEL))
4560 ME_InvalidateSelection( editor );
4561 return 0;
4562 case WM_COMMAND:
4563 TRACE("editor wnd command = %d\n", LOWORD(wParam));
4564 return 0;
4565 case WM_KEYUP:
4566 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4567 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4568 return 0;
4569 goto do_default;
4570 case WM_KEYDOWN:
4571 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4572 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4573 return 0;
4574 if (ME_KeyDown(editor, LOWORD(wParam)))
4575 return 0;
4576 goto do_default;
4577 case WM_CHAR:
4578 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4579 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4580 return 0;
4581 return ME_Char(editor, wParam, lParam, unicode);
4582 case WM_UNICHAR:
4583 if (unicode)
4585 if(wParam == UNICODE_NOCHAR) return TRUE;
4586 if(wParam <= 0x000fffff)
4588 if(wParam > 0xffff) /* convert to surrogates */
4590 wParam -= 0x10000;
4591 ME_Char(editor, (wParam >> 10) + 0xd800, 0, TRUE);
4592 ME_Char(editor, (wParam & 0x03ff) + 0xdc00, 0, TRUE);
4593 } else {
4594 ME_Char(editor, wParam, 0, TRUE);
4597 return 0;
4599 break;
4600 case EM_STOPGROUPTYPING:
4601 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4602 return 0;
4603 case WM_HSCROLL:
4605 const int scrollUnit = 7;
4607 switch(LOWORD(wParam))
4609 case SB_LEFT:
4610 ME_ScrollAbs(editor, 0, 0);
4611 break;
4612 case SB_RIGHT:
4613 ME_ScrollAbs(editor,
4614 editor->horz_si.nMax - (int)editor->horz_si.nPage,
4615 editor->vert_si.nMax - (int)editor->vert_si.nPage);
4616 break;
4617 case SB_LINELEFT:
4618 ME_ScrollLeft(editor, scrollUnit);
4619 break;
4620 case SB_LINERIGHT:
4621 ME_ScrollRight(editor, scrollUnit);
4622 break;
4623 case SB_PAGELEFT:
4624 ME_ScrollLeft(editor, editor->sizeWindow.cx);
4625 break;
4626 case SB_PAGERIGHT:
4627 ME_ScrollRight(editor, editor->sizeWindow.cx);
4628 break;
4629 case SB_THUMBTRACK:
4630 case SB_THUMBPOSITION:
4632 int pos = HIWORD(wParam);
4633 if (editor->horz_si.nMax > 0xffff)
4634 pos = MulDiv(pos, editor->horz_si.nMax, 0xffff);
4635 ME_HScrollAbs(editor, pos);
4636 break;
4639 break;
4641 case EM_SCROLL: /* fall through */
4642 case WM_VSCROLL:
4644 int origNPos;
4645 int lineHeight = get_default_line_height( editor );
4647 origNPos = editor->vert_si.nPos;
4649 switch(LOWORD(wParam))
4651 case SB_TOP:
4652 ME_ScrollAbs(editor, 0, 0);
4653 break;
4654 case SB_BOTTOM:
4655 ME_ScrollAbs(editor,
4656 editor->horz_si.nMax - (int)editor->horz_si.nPage,
4657 editor->vert_si.nMax - (int)editor->vert_si.nPage);
4658 break;
4659 case SB_LINEUP:
4660 ME_ScrollUp(editor,lineHeight);
4661 break;
4662 case SB_LINEDOWN:
4663 ME_ScrollDown(editor,lineHeight);
4664 break;
4665 case SB_PAGEUP:
4666 ME_ScrollUp(editor,editor->sizeWindow.cy);
4667 break;
4668 case SB_PAGEDOWN:
4669 ME_ScrollDown(editor,editor->sizeWindow.cy);
4670 break;
4671 case SB_THUMBTRACK:
4672 case SB_THUMBPOSITION:
4674 int pos = HIWORD(wParam);
4675 if (editor->vert_si.nMax > 0xffff)
4676 pos = MulDiv(pos, editor->vert_si.nMax, 0xffff);
4677 ME_VScrollAbs(editor, pos);
4678 break;
4681 if (msg == EM_SCROLL)
4682 return 0x00010000 | (((editor->vert_si.nPos - origNPos)/lineHeight) & 0xffff);
4683 break;
4685 case WM_MOUSEWHEEL:
4687 int delta;
4688 BOOL ctrl_is_down;
4690 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4691 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4692 return 0;
4694 ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
4696 delta = GET_WHEEL_DELTA_WPARAM(wParam);
4698 /* if scrolling changes direction, ignore left overs */
4699 if ((delta < 0 && editor->wheel_remain < 0) ||
4700 (delta > 0 && editor->wheel_remain > 0))
4701 editor->wheel_remain += delta;
4702 else
4703 editor->wheel_remain = delta;
4705 if (editor->wheel_remain)
4707 if (ctrl_is_down) {
4708 int numerator;
4709 if (!editor->nZoomNumerator || !editor->nZoomDenominator)
4711 numerator = 100;
4712 } else {
4713 numerator = editor->nZoomNumerator * 100 / editor->nZoomDenominator;
4715 numerator += calc_wheel_change( &editor->wheel_remain, 10 );
4716 if (numerator >= 10 && numerator <= 500)
4717 ME_SetZoom(editor, numerator, 100);
4718 } else {
4719 UINT max_lines = 3;
4720 int lines = 0;
4722 SystemParametersInfoW( SPI_GETWHEELSCROLLLINES, 0, &max_lines, 0 );
4723 if (max_lines)
4724 lines = calc_wheel_change( &editor->wheel_remain, (int)max_lines );
4725 if (lines)
4726 ME_ScrollDown( editor, -lines * get_default_line_height( editor ) );
4729 break;
4731 case EM_GETRECT:
4733 *((RECT *)lParam) = editor->rcFormat;
4734 if (editor->bDefaultFormatRect)
4735 ((RECT *)lParam)->left -= editor->selofs;
4736 return 0;
4738 case EM_SETRECT:
4739 case EM_SETRECTNP:
4741 if (lParam)
4743 int border = 0;
4744 RECT clientRect;
4745 RECT *rc = (RECT *)lParam;
4747 border = editor->exStyleFlags & WS_EX_CLIENTEDGE ? 1 : 0;
4748 ITextHost_TxGetClientRect(editor->texthost, &clientRect);
4749 if (wParam == 0)
4751 editor->rcFormat.top = max(0, rc->top - border);
4752 editor->rcFormat.left = max(0, rc->left - border);
4753 editor->rcFormat.bottom = min(clientRect.bottom, rc->bottom);
4754 editor->rcFormat.right = min(clientRect.right, rc->right + border);
4755 } else if (wParam == 1) {
4756 /* MSDN incorrectly says a wParam value of 1 causes the
4757 * lParam rect to be used as a relative offset,
4758 * however, the tests show it just prevents min/max bound
4759 * checking. */
4760 editor->rcFormat.top = rc->top - border;
4761 editor->rcFormat.left = rc->left - border;
4762 editor->rcFormat.bottom = rc->bottom;
4763 editor->rcFormat.right = rc->right + border;
4764 } else {
4765 return 0;
4767 editor->bDefaultFormatRect = FALSE;
4769 else
4771 ME_SetDefaultFormatRect(editor);
4772 editor->bDefaultFormatRect = TRUE;
4774 ME_MarkAllForWrapping(editor);
4775 ME_WrapMarkedParagraphs(editor);
4776 ME_UpdateScrollBar(editor);
4777 if (msg != EM_SETRECTNP)
4778 ME_Repaint(editor);
4779 return 0;
4781 case EM_REQUESTRESIZE:
4782 ME_SendRequestResize(editor, TRUE);
4783 return 0;
4784 case WM_SETREDRAW:
4785 goto do_default;
4786 case WM_WINDOWPOSCHANGED:
4788 RECT clientRect;
4789 WINDOWPOS *winpos = (WINDOWPOS *)lParam;
4791 if (winpos->flags & SWP_NOCLIENTSIZE) goto do_default;
4792 ITextHost_TxGetClientRect(editor->texthost, &clientRect);
4793 if (editor->bDefaultFormatRect) {
4794 ME_SetDefaultFormatRect(editor);
4795 } else {
4796 editor->rcFormat.right += clientRect.right - editor->prevClientRect.right;
4797 editor->rcFormat.bottom += clientRect.bottom - editor->prevClientRect.bottom;
4799 editor->prevClientRect = clientRect;
4800 ME_RewrapRepaint(editor);
4801 goto do_default;
4803 /* IME messages to make richedit controls IME aware */
4804 case WM_IME_SETCONTEXT:
4805 case WM_IME_CONTROL:
4806 case WM_IME_SELECT:
4807 case WM_IME_COMPOSITIONFULL:
4808 return 0;
4809 case WM_IME_STARTCOMPOSITION:
4811 editor->imeStartIndex=ME_GetCursorOfs(&editor->pCursors[0]);
4812 ME_DeleteSelection(editor);
4813 ME_CommitUndo(editor);
4814 ME_UpdateRepaint(editor, FALSE);
4815 return 0;
4817 case WM_IME_COMPOSITION:
4819 HIMC hIMC;
4821 ME_Style *style = style_get_insert_style( editor, editor->pCursors );
4822 hIMC = ITextHost_TxImmGetContext(editor->texthost);
4823 ME_DeleteSelection(editor);
4824 ME_SaveTempStyle(editor, style);
4825 if (lParam & (GCS_RESULTSTR|GCS_COMPSTR))
4827 LPWSTR lpCompStr = NULL;
4828 DWORD dwBufLen;
4829 DWORD dwIndex = lParam & GCS_RESULTSTR;
4830 if (!dwIndex)
4831 dwIndex = GCS_COMPSTR;
4833 dwBufLen = ImmGetCompositionStringW(hIMC, dwIndex, NULL, 0);
4834 lpCompStr = HeapAlloc(GetProcessHeap(),0,dwBufLen + sizeof(WCHAR));
4835 ImmGetCompositionStringW(hIMC, dwIndex, lpCompStr, dwBufLen);
4836 lpCompStr[dwBufLen/sizeof(WCHAR)] = 0;
4837 ME_InsertTextFromCursor(editor,0,lpCompStr,dwBufLen/sizeof(WCHAR),style);
4838 HeapFree(GetProcessHeap(), 0, lpCompStr);
4840 if (dwIndex == GCS_COMPSTR)
4841 set_selection_cursors(editor,editor->imeStartIndex,
4842 editor->imeStartIndex + dwBufLen/sizeof(WCHAR));
4844 ME_ReleaseStyle(style);
4845 ME_CommitUndo(editor);
4846 ME_UpdateRepaint(editor, FALSE);
4847 return 0;
4849 case WM_IME_ENDCOMPOSITION:
4851 ME_DeleteSelection(editor);
4852 editor->imeStartIndex=-1;
4853 return 0;
4855 case EM_GETOLEINTERFACE:
4857 if (!editor->reOle)
4858 if (!CreateIRichEditOle(NULL, editor, (LPVOID *)&editor->reOle))
4859 return 0;
4860 if (IUnknown_QueryInterface(editor->reOle, &IID_IRichEditOle, (LPVOID *)lParam) == S_OK)
4861 return 1;
4862 return 0;
4864 case EM_GETPASSWORDCHAR:
4866 return editor->cPasswordMask;
4868 case EM_SETOLECALLBACK:
4869 if(editor->lpOleCallback)
4870 IRichEditOleCallback_Release(editor->lpOleCallback);
4871 editor->lpOleCallback = (IRichEditOleCallback*)lParam;
4872 if(editor->lpOleCallback)
4873 IRichEditOleCallback_AddRef(editor->lpOleCallback);
4874 return TRUE;
4875 case EM_GETWORDBREAKPROC:
4876 return (LRESULT)editor->pfnWordBreak;
4877 case EM_SETWORDBREAKPROC:
4879 EDITWORDBREAKPROCW pfnOld = editor->pfnWordBreak;
4881 editor->pfnWordBreak = (EDITWORDBREAKPROCW)lParam;
4882 return (LRESULT)pfnOld;
4884 case EM_GETTEXTMODE:
4885 return editor->mode;
4886 case EM_SETTEXTMODE:
4888 int mask = 0;
4889 int changes = 0;
4891 if (ME_GetTextLength(editor) ||
4892 !list_empty( &editor->undo_stack ) || !list_empty( &editor->redo_stack ))
4893 return E_UNEXPECTED;
4895 /* Check for mutually exclusive flags in adjacent bits of wParam */
4896 if ((wParam & (TM_RICHTEXT | TM_MULTILEVELUNDO | TM_MULTICODEPAGE)) &
4897 (wParam & (TM_PLAINTEXT | TM_SINGLELEVELUNDO | TM_SINGLECODEPAGE)) << 1)
4898 return E_INVALIDARG;
4900 if (wParam & (TM_RICHTEXT | TM_PLAINTEXT))
4902 mask |= TM_RICHTEXT | TM_PLAINTEXT;
4903 changes |= wParam & (TM_RICHTEXT | TM_PLAINTEXT);
4904 if (wParam & TM_PLAINTEXT) {
4905 /* Clear selection since it should be possible to select the
4906 * end of text run for rich text */
4907 ME_InvalidateSelection(editor);
4908 ME_SetCursorToStart(editor, &editor->pCursors[0]);
4909 editor->pCursors[1] = editor->pCursors[0];
4910 /* plain text can only have the default style. */
4911 ME_ClearTempStyle(editor);
4912 ME_AddRefStyle(editor->pBuffer->pDefaultStyle);
4913 ME_ReleaseStyle(editor->pCursors[0].pRun->member.run.style);
4914 editor->pCursors[0].pRun->member.run.style = editor->pBuffer->pDefaultStyle;
4917 /* FIXME: Currently no support for undo level and code page options */
4918 editor->mode = (editor->mode & ~mask) | changes;
4919 return 0;
4921 case EM_SETPASSWORDCHAR:
4923 editor->cPasswordMask = wParam;
4924 ME_RewrapRepaint(editor);
4925 return 0;
4927 case EM_SETTARGETDEVICE:
4928 if (wParam == 0)
4930 BOOL new = (lParam == 0 && (editor->styleFlags & ES_MULTILINE));
4931 if (editor->nAvailWidth || editor->bWordWrap != new)
4933 editor->bWordWrap = new;
4934 editor->nAvailWidth = 0; /* wrap to client area */
4935 ME_RewrapRepaint(editor);
4937 } else {
4938 int width = max(0, lParam);
4939 if ((editor->styleFlags & ES_MULTILINE) &&
4940 (!editor->bWordWrap || editor->nAvailWidth != width))
4942 editor->nAvailWidth = width;
4943 editor->bWordWrap = TRUE;
4944 ME_RewrapRepaint(editor);
4946 FIXME("EM_SETTARGETDEVICE doesn't use non-NULL target devices\n");
4948 return TRUE;
4949 default:
4950 do_default:
4951 *phresult = S_FALSE;
4952 break;
4954 return 0L;
4957 static BOOL create_windowed_editor(HWND hwnd, CREATESTRUCTW *create, BOOL emulate_10)
4959 ITextHost *host = ME_CreateTextHost( hwnd, create, emulate_10 );
4960 ME_TextEditor *editor;
4962 if (!host) return FALSE;
4964 editor = ME_MakeEditor( host, emulate_10 );
4965 if (!editor)
4967 ITextHost_Release( host );
4968 return FALSE;
4971 editor->exStyleFlags = GetWindowLongW( hwnd, GWL_EXSTYLE );
4972 editor->styleFlags |= GetWindowLongW( hwnd, GWL_STYLE ) & ES_WANTRETURN;
4973 editor->hWnd = hwnd; /* FIXME: Remove editor's dependence on hWnd */
4974 editor->hwndParent = create->hwndParent;
4976 SetWindowLongPtrW( hwnd, 0, (LONG_PTR)editor );
4978 return TRUE;
4981 static LRESULT RichEditWndProc_common(HWND hWnd, UINT msg, WPARAM wParam,
4982 LPARAM lParam, BOOL unicode)
4984 ME_TextEditor *editor;
4985 HRESULT hresult;
4986 LRESULT lresult = 0;
4988 TRACE("enter hwnd %p msg %04x (%s) %lx %lx, unicode %d\n",
4989 hWnd, msg, get_msg_name(msg), wParam, lParam, unicode);
4991 editor = (ME_TextEditor *)GetWindowLongPtrW(hWnd, 0);
4992 if (!editor)
4994 if (msg == WM_NCCREATE)
4996 CREATESTRUCTW *pcs = (CREATESTRUCTW *)lParam;
4998 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd, pcs->style);
4999 return create_windowed_editor( hWnd, pcs, FALSE );
5001 else
5003 return DefWindowProcW(hWnd, msg, wParam, lParam);
5007 switch (msg)
5009 case WM_PAINT:
5011 HDC hdc;
5012 RECT rc;
5013 PAINTSTRUCT ps;
5014 HBRUSH old_brush;
5016 update_caret(editor);
5017 hdc = BeginPaint(editor->hWnd, &ps);
5018 if (!editor->bEmulateVersion10 || (editor->nEventMask & ENM_UPDATE))
5019 ME_SendOldNotify(editor, EN_UPDATE);
5020 old_brush = SelectObject(hdc, editor->hbrBackground);
5022 /* Erase area outside of the formatting rectangle */
5023 if (ps.rcPaint.top < editor->rcFormat.top)
5025 rc = ps.rcPaint;
5026 rc.bottom = editor->rcFormat.top;
5027 PatBlt(hdc, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, PATCOPY);
5028 ps.rcPaint.top = editor->rcFormat.top;
5030 if (ps.rcPaint.bottom > editor->rcFormat.bottom) {
5031 rc = ps.rcPaint;
5032 rc.top = editor->rcFormat.bottom;
5033 PatBlt(hdc, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, PATCOPY);
5034 ps.rcPaint.bottom = editor->rcFormat.bottom;
5036 if (ps.rcPaint.left < editor->rcFormat.left) {
5037 rc = ps.rcPaint;
5038 rc.right = editor->rcFormat.left;
5039 PatBlt(hdc, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, PATCOPY);
5040 ps.rcPaint.left = editor->rcFormat.left;
5042 if (ps.rcPaint.right > editor->rcFormat.right) {
5043 rc = ps.rcPaint;
5044 rc.left = editor->rcFormat.right;
5045 PatBlt(hdc, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, PATCOPY);
5046 ps.rcPaint.right = editor->rcFormat.right;
5049 ME_PaintContent(editor, hdc, &ps.rcPaint);
5050 SelectObject(hdc, old_brush);
5051 EndPaint(editor->hWnd, &ps);
5052 return 0;
5054 case WM_ERASEBKGND:
5056 HDC hDC = (HDC)wParam;
5057 RECT rc;
5059 if (GetUpdateRect(editor->hWnd, &rc, TRUE))
5060 FillRect(hDC, &rc, editor->hbrBackground);
5061 return 1;
5063 case EM_SETOPTIONS:
5065 DWORD dwStyle;
5066 const DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
5067 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN |
5068 ECO_SELECTIONBAR;
5069 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
5070 dwStyle = GetWindowLongW(hWnd, GWL_STYLE);
5071 dwStyle = (dwStyle & ~mask) | (lresult & mask);
5072 SetWindowLongW(hWnd, GWL_STYLE, dwStyle);
5073 return lresult;
5075 case EM_SETREADONLY:
5077 DWORD dwStyle;
5078 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
5079 dwStyle = GetWindowLongW(hWnd, GWL_STYLE);
5080 dwStyle &= ~ES_READONLY;
5081 if (wParam)
5082 dwStyle |= ES_READONLY;
5083 SetWindowLongW(hWnd, GWL_STYLE, dwStyle);
5084 return lresult;
5086 default:
5087 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
5090 if (hresult == S_FALSE)
5091 lresult = DefWindowProcW(hWnd, msg, wParam, lParam);
5093 TRACE("exit hwnd %p msg %04x (%s) %lx %lx, unicode %d -> %lu\n",
5094 hWnd, msg, get_msg_name(msg), wParam, lParam, unicode, lresult);
5096 return lresult;
5099 static LRESULT WINAPI RichEditWndProcW(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5101 BOOL unicode = TRUE;
5103 /* Under Win9x RichEdit20W returns ANSI strings, see the tests. */
5104 if (msg == WM_GETTEXT && (GetVersion() & 0x80000000))
5105 unicode = FALSE;
5107 return RichEditWndProc_common(hWnd, msg, wParam, lParam, unicode);
5110 static LRESULT WINAPI RichEditWndProcA(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5112 return RichEditWndProc_common(hWnd, msg, wParam, lParam, FALSE);
5115 /******************************************************************
5116 * RichEditANSIWndProc (RICHED20.10)
5118 LRESULT WINAPI RichEditANSIWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5120 return RichEditWndProcA(hWnd, msg, wParam, lParam);
5123 /******************************************************************
5124 * RichEdit10ANSIWndProc (RICHED20.9)
5126 LRESULT WINAPI RichEdit10ANSIWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5128 if (msg == WM_NCCREATE && !GetWindowLongPtrW(hWnd, 0))
5130 CREATESTRUCTW *pcs = (CREATESTRUCTW *)lParam;
5132 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd, pcs->style);
5133 return create_windowed_editor( hWnd, pcs, TRUE );
5135 return RichEditANSIWndProc(hWnd, msg, wParam, lParam);
5138 void ME_SendOldNotify(ME_TextEditor *editor, int nCode)
5140 ITextHost_TxNotify(editor->texthost, nCode, NULL);
5143 /* Fill buffer with srcChars unicode characters from the start cursor.
5145 * buffer: destination buffer
5146 * buflen: length of buffer in characters excluding the NULL terminator.
5147 * start: start of editor text to copy into buffer.
5148 * srcChars: Number of characters to use from the editor text.
5149 * bCRLF: if true, replaces all end of lines with \r\n pairs.
5151 * returns the number of characters written excluding the NULL terminator.
5153 * The written text is always NULL terminated.
5155 int ME_GetTextW(ME_TextEditor *editor, WCHAR *buffer, int buflen,
5156 const ME_Cursor *start, int srcChars, BOOL bCRLF,
5157 BOOL bEOP)
5159 ME_DisplayItem *pRun, *pNextRun;
5160 const WCHAR *pStart = buffer;
5161 const WCHAR cr_lf[] = {'\r', '\n', 0};
5162 const WCHAR *str;
5163 int nLen;
5165 /* bCRLF flag is only honored in 2.0 and up. 1.0 must always return text verbatim */
5166 if (editor->bEmulateVersion10) bCRLF = FALSE;
5168 pRun = start->pRun;
5169 assert(pRun);
5170 pNextRun = ME_FindItemFwd(pRun, diRun);
5172 nLen = pRun->member.run.len - start->nOffset;
5173 str = get_text( &pRun->member.run, start->nOffset );
5175 while (srcChars && buflen && pNextRun)
5177 int nFlags = pRun->member.run.nFlags;
5179 if (bCRLF && nFlags & MERF_ENDPARA && ~nFlags & MERF_ENDCELL)
5181 if (buflen == 1) break;
5182 /* FIXME: native fails to reduce srcChars here for WM_GETTEXT or
5183 * EM_GETTEXTEX, however, this is done for copying text which
5184 * also uses this function. */
5185 srcChars -= min(nLen, srcChars);
5186 nLen = 2;
5187 str = cr_lf;
5188 } else {
5189 nLen = min(nLen, srcChars);
5190 srcChars -= nLen;
5193 nLen = min(nLen, buflen);
5194 buflen -= nLen;
5196 CopyMemory(buffer, str, sizeof(WCHAR) * nLen);
5198 buffer += nLen;
5200 pRun = pNextRun;
5201 pNextRun = ME_FindItemFwd(pRun, diRun);
5203 nLen = pRun->member.run.len;
5204 str = get_text( &pRun->member.run, 0 );
5206 /* append '\r' to the last paragraph. */
5207 if (pRun->next->type == diTextEnd && bEOP)
5209 *buffer = '\r';
5210 buffer ++;
5212 *buffer = 0;
5213 return buffer - pStart;
5216 static BOOL ME_RegisterEditorClass(HINSTANCE hInstance)
5218 WNDCLASSW wcW;
5219 WNDCLASSA wcA;
5221 wcW.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS;
5222 wcW.lpfnWndProc = RichEditWndProcW;
5223 wcW.cbClsExtra = 0;
5224 wcW.cbWndExtra = sizeof(ME_TextEditor *);
5225 wcW.hInstance = NULL; /* hInstance would register DLL-local class */
5226 wcW.hIcon = NULL;
5227 wcW.hCursor = LoadCursorW(NULL, (LPWSTR)IDC_IBEAM);
5228 wcW.hbrBackground = GetStockObject(NULL_BRUSH);
5229 wcW.lpszMenuName = NULL;
5231 if (is_version_nt())
5233 wcW.lpszClassName = RICHEDIT_CLASS20W;
5234 if (!RegisterClassW(&wcW)) return FALSE;
5235 wcW.lpszClassName = MSFTEDIT_CLASS;
5236 if (!RegisterClassW(&wcW)) return FALSE;
5238 else
5240 /* WNDCLASSA/W have the same layout */
5241 wcW.lpszClassName = (LPCWSTR)"RichEdit20W";
5242 if (!RegisterClassA((WNDCLASSA *)&wcW)) return FALSE;
5243 wcW.lpszClassName = (LPCWSTR)"RichEdit50W";
5244 if (!RegisterClassA((WNDCLASSA *)&wcW)) return FALSE;
5247 wcA.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS;
5248 wcA.lpfnWndProc = RichEditWndProcA;
5249 wcA.cbClsExtra = 0;
5250 wcA.cbWndExtra = sizeof(ME_TextEditor *);
5251 wcA.hInstance = NULL; /* hInstance would register DLL-local class */
5252 wcA.hIcon = NULL;
5253 wcA.hCursor = LoadCursorW(NULL, (LPWSTR)IDC_IBEAM);
5254 wcA.hbrBackground = GetStockObject(NULL_BRUSH);
5255 wcA.lpszMenuName = NULL;
5256 wcA.lpszClassName = RICHEDIT_CLASS20A;
5257 if (!RegisterClassA(&wcA)) return FALSE;
5258 wcA.lpszClassName = "RichEdit50A";
5259 if (!RegisterClassA(&wcA)) return FALSE;
5261 return TRUE;
5264 static LRESULT WINAPI REComboWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
5265 /* FIXME: Not implemented */
5266 TRACE("hWnd %p msg %04x (%s) %08lx %08lx\n",
5267 hWnd, msg, get_msg_name(msg), wParam, lParam);
5268 return DefWindowProcW(hWnd, msg, wParam, lParam);
5271 static LRESULT WINAPI REListWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
5272 /* FIXME: Not implemented */
5273 TRACE("hWnd %p msg %04x (%s) %08lx %08lx\n",
5274 hWnd, msg, get_msg_name(msg), wParam, lParam);
5275 return DefWindowProcW(hWnd, msg, wParam, lParam);
5278 /******************************************************************
5279 * REExtendedRegisterClass (RICHED20.8)
5281 * FIXME undocumented
5282 * Need to check for errors and implement controls and callbacks
5284 LRESULT WINAPI REExtendedRegisterClass(void)
5286 WNDCLASSW wcW;
5287 UINT result;
5289 FIXME("semi stub\n");
5291 wcW.cbClsExtra = 0;
5292 wcW.cbWndExtra = 4;
5293 wcW.hInstance = NULL;
5294 wcW.hIcon = NULL;
5295 wcW.hCursor = NULL;
5296 wcW.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
5297 wcW.lpszMenuName = NULL;
5299 if (!ME_ListBoxRegistered)
5301 wcW.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS;
5302 wcW.lpfnWndProc = REListWndProc;
5303 wcW.lpszClassName = REListBox20W;
5304 if (RegisterClassW(&wcW)) ME_ListBoxRegistered = TRUE;
5307 if (!ME_ComboBoxRegistered)
5309 wcW.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS | CS_VREDRAW | CS_HREDRAW;
5310 wcW.lpfnWndProc = REComboWndProc;
5311 wcW.lpszClassName = REComboBox20W;
5312 if (RegisterClassW(&wcW)) ME_ComboBoxRegistered = TRUE;
5315 result = 0;
5316 if (ME_ListBoxRegistered)
5317 result += 1;
5318 if (ME_ComboBoxRegistered)
5319 result += 2;
5321 return result;
5324 static int __cdecl wchar_comp( const void *key, const void *elem )
5326 return *(const WCHAR *)key - *(const WCHAR *)elem;
5329 /* neutral characters end the url if the next non-neutral character is a space character,
5330 otherwise they are included in the url. */
5331 static BOOL isurlneutral( WCHAR c )
5333 /* NB this list is sorted */
5334 static const WCHAR neutral_chars[] = {'!','\"','\'','(',')',',','-','.',':',';','<','>','?','[',']','{','}'};
5336 /* Some shortcuts */
5337 if (isalnum( c )) return FALSE;
5338 if (c > neutral_chars[ARRAY_SIZE( neutral_chars ) - 1]) return FALSE;
5340 return !!bsearch( &c, neutral_chars, ARRAY_SIZE( neutral_chars ), sizeof(c), wchar_comp );
5344 * This proc takes a selection, and scans it forward in order to select the span
5345 * of a possible URL candidate. A possible URL candidate must start with isalnum
5346 * or one of the following special characters: *|/\+%#@ and must consist entirely
5347 * of the characters allowed to start the URL, plus : (colon) which may occur
5348 * at most once, and not at either end.
5350 static BOOL ME_FindNextURLCandidate(ME_TextEditor *editor,
5351 const ME_Cursor *start,
5352 int nChars,
5353 ME_Cursor *candidate_min,
5354 ME_Cursor *candidate_max)
5356 ME_Cursor cursor = *start, neutral_end, space_end;
5357 BOOL candidateStarted = FALSE, quoted = FALSE;
5358 WCHAR c;
5360 while (nChars > 0)
5362 WCHAR *str = get_text( &cursor.pRun->member.run, 0 );
5363 int run_len = cursor.pRun->member.run.len;
5365 nChars -= run_len - cursor.nOffset;
5367 /* Find start of candidate */
5368 if (!candidateStarted)
5370 while (cursor.nOffset < run_len)
5372 c = str[cursor.nOffset];
5373 if (!iswspace( c ) && !isurlneutral( c ))
5375 *candidate_min = cursor;
5376 candidateStarted = TRUE;
5377 neutral_end.pPara = NULL;
5378 space_end.pPara = NULL;
5379 cursor.nOffset++;
5380 break;
5382 quoted = (c == '<');
5383 cursor.nOffset++;
5387 /* Find end of candidate */
5388 if (candidateStarted)
5390 while (cursor.nOffset < run_len)
5392 c = str[cursor.nOffset];
5393 if (iswspace( c ))
5395 if (quoted && c != '\r')
5397 if (!space_end.pPara)
5399 if (neutral_end.pPara)
5400 space_end = neutral_end;
5401 else
5402 space_end = cursor;
5405 else
5406 goto done;
5408 else if (isurlneutral( c ))
5410 if (quoted && c == '>')
5412 neutral_end.pPara = NULL;
5413 space_end.pPara = NULL;
5414 goto done;
5416 if (!neutral_end.pPara)
5417 neutral_end = cursor;
5419 else
5420 neutral_end.pPara = NULL;
5422 cursor.nOffset++;
5426 cursor.nOffset = 0;
5427 if (!ME_NextRun(&cursor.pPara, &cursor.pRun, TRUE))
5428 goto done;
5431 done:
5432 if (candidateStarted)
5434 if (space_end.pPara)
5435 *candidate_max = space_end;
5436 else if (neutral_end.pPara)
5437 *candidate_max = neutral_end;
5438 else
5439 *candidate_max = cursor;
5440 return TRUE;
5442 *candidate_max = *candidate_min = cursor;
5443 return FALSE;
5447 * This proc evaluates the selection and returns TRUE if it can be considered an URL
5449 static BOOL ME_IsCandidateAnURL(ME_TextEditor *editor, const ME_Cursor *start, int nChars)
5451 #define MAX_PREFIX_LEN 9
5452 struct prefix_s {
5453 const WCHAR text[MAX_PREFIX_LEN];
5454 int length;
5455 }prefixes[] = {
5456 {{'p','r','o','s','p','e','r','o',':'}, 9},
5457 {{'t','e','l','n','e','t',':'}, 7},
5458 {{'g','o','p','h','e','r',':'}, 7},
5459 {{'m','a','i','l','t','o',':'}, 7},
5460 {{'h','t','t','p','s',':'}, 6},
5461 {{'f','i','l','e',':'}, 5},
5462 {{'n','e','w','s',':'}, 5},
5463 {{'w','a','i','s',':'}, 5},
5464 {{'n','n','t','p',':'}, 5},
5465 {{'h','t','t','p',':'}, 5},
5466 {{'w','w','w','.'}, 4},
5467 {{'f','t','p',':'}, 4},
5469 WCHAR bufferW[MAX_PREFIX_LEN + 1];
5470 unsigned int i;
5472 ME_GetTextW(editor, bufferW, MAX_PREFIX_LEN, start, nChars, FALSE, FALSE);
5473 for (i = 0; i < ARRAY_SIZE(prefixes); i++)
5475 if (nChars < prefixes[i].length) continue;
5476 if (!memcmp(prefixes[i].text, bufferW, prefixes[i].length * sizeof(WCHAR)))
5477 return TRUE;
5479 return FALSE;
5480 #undef MAX_PREFIX_LEN
5484 * This proc walks through the indicated selection and evaluates whether each
5485 * section identified by ME_FindNextURLCandidate and in-between sections have
5486 * their proper CFE_LINK attributes set or unset. If the CFE_LINK attribute is
5487 * not what it is supposed to be, this proc sets or unsets it as appropriate.
5489 * Since this function can cause runs to be split, do not depend on the value
5490 * of the start cursor at the end of the function.
5492 * nChars may be set to INT_MAX to update to the end of the text.
5494 * Returns TRUE if at least one section was modified.
5496 static BOOL ME_UpdateLinkAttribute(ME_TextEditor *editor, ME_Cursor *start, int nChars)
5498 BOOL modified = FALSE;
5499 ME_Cursor startCur = *start;
5501 if (!editor->AutoURLDetect_bEnable) return FALSE;
5505 CHARFORMAT2W link;
5506 ME_Cursor candidateStart, candidateEnd;
5508 if (ME_FindNextURLCandidate(editor, &startCur, nChars,
5509 &candidateStart, &candidateEnd))
5511 /* Section before candidate is not an URL */
5512 int cMin = ME_GetCursorOfs(&candidateStart);
5513 int cMax = ME_GetCursorOfs(&candidateEnd);
5515 if (!ME_IsCandidateAnURL(editor, &candidateStart, cMax - cMin))
5516 candidateStart = candidateEnd;
5517 nChars -= cMax - ME_GetCursorOfs(&startCur);
5519 else
5521 /* No more candidates until end of selection */
5522 nChars = 0;
5525 if (startCur.pRun != candidateStart.pRun ||
5526 startCur.nOffset != candidateStart.nOffset)
5528 /* CFE_LINK effect should be consistently unset */
5529 link.cbSize = sizeof(link);
5530 ME_GetCharFormat(editor, &startCur, &candidateStart, &link);
5531 if (!(link.dwMask & CFM_LINK) || (link.dwEffects & CFE_LINK))
5533 /* CFE_LINK must be unset from this range */
5534 memset(&link, 0, sizeof(CHARFORMAT2W));
5535 link.cbSize = sizeof(link);
5536 link.dwMask = CFM_LINK;
5537 link.dwEffects = 0;
5538 ME_SetCharFormat(editor, &startCur, &candidateStart, &link);
5539 /* Update candidateEnd since setting character formats may split
5540 * runs, which can cause a cursor to be at an invalid offset within
5541 * a split run. */
5542 while (candidateEnd.nOffset >= candidateEnd.pRun->member.run.len)
5544 candidateEnd.nOffset -= candidateEnd.pRun->member.run.len;
5545 candidateEnd.pRun = ME_FindItemFwd(candidateEnd.pRun, diRun);
5547 modified = TRUE;
5550 if (candidateStart.pRun != candidateEnd.pRun ||
5551 candidateStart.nOffset != candidateEnd.nOffset)
5553 /* CFE_LINK effect should be consistently set */
5554 link.cbSize = sizeof(link);
5555 ME_GetCharFormat(editor, &candidateStart, &candidateEnd, &link);
5556 if (!(link.dwMask & CFM_LINK) || !(link.dwEffects & CFE_LINK))
5558 /* CFE_LINK must be set on this range */
5559 memset(&link, 0, sizeof(CHARFORMAT2W));
5560 link.cbSize = sizeof(link);
5561 link.dwMask = CFM_LINK;
5562 link.dwEffects = CFE_LINK;
5563 ME_SetCharFormat(editor, &candidateStart, &candidateEnd, &link);
5564 modified = TRUE;
5567 startCur = candidateEnd;
5568 } while (nChars > 0);
5569 return modified;