mshtml: Rename fire_event_obj and dispatch_event.
[wine.git] / dlls / riched20 / editor.c
bloba7b1bc748fde41d9d5006c3c4f9450c9313fd1b2
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 (currently single line controls aren't supported)
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) {
265 ME_TextBuffer *buf = ALLOC_OBJ(ME_TextBuffer);
267 ME_DisplayItem *p1 = ME_MakeDI(diTextStart);
268 ME_DisplayItem *p2 = ME_MakeDI(diTextEnd);
270 p1->prev = NULL;
271 p1->next = p2;
272 p2->prev = p1;
273 p2->next = NULL;
274 p1->member.para.next_para = p2;
275 p2->member.para.prev_para = p1;
276 p2->member.para.nCharOfs = 0;
278 buf->pFirst = p1;
279 buf->pLast = p2;
280 buf->pCharStyle = NULL;
282 return buf;
286 static LRESULT ME_StreamInText(ME_TextEditor *editor, DWORD dwFormat, ME_InStream *stream, ME_Style *style)
288 WCHAR *pText;
289 LRESULT total_bytes_read = 0;
290 BOOL is_read = FALSE;
291 DWORD cp = CP_ACP, copy = 0;
292 char conv_buf[4 + STREAMIN_BUFFER_SIZE]; /* up to 4 additional UTF-8 bytes */
294 static const char bom_utf8[] = {0xEF, 0xBB, 0xBF};
296 TRACE("%08x %p\n", dwFormat, stream);
298 do {
299 LONG nWideChars = 0;
300 WCHAR wszText[STREAMIN_BUFFER_SIZE+1];
302 if (!stream->dwSize)
304 ME_StreamInFill(stream);
305 if (stream->editstream->dwError)
306 break;
307 if (!stream->dwSize)
308 break;
309 total_bytes_read += stream->dwSize;
312 if (!(dwFormat & SF_UNICODE))
314 char * buf = stream->buffer;
315 DWORD size = stream->dwSize, end;
317 if (!is_read)
319 is_read = TRUE;
320 if (stream->dwSize >= 3 && !memcmp(stream->buffer, bom_utf8, 3))
322 cp = CP_UTF8;
323 buf += 3;
324 size -= 3;
328 if (cp == CP_UTF8)
330 if (copy)
332 memcpy(conv_buf + copy, buf, size);
333 buf = conv_buf;
334 size += copy;
336 end = size;
337 while ((buf[end-1] & 0xC0) == 0x80)
339 --end;
340 --total_bytes_read; /* strange, but seems to match windows */
342 if (buf[end-1] & 0x80)
344 DWORD need = 0;
345 if ((buf[end-1] & 0xE0) == 0xC0)
346 need = 1;
347 if ((buf[end-1] & 0xF0) == 0xE0)
348 need = 2;
349 if ((buf[end-1] & 0xF8) == 0xF0)
350 need = 3;
352 if (size - end >= need)
354 /* we have enough bytes for this sequence */
355 end = size;
357 else
359 /* need more bytes, so don't transcode this sequence */
360 --end;
364 else
365 end = size;
367 nWideChars = MultiByteToWideChar(cp, 0, buf, end, wszText, STREAMIN_BUFFER_SIZE);
368 pText = wszText;
370 if (cp == CP_UTF8)
372 if (end != size)
374 memcpy(conv_buf, buf + end, size - end);
375 copy = size - end;
379 else
381 nWideChars = stream->dwSize >> 1;
382 pText = (WCHAR *)stream->buffer;
385 ME_InsertTextFromCursor(editor, 0, pText, nWideChars, style);
386 if (stream->dwSize == 0)
387 break;
388 stream->dwSize = 0;
389 } while(1);
390 return total_bytes_read;
393 static void ME_ApplyBorderProperties(RTF_Info *info,
394 ME_BorderRect *borderRect,
395 RTFBorder *borderDef)
397 int i, colorNum;
398 ME_Border *pBorders[] = {&borderRect->top,
399 &borderRect->left,
400 &borderRect->bottom,
401 &borderRect->right};
402 for (i = 0; i < 4; i++)
404 RTFColor *colorDef = info->colorList;
405 pBorders[i]->width = borderDef[i].width;
406 colorNum = borderDef[i].color;
407 while (colorDef && colorDef->rtfCNum != colorNum)
408 colorDef = colorDef->rtfNextColor;
409 if (colorDef)
410 pBorders[i]->colorRef = RGB(
411 colorDef->rtfCRed >= 0 ? colorDef->rtfCRed : 0,
412 colorDef->rtfCGreen >= 0 ? colorDef->rtfCGreen : 0,
413 colorDef->rtfCBlue >= 0 ? colorDef->rtfCBlue : 0);
414 else
415 pBorders[i]->colorRef = RGB(0, 0, 0);
419 void ME_RTFCharAttrHook(RTF_Info *info)
421 CHARFORMAT2W fmt;
422 fmt.cbSize = sizeof(fmt);
423 fmt.dwMask = 0;
424 fmt.dwEffects = 0;
426 switch(info->rtfMinor)
428 case rtfPlain:
429 /* FIXME add more flags once they're implemented */
430 fmt.dwMask = CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE | CFM_UNDERLINETYPE | CFM_STRIKEOUT |
431 CFM_COLOR | CFM_BACKCOLOR | CFM_SIZE | CFM_WEIGHT;
432 fmt.dwEffects = CFE_AUTOCOLOR | CFE_AUTOBACKCOLOR;
433 fmt.yHeight = 12*20; /* 12pt */
434 fmt.wWeight = FW_NORMAL;
435 fmt.bUnderlineType = CFU_UNDERLINE;
436 break;
437 case rtfBold:
438 fmt.dwMask = CFM_BOLD | CFM_WEIGHT;
439 fmt.dwEffects = info->rtfParam ? CFE_BOLD : 0;
440 fmt.wWeight = info->rtfParam ? FW_BOLD : FW_NORMAL;
441 break;
442 case rtfItalic:
443 fmt.dwMask = CFM_ITALIC;
444 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
445 break;
446 case rtfUnderline:
447 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
448 fmt.bUnderlineType = CFU_UNDERLINE;
449 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
450 break;
451 case rtfDotUnderline:
452 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
453 fmt.bUnderlineType = CFU_UNDERLINEDOTTED;
454 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
455 break;
456 case rtfDbUnderline:
457 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
458 fmt.bUnderlineType = CFU_UNDERLINEDOUBLE;
459 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
460 break;
461 case rtfWordUnderline:
462 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
463 fmt.bUnderlineType = CFU_UNDERLINEWORD;
464 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
465 break;
466 case rtfNoUnderline:
467 fmt.dwMask = CFM_UNDERLINE;
468 fmt.dwEffects = 0;
469 break;
470 case rtfStrikeThru:
471 fmt.dwMask = CFM_STRIKEOUT;
472 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
473 break;
474 case rtfSubScript:
475 case rtfSuperScript:
476 case rtfSubScrShrink:
477 case rtfSuperScrShrink:
478 case rtfNoSuperSub:
479 fmt.dwMask = CFM_SUBSCRIPT|CFM_SUPERSCRIPT;
480 if (info->rtfMinor == rtfSubScrShrink) fmt.dwEffects = CFE_SUBSCRIPT;
481 if (info->rtfMinor == rtfSuperScrShrink) fmt.dwEffects = CFE_SUPERSCRIPT;
482 if (info->rtfMinor == rtfNoSuperSub) fmt.dwEffects = 0;
483 break;
484 case rtfInvisible:
485 fmt.dwMask = CFM_HIDDEN;
486 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
487 break;
488 case rtfBackColor:
489 fmt.dwMask = CFM_BACKCOLOR;
490 fmt.dwEffects = 0;
491 if (info->rtfParam == 0)
492 fmt.dwEffects = CFE_AUTOBACKCOLOR;
493 else if (info->rtfParam != rtfNoParam)
495 RTFColor *c = RTFGetColor(info, info->rtfParam);
496 if (c && c->rtfCBlue >= 0)
497 fmt.crBackColor = (c->rtfCBlue<<16)|(c->rtfCGreen<<8)|(c->rtfCRed);
498 else
499 fmt.dwEffects = CFE_AUTOBACKCOLOR;
501 break;
502 case rtfForeColor:
503 fmt.dwMask = CFM_COLOR;
504 fmt.dwEffects = 0;
505 if (info->rtfParam == 0)
506 fmt.dwEffects = CFE_AUTOCOLOR;
507 else if (info->rtfParam != rtfNoParam)
509 RTFColor *c = RTFGetColor(info, info->rtfParam);
510 if (c && c->rtfCBlue >= 0)
511 fmt.crTextColor = (c->rtfCBlue<<16)|(c->rtfCGreen<<8)|(c->rtfCRed);
512 else {
513 fmt.dwEffects = CFE_AUTOCOLOR;
516 break;
517 case rtfFontNum:
518 if (info->rtfParam != rtfNoParam)
520 RTFFont *f = RTFGetFont(info, info->rtfParam);
521 if (f)
523 MultiByteToWideChar(CP_ACP, 0, f->rtfFName, -1, fmt.szFaceName, sizeof(fmt.szFaceName)/sizeof(WCHAR));
524 fmt.szFaceName[sizeof(fmt.szFaceName)/sizeof(WCHAR)-1] = '\0';
525 fmt.bCharSet = f->rtfFCharSet;
526 fmt.dwMask = CFM_FACE | CFM_CHARSET;
527 fmt.bPitchAndFamily = f->rtfFPitch | (f->rtfFFamily << 4);
530 break;
531 case rtfFontSize:
532 fmt.dwMask = CFM_SIZE;
533 if (info->rtfParam != rtfNoParam)
534 fmt.yHeight = info->rtfParam*10;
535 break;
537 if (fmt.dwMask) {
538 ME_Style *style2;
539 RTFFlushOutputBuffer(info);
540 /* FIXME too slow ? how come ? */
541 style2 = ME_ApplyStyle(info->editor, info->style, &fmt);
542 ME_ReleaseStyle(info->style);
543 info->style = style2;
544 info->styleChanged = TRUE;
548 /* FIXME this function doesn't get any information about context of the RTF tag, which is very bad,
549 the same tags mean different things in different contexts */
550 void ME_RTFParAttrHook(RTF_Info *info)
552 switch(info->rtfMinor)
554 case rtfParDef: /* restores default paragraph attributes */
555 if (!info->editor->bEmulateVersion10) /* v4.1 */
556 info->borderType = RTFBorderParaLeft;
557 else /* v1.0 - 3.0 */
558 info->borderType = RTFBorderParaTop;
559 info->fmt.dwMask = PFM_ALIGNMENT | PFM_BORDER | PFM_LINESPACING | PFM_TABSTOPS |
560 PFM_OFFSET | PFM_RIGHTINDENT | PFM_SPACEAFTER | PFM_SPACEBEFORE |
561 PFM_STARTINDENT | PFM_RTLPARA | PFM_NUMBERING | PFM_NUMBERINGSTART |
562 PFM_NUMBERINGSTYLE | PFM_NUMBERINGTAB;
563 /* TODO: shading */
564 info->fmt.wAlignment = PFA_LEFT;
565 info->fmt.cTabCount = 0;
566 info->fmt.dxOffset = info->fmt.dxStartIndent = info->fmt.dxRightIndent = 0;
567 info->fmt.wBorderWidth = info->fmt.wBorders = 0;
568 info->fmt.wBorderSpace = 0;
569 info->fmt.bLineSpacingRule = 0;
570 info->fmt.dySpaceBefore = info->fmt.dySpaceAfter = 0;
571 info->fmt.dyLineSpacing = 0;
572 info->fmt.wEffects &= ~PFE_RTLPARA;
573 info->fmt.wNumbering = 0;
574 info->fmt.wNumberingStart = 0;
575 info->fmt.wNumberingStyle = 0;
576 info->fmt.wNumberingTab = 0;
578 if (!info->editor->bEmulateVersion10) /* v4.1 */
580 if (info->tableDef && info->tableDef->tableRowStart &&
581 info->tableDef->tableRowStart->member.para.nFlags & MEPF_ROWEND)
583 ME_Cursor cursor;
584 ME_DisplayItem *para;
585 /* We are just after a table row. */
586 RTFFlushOutputBuffer(info);
587 cursor = info->editor->pCursors[0];
588 para = cursor.pPara;
589 if (para == info->tableDef->tableRowStart->member.para.next_para
590 && !cursor.nOffset && !cursor.pRun->member.run.nCharOfs)
592 /* Since the table row end, no text has been inserted, and the \intbl
593 * control word has not be used. We can confirm that we are not in a
594 * table anymore.
596 info->tableDef->tableRowStart = NULL;
597 info->canInheritInTbl = FALSE;
600 } else { /* v1.0 - v3.0 */
601 info->fmt.dwMask |= PFM_TABLE;
602 info->fmt.wEffects &= ~PFE_TABLE;
604 break;
605 case rtfNestLevel:
606 if (!info->editor->bEmulateVersion10) /* v4.1 */
608 while (info->rtfParam > info->nestingLevel) {
609 RTFTable *tableDef = ALLOC_OBJ(RTFTable);
610 ZeroMemory(tableDef, sizeof(RTFTable));
611 tableDef->parent = info->tableDef;
612 info->tableDef = tableDef;
614 RTFFlushOutputBuffer(info);
615 if (tableDef->tableRowStart &&
616 tableDef->tableRowStart->member.para.nFlags & MEPF_ROWEND)
618 ME_DisplayItem *para = tableDef->tableRowStart;
619 para = para->member.para.next_para;
620 para = ME_InsertTableRowStartAtParagraph(info->editor, para);
621 tableDef->tableRowStart = para;
622 } else {
623 ME_Cursor cursor;
624 WCHAR endl = '\r';
625 cursor = info->editor->pCursors[0];
626 if (cursor.nOffset || cursor.pRun->member.run.nCharOfs)
627 ME_InsertTextFromCursor(info->editor, 0, &endl, 1, info->style);
628 tableDef->tableRowStart = ME_InsertTableRowStartFromCursor(info->editor);
631 info->nestingLevel++;
633 info->canInheritInTbl = FALSE;
635 break;
636 case rtfInTable:
638 if (!info->editor->bEmulateVersion10) /* v4.1 */
640 if (info->nestingLevel < 1)
642 RTFTable *tableDef;
643 if (!info->tableDef)
645 info->tableDef = ALLOC_OBJ(RTFTable);
646 ZeroMemory(info->tableDef, sizeof(RTFTable));
648 tableDef = info->tableDef;
649 RTFFlushOutputBuffer(info);
650 if (tableDef->tableRowStart &&
651 tableDef->tableRowStart->member.para.nFlags & MEPF_ROWEND)
653 ME_DisplayItem *para = tableDef->tableRowStart;
654 para = para->member.para.next_para;
655 para = ME_InsertTableRowStartAtParagraph(info->editor, para);
656 tableDef->tableRowStart = para;
657 } else {
658 ME_Cursor cursor;
659 WCHAR endl = '\r';
660 cursor = info->editor->pCursors[0];
661 if (cursor.nOffset || cursor.pRun->member.run.nCharOfs)
662 ME_InsertTextFromCursor(info->editor, 0, &endl, 1, info->style);
663 tableDef->tableRowStart = ME_InsertTableRowStartFromCursor(info->editor);
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 STGMEDIUM stgm;
1134 FORMATETC fm;
1135 CLSID clsid;
1136 HRESULT hr = E_FAIL;
1137 DWORD conn;
1139 if (hemf)
1141 stgm.tymed = TYMED_ENHMF;
1142 stgm.u.hEnhMetaFile = hemf;
1143 fm.cfFormat = CF_ENHMETAFILE;
1145 else if (hbmp)
1147 stgm.tymed = TYMED_GDI;
1148 stgm.u.hBitmap = hbmp;
1149 fm.cfFormat = CF_BITMAP;
1151 stgm.pUnkForRelease = NULL;
1153 fm.ptd = NULL;
1154 fm.dwAspect = DVASPECT_CONTENT;
1155 fm.lindex = -1;
1156 fm.tymed = stgm.tymed;
1158 if (!editor->reOle)
1160 if (!CreateIRichEditOle(NULL, editor, (LPVOID *)&editor->reOle))
1161 return hr;
1164 if (OleCreateDefaultHandler(&CLSID_NULL, NULL, &IID_IOleObject, (void**)&lpObject) == S_OK &&
1165 IRichEditOle_GetClientSite(editor->reOle, &lpClientSite) == S_OK &&
1166 IOleObject_SetClientSite(lpObject, lpClientSite) == S_OK &&
1167 IOleObject_GetUserClassID(lpObject, &clsid) == S_OK &&
1168 IOleObject_QueryInterface(lpObject, &IID_IOleCache, (void**)&lpOleCache) == S_OK &&
1169 IOleCache_Cache(lpOleCache, &fm, 0, &conn) == S_OK &&
1170 IOleObject_QueryInterface(lpObject, &IID_IDataObject, (void**)&lpDataObject) == S_OK &&
1171 IDataObject_SetData(lpDataObject, &fm, &stgm, TRUE) == S_OK)
1173 REOBJECT reobject;
1175 reobject.cbStruct = sizeof(reobject);
1176 reobject.cp = REO_CP_SELECTION;
1177 reobject.clsid = clsid;
1178 reobject.poleobj = lpObject;
1179 reobject.pstg = lpStorage;
1180 reobject.polesite = lpClientSite;
1181 /* convert from twips to .01 mm */
1182 reobject.sizel.cx = MulDiv(sz->cx, 254, 144);
1183 reobject.sizel.cy = MulDiv(sz->cy, 254, 144);
1184 reobject.dvaspect = DVASPECT_CONTENT;
1185 reobject.dwFlags = 0; /* FIXME */
1186 reobject.dwUser = 0;
1188 ME_InsertOLEFromCursor(editor, &reobject, 0);
1189 hr = S_OK;
1192 if (lpObject) IOleObject_Release(lpObject);
1193 if (lpClientSite) IOleClientSite_Release(lpClientSite);
1194 if (lpStorage) IStorage_Release(lpStorage);
1195 if (lpDataObject) IDataObject_Release(lpDataObject);
1196 if (lpOleCache) IOleCache_Release(lpOleCache);
1198 return hr;
1201 static void ME_RTFReadShpPictGroup( RTF_Info *info )
1203 int level = 1;
1205 for (;;)
1207 RTFGetToken (info);
1209 if (info->rtfClass == rtfEOF) return;
1210 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1212 if (--level == 0) break;
1214 else if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1216 level++;
1218 else
1220 RTFRouteToken( info );
1221 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1222 level--;
1226 RTFRouteToken( info ); /* feed "}" back to router */
1227 return;
1230 static DWORD read_hex_data( RTF_Info *info, BYTE **out )
1232 DWORD read = 0, size = 1024;
1233 BYTE *buf, val;
1234 BOOL flip;
1236 *out = NULL;
1238 if (info->rtfClass != rtfText)
1240 ERR("Called with incorrect token\n");
1241 return 0;
1244 buf = HeapAlloc( GetProcessHeap(), 0, size );
1245 if (!buf) return 0;
1247 val = info->rtfMajor;
1248 for (flip = TRUE;; flip = !flip)
1250 RTFGetToken( info );
1251 if (info->rtfClass == rtfEOF)
1253 HeapFree( GetProcessHeap(), 0, buf );
1254 return 0;
1256 if (info->rtfClass != rtfText) break;
1257 if (flip)
1259 if (read >= size)
1261 size *= 2;
1262 buf = HeapReAlloc( GetProcessHeap(), 0, buf, size );
1263 if (!buf) return 0;
1265 buf[read++] = RTFCharToHex(val) * 16 + RTFCharToHex(info->rtfMajor);
1267 else
1268 val = info->rtfMajor;
1270 if (flip) FIXME("wrong hex string\n");
1272 *out = buf;
1273 return read;
1276 static void ME_RTFReadPictGroup(RTF_Info *info)
1278 SIZEL sz;
1279 BYTE *buffer = NULL;
1280 DWORD size = 0;
1281 METAFILEPICT mfp;
1282 HENHMETAFILE hemf;
1283 HBITMAP hbmp;
1284 enum gfxkind {gfx_unknown = 0, gfx_enhmetafile, gfx_metafile, gfx_dib} gfx = gfx_unknown;
1285 int level = 1;
1287 mfp.mm = MM_TEXT;
1288 sz.cx = sz.cy = 0;
1290 for (;;)
1292 RTFGetToken( info );
1294 if (info->rtfClass == rtfText)
1296 if (level == 1)
1298 if (!buffer)
1299 size = read_hex_data( info, &buffer );
1301 else
1303 RTFSkipGroup( info );
1305 } /* We potentially have a new token so fall through. */
1307 if (info->rtfClass == rtfEOF) return;
1309 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1311 if (--level == 0) break;
1312 continue;
1314 if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1316 level++;
1317 continue;
1319 if (!RTFCheckCM( info, rtfControl, rtfPictAttr ))
1321 RTFRouteToken( info );
1322 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1323 level--;
1324 continue;
1327 if (RTFCheckMM( info, rtfPictAttr, rtfWinMetafile ))
1329 mfp.mm = info->rtfParam;
1330 gfx = gfx_metafile;
1332 else if (RTFCheckMM( info, rtfPictAttr, rtfDevIndBitmap ))
1334 if (info->rtfParam != 0) FIXME("dibitmap should be 0 (%d)\n", info->rtfParam);
1335 gfx = gfx_dib;
1337 else if (RTFCheckMM( info, rtfPictAttr, rtfEmfBlip ))
1338 gfx = gfx_enhmetafile;
1339 else if (RTFCheckMM( info, rtfPictAttr, rtfPicWid ))
1340 mfp.xExt = info->rtfParam;
1341 else if (RTFCheckMM( info, rtfPictAttr, rtfPicHt ))
1342 mfp.yExt = info->rtfParam;
1343 else if (RTFCheckMM( info, rtfPictAttr, rtfPicGoalWid ))
1344 sz.cx = info->rtfParam;
1345 else if (RTFCheckMM( info, rtfPictAttr, rtfPicGoalHt ))
1346 sz.cy = info->rtfParam;
1347 else
1348 FIXME("Non supported attribute: %d %d %d\n", info->rtfClass, info->rtfMajor, info->rtfMinor);
1351 if (buffer)
1353 switch (gfx)
1355 case gfx_enhmetafile:
1356 if ((hemf = SetEnhMetaFileBits( size, buffer )))
1357 insert_static_object( info->editor, hemf, NULL, &sz );
1358 break;
1359 case gfx_metafile:
1360 if ((hemf = SetWinMetaFileBits( size, buffer, NULL, &mfp )))
1361 insert_static_object( info->editor, hemf, NULL, &sz );
1362 break;
1363 case gfx_dib:
1365 BITMAPINFO *bi = (BITMAPINFO*)buffer;
1366 HDC hdc = GetDC(0);
1367 unsigned nc = bi->bmiHeader.biClrUsed;
1369 /* not quite right, especially for bitfields type of compression */
1370 if (!nc && bi->bmiHeader.biBitCount <= 8)
1371 nc = 1 << bi->bmiHeader.biBitCount;
1372 if ((hbmp = CreateDIBitmap( hdc, &bi->bmiHeader,
1373 CBM_INIT, (char*)(bi + 1) + nc * sizeof(RGBQUAD),
1374 bi, DIB_RGB_COLORS)) )
1375 insert_static_object( info->editor, NULL, hbmp, &sz );
1376 ReleaseDC( 0, hdc );
1377 break;
1379 default:
1380 break;
1383 HeapFree( GetProcessHeap(), 0, buffer );
1384 RTFRouteToken( info ); /* feed "}" back to router */
1385 return;
1388 /* for now, lookup the \result part and use it, whatever the object */
1389 static void ME_RTFReadObjectGroup(RTF_Info *info)
1391 for (;;)
1393 RTFGetToken (info);
1394 if (info->rtfClass == rtfEOF)
1395 return;
1396 if (RTFCheckCM(info, rtfGroup, rtfEndGroup))
1397 break;
1398 if (RTFCheckCM(info, rtfGroup, rtfBeginGroup))
1400 RTFGetToken (info);
1401 if (info->rtfClass == rtfEOF)
1402 return;
1403 if (RTFCheckCMM(info, rtfControl, rtfDestination, rtfObjResult))
1405 int level = 1;
1407 while (RTFGetToken (info) != rtfEOF)
1409 if (info->rtfClass == rtfGroup)
1411 if (info->rtfMajor == rtfBeginGroup) level++;
1412 else if (info->rtfMajor == rtfEndGroup && --level < 0) break;
1414 RTFRouteToken(info);
1417 else RTFSkipGroup(info);
1418 continue;
1420 if (!RTFCheckCM (info, rtfControl, rtfObjAttr))
1422 FIXME("Non supported attribute: %d %d %d\n", info->rtfClass, info->rtfMajor, info->rtfMinor);
1423 return;
1426 RTFRouteToken(info); /* feed "}" back to router */
1429 static void ME_RTFReadParnumGroup( RTF_Info *info )
1431 int level = 1, type = -1;
1432 WORD indent = 0, start = 1;
1433 WCHAR txt_before = 0, txt_after = 0;
1435 for (;;)
1437 RTFGetToken( info );
1439 if (RTFCheckCMM( info, rtfControl, rtfDestination, rtfParNumTextBefore ) ||
1440 RTFCheckCMM( info, rtfControl, rtfDestination, rtfParNumTextAfter ))
1442 int loc = info->rtfMinor;
1444 RTFGetToken( info );
1445 if (info->rtfClass == rtfText)
1447 if (loc == rtfParNumTextBefore)
1448 txt_before = info->rtfMajor;
1449 else
1450 txt_after = info->rtfMajor;
1451 continue;
1453 /* falling through to catch EOFs and group level changes */
1456 if (info->rtfClass == rtfEOF)
1457 return;
1459 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1461 if (--level == 0) break;
1462 continue;
1465 if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1467 level++;
1468 continue;
1471 /* Ignore non para-attr */
1472 if (!RTFCheckCM( info, rtfControl, rtfParAttr ))
1473 continue;
1475 switch (info->rtfMinor)
1477 case rtfParLevel: /* Para level is ignored */
1478 case rtfParSimple:
1479 break;
1480 case rtfParBullet:
1481 type = PFN_BULLET;
1482 break;
1484 case rtfParNumDecimal:
1485 type = PFN_ARABIC;
1486 break;
1487 case rtfParNumULetter:
1488 type = PFN_UCLETTER;
1489 break;
1490 case rtfParNumURoman:
1491 type = PFN_UCROMAN;
1492 break;
1493 case rtfParNumLLetter:
1494 type = PFN_LCLETTER;
1495 break;
1496 case rtfParNumLRoman:
1497 type = PFN_LCROMAN;
1498 break;
1500 case rtfParNumIndent:
1501 indent = info->rtfParam;
1502 break;
1503 case rtfParNumStartAt:
1504 start = info->rtfParam;
1505 break;
1509 if (type != -1)
1511 info->fmt.dwMask |= (PFM_NUMBERING | PFM_NUMBERINGSTART | PFM_NUMBERINGSTYLE | PFM_NUMBERINGTAB);
1512 info->fmt.wNumbering = type;
1513 info->fmt.wNumberingStart = start;
1514 info->fmt.wNumberingStyle = PFNS_PAREN;
1515 if (type != PFN_BULLET)
1517 if (txt_before == 0 && txt_after == 0)
1518 info->fmt.wNumberingStyle = PFNS_PLAIN;
1519 else if (txt_after == '.')
1520 info->fmt.wNumberingStyle = PFNS_PERIOD;
1521 else if (txt_before == '(' && txt_after == ')')
1522 info->fmt.wNumberingStyle = PFNS_PARENS;
1524 info->fmt.wNumberingTab = indent;
1527 TRACE("type %d indent %d start %d txt before %04x txt after %04x\n",
1528 type, indent, start, txt_before, txt_after);
1530 RTFRouteToken( info ); /* feed "}" back to router */
1533 static void ME_RTFReadHook(RTF_Info *info)
1535 switch(info->rtfClass)
1537 case rtfGroup:
1538 switch(info->rtfMajor)
1540 case rtfBeginGroup:
1541 if (info->stackTop < maxStack) {
1542 info->stack[info->stackTop].style = info->style;
1543 ME_AddRefStyle(info->style);
1544 info->stack[info->stackTop].codePage = info->codePage;
1545 info->stack[info->stackTop].unicodeLength = info->unicodeLength;
1547 info->stackTop++;
1548 info->styleChanged = FALSE;
1549 break;
1550 case rtfEndGroup:
1552 RTFFlushOutputBuffer(info);
1553 info->stackTop--;
1554 if (info->stackTop <= 0)
1555 info->rtfClass = rtfEOF;
1556 if (info->stackTop < 0)
1557 return;
1559 ME_ReleaseStyle(info->style);
1560 info->style = info->stack[info->stackTop].style;
1561 info->codePage = info->stack[info->stackTop].codePage;
1562 info->unicodeLength = info->stack[info->stackTop].unicodeLength;
1563 break;
1566 break;
1570 void
1571 ME_StreamInFill(ME_InStream *stream)
1573 stream->editstream->dwError = stream->editstream->pfnCallback(stream->editstream->dwCookie,
1574 (BYTE *)stream->buffer,
1575 sizeof(stream->buffer),
1576 (LONG *)&stream->dwSize);
1577 stream->dwUsed = 0;
1580 static LRESULT ME_StreamIn(ME_TextEditor *editor, DWORD format, EDITSTREAM *stream, BOOL stripLastCR)
1582 RTF_Info parser;
1583 ME_Style *style;
1584 int from, to, nUndoMode;
1585 int nEventMask = editor->nEventMask;
1586 ME_InStream inStream;
1587 BOOL invalidRTF = FALSE;
1588 ME_Cursor *selStart, *selEnd;
1589 LRESULT num_read = 0; /* bytes read for SF_TEXT, non-control chars inserted for SF_RTF */
1591 TRACE("stream==%p editor==%p format==0x%X\n", stream, editor, format);
1592 editor->nEventMask = 0;
1594 ME_GetSelectionOfs(editor, &from, &to);
1595 if (format & SFF_SELECTION && editor->mode & TM_RICHTEXT)
1597 ME_GetSelection(editor, &selStart, &selEnd);
1598 style = ME_GetSelectionInsertStyle(editor);
1600 ME_InternalDeleteText(editor, selStart, to - from, FALSE);
1602 /* Don't insert text at the end of the table row */
1603 if (!editor->bEmulateVersion10) { /* v4.1 */
1604 ME_DisplayItem *para = editor->pCursors->pPara;
1605 if (para->member.para.nFlags & MEPF_ROWEND)
1607 para = para->member.para.next_para;
1608 editor->pCursors[0].pPara = para;
1609 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
1610 editor->pCursors[0].nOffset = 0;
1612 if (para->member.para.nFlags & MEPF_ROWSTART)
1614 para = para->member.para.next_para;
1615 editor->pCursors[0].pPara = para;
1616 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
1617 editor->pCursors[0].nOffset = 0;
1619 editor->pCursors[1] = editor->pCursors[0];
1620 } else { /* v1.0 - 3.0 */
1621 if (editor->pCursors[0].pRun->member.run.nFlags & MERF_ENDPARA &&
1622 ME_IsInTable(editor->pCursors[0].pRun))
1623 return 0;
1625 } else {
1626 style = editor->pBuffer->pDefaultStyle;
1627 ME_AddRefStyle(style);
1628 ME_SetSelection(editor, 0, 0);
1629 ME_InternalDeleteText(editor, &editor->pCursors[1],
1630 ME_GetTextLength(editor), FALSE);
1631 from = to = 0;
1632 ME_ClearTempStyle(editor);
1633 ME_SetDefaultParaFormat(editor, &editor->pCursors[0].pPara->member.para.fmt);
1637 /* Back up undo mode to a local variable */
1638 nUndoMode = editor->nUndoMode;
1640 /* Only create an undo if SFF_SELECTION is set */
1641 if (!(format & SFF_SELECTION))
1642 editor->nUndoMode = umIgnore;
1644 inStream.editstream = stream;
1645 inStream.editstream->dwError = 0;
1646 inStream.dwSize = 0;
1647 inStream.dwUsed = 0;
1649 if (format & SF_RTF)
1651 /* Check if it's really RTF, and if it is not, use plain text */
1652 ME_StreamInFill(&inStream);
1653 if (!inStream.editstream->dwError)
1655 if ((!editor->bEmulateVersion10 && strncmp(inStream.buffer, "{\\rtf", 5) && strncmp(inStream.buffer, "{\\urtf", 6))
1656 || (editor->bEmulateVersion10 && *inStream.buffer != '{'))
1658 invalidRTF = TRUE;
1659 inStream.editstream->dwError = -16;
1664 if (!invalidRTF && !inStream.editstream->dwError)
1666 ME_Cursor start;
1667 from = ME_GetCursorOfs(&editor->pCursors[0]);
1668 if (format & SF_RTF) {
1670 /* setup the RTF parser */
1671 memset(&parser, 0, sizeof parser);
1672 RTFSetEditStream(&parser, &inStream);
1673 parser.rtfFormat = format&(SF_TEXT|SF_RTF);
1674 parser.editor = editor;
1675 parser.style = style;
1676 WriterInit(&parser);
1677 RTFInit(&parser);
1678 RTFSetReadHook(&parser, ME_RTFReadHook);
1679 RTFSetDestinationCallback(&parser, rtfShpPict, ME_RTFReadShpPictGroup);
1680 RTFSetDestinationCallback(&parser, rtfPict, ME_RTFReadPictGroup);
1681 RTFSetDestinationCallback(&parser, rtfObject, ME_RTFReadObjectGroup);
1682 RTFSetDestinationCallback(&parser, rtfParNumbering, ME_RTFReadParnumGroup);
1683 if (!parser.editor->bEmulateVersion10) /* v4.1 */
1685 RTFSetDestinationCallback(&parser, rtfNoNestTables, RTFSkipGroup);
1686 RTFSetDestinationCallback(&parser, rtfNestTableProps, RTFReadGroup);
1688 BeginFile(&parser);
1690 /* do the parsing */
1691 RTFRead(&parser);
1692 RTFFlushOutputBuffer(&parser);
1693 if (!editor->bEmulateVersion10) { /* v4.1 */
1694 if (parser.tableDef && parser.tableDef->tableRowStart &&
1695 (parser.nestingLevel > 0 || parser.canInheritInTbl))
1697 /* Delete any incomplete table row at the end of the rich text. */
1698 int nOfs, nChars;
1699 ME_DisplayItem *para;
1701 parser.rtfMinor = rtfRow;
1702 /* Complete the table row before deleting it.
1703 * By doing it this way we will have the current paragraph format set
1704 * properly to reflect that is not in the complete table, and undo items
1705 * will be added for this change to the current paragraph format. */
1706 if (parser.nestingLevel > 0)
1708 while (parser.nestingLevel > 1)
1709 ME_RTFSpecialCharHook(&parser); /* Decrements nestingLevel */
1710 para = parser.tableDef->tableRowStart;
1711 ME_RTFSpecialCharHook(&parser);
1712 } else {
1713 para = parser.tableDef->tableRowStart;
1714 ME_RTFSpecialCharHook(&parser);
1715 assert(para->member.para.nFlags & MEPF_ROWEND);
1716 para = para->member.para.next_para;
1719 editor->pCursors[1].pPara = para;
1720 editor->pCursors[1].pRun = ME_FindItemFwd(para, diRun);
1721 editor->pCursors[1].nOffset = 0;
1722 nOfs = ME_GetCursorOfs(&editor->pCursors[1]);
1723 nChars = ME_GetCursorOfs(&editor->pCursors[0]) - nOfs;
1724 ME_InternalDeleteText(editor, &editor->pCursors[1], nChars, TRUE);
1725 if (parser.tableDef)
1726 parser.tableDef->tableRowStart = NULL;
1729 ME_CheckTablesForCorruption(editor);
1730 RTFDestroy(&parser);
1732 if (parser.stackTop > 0)
1734 while (--parser.stackTop >= 0)
1736 ME_ReleaseStyle(parser.style);
1737 parser.style = parser.stack[parser.stackTop].style;
1739 if (!inStream.editstream->dwError)
1740 inStream.editstream->dwError = HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
1743 /* Remove last line break, as mandated by tests. This is not affected by
1744 CR/LF counters, since RTF streaming presents only \para tokens, which
1745 are converted according to the standard rules: \r for 2.0, \r\n for 1.0
1747 if (stripLastCR && !(format & SFF_SELECTION)) {
1748 int newto;
1749 ME_GetSelection(editor, &selStart, &selEnd);
1750 newto = ME_GetCursorOfs(selEnd);
1751 if (newto > to + (editor->bEmulateVersion10 ? 1 : 0)) {
1752 WCHAR lastchar[3] = {'\0', '\0'};
1753 int linebreakSize = editor->bEmulateVersion10 ? 2 : 1;
1754 ME_Cursor linebreakCursor = *selEnd, lastcharCursor = *selEnd;
1755 CHARFORMAT2W cf;
1757 /* Set the final eop to the char fmt of the last char */
1758 cf.cbSize = sizeof(cf);
1759 cf.dwMask = CFM_ALL2;
1760 ME_MoveCursorChars(editor, &lastcharCursor, -1, FALSE);
1761 ME_GetCharFormat(editor, &lastcharCursor, &linebreakCursor, &cf);
1762 ME_SetSelection(editor, newto, -1);
1763 ME_SetSelectionCharFormat(editor, &cf);
1764 ME_SetSelection(editor, newto, newto);
1766 ME_MoveCursorChars(editor, &linebreakCursor, -linebreakSize, FALSE);
1767 ME_GetTextW(editor, lastchar, 2, &linebreakCursor, linebreakSize, FALSE, FALSE);
1768 if (lastchar[0] == '\r' && (lastchar[1] == '\n' || lastchar[1] == '\0')) {
1769 ME_InternalDeleteText(editor, &linebreakCursor, linebreakSize, FALSE);
1773 to = ME_GetCursorOfs(&editor->pCursors[0]);
1774 num_read = to - from;
1776 style = parser.style;
1778 else if (format & SF_TEXT)
1780 num_read = ME_StreamInText(editor, format, &inStream, style);
1781 to = ME_GetCursorOfs(&editor->pCursors[0]);
1783 else
1784 ERR("EM_STREAMIN without SF_TEXT or SF_RTF\n");
1785 /* put the cursor at the top */
1786 if (!(format & SFF_SELECTION))
1787 ME_SetSelection(editor, 0, 0);
1788 ME_CursorFromCharOfs(editor, from, &start);
1789 ME_UpdateLinkAttribute(editor, &start, to - from);
1792 /* Restore saved undo mode */
1793 editor->nUndoMode = nUndoMode;
1795 /* even if we didn't add an undo, we need to commit anything on the stack */
1796 ME_CommitUndo(editor);
1798 /* If SFF_SELECTION isn't set, delete any undos from before we started too */
1799 if (!(format & SFF_SELECTION))
1800 ME_EmptyUndoStack(editor);
1802 ME_ReleaseStyle(style);
1803 editor->nEventMask = nEventMask;
1804 ME_UpdateRepaint(editor, FALSE);
1805 if (!(format & SFF_SELECTION)) {
1806 ME_ClearTempStyle(editor);
1808 ITextHost_TxShowCaret(editor->texthost, FALSE);
1809 ME_MoveCaret(editor);
1810 ITextHost_TxShowCaret(editor->texthost, TRUE);
1811 ME_SendSelChange(editor);
1812 ME_SendRequestResize(editor, FALSE);
1814 return num_read;
1818 typedef struct tagME_RTFStringStreamStruct
1820 char *string;
1821 int pos;
1822 int length;
1823 } ME_RTFStringStreamStruct;
1825 static DWORD CALLBACK ME_ReadFromRTFString(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
1827 ME_RTFStringStreamStruct *pStruct = (ME_RTFStringStreamStruct *)dwCookie;
1828 int count;
1830 count = min(cb, pStruct->length - pStruct->pos);
1831 memmove(lpBuff, pStruct->string + pStruct->pos, count);
1832 pStruct->pos += count;
1833 *pcb = count;
1834 return 0;
1837 static void
1838 ME_StreamInRTFString(ME_TextEditor *editor, BOOL selection, char *string)
1840 EDITSTREAM es;
1841 ME_RTFStringStreamStruct data;
1843 data.string = string;
1844 data.length = strlen(string);
1845 data.pos = 0;
1846 es.dwCookie = (DWORD_PTR)&data;
1847 es.pfnCallback = ME_ReadFromRTFString;
1848 ME_StreamIn(editor, SF_RTF | (selection ? SFF_SELECTION : 0), &es, TRUE);
1852 static int
1853 ME_FindText(ME_TextEditor *editor, DWORD flags, const CHARRANGE *chrg, const WCHAR *text, CHARRANGE *chrgText)
1855 const int nLen = lstrlenW(text);
1856 const int nTextLen = ME_GetTextLength(editor);
1857 int nMin, nMax;
1858 ME_Cursor cursor;
1859 WCHAR wLastChar = ' ';
1861 TRACE("flags==0x%08x, chrg->cpMin==%d, chrg->cpMax==%d text==%s\n",
1862 flags, chrg->cpMin, chrg->cpMax, debugstr_w(text));
1864 if (flags & ~(FR_DOWN | FR_MATCHCASE | FR_WHOLEWORD))
1865 FIXME("Flags 0x%08x not implemented\n",
1866 flags & ~(FR_DOWN | FR_MATCHCASE | FR_WHOLEWORD));
1868 nMin = chrg->cpMin;
1869 if (chrg->cpMax == -1)
1870 nMax = nTextLen;
1871 else
1872 nMax = chrg->cpMax > nTextLen ? nTextLen : chrg->cpMax;
1874 /* In 1.0 emulation, if cpMax reaches end of text, add the FR_DOWN flag */
1875 if (editor->bEmulateVersion10 && nMax == nTextLen)
1877 flags |= FR_DOWN;
1880 /* In 1.0 emulation, cpMin must always be no greater than cpMax */
1881 if (editor->bEmulateVersion10 && nMax < nMin)
1883 if (chrgText)
1885 chrgText->cpMin = -1;
1886 chrgText->cpMax = -1;
1888 return -1;
1891 /* when searching up, if cpMin < cpMax, then instead of searching
1892 * on [cpMin,cpMax], we search on [0,cpMin], otherwise, search on
1893 * [cpMax, cpMin]. The exception is when cpMax is -1, in which
1894 * case, it is always bigger than cpMin.
1896 if (!editor->bEmulateVersion10 && !(flags & FR_DOWN))
1898 int nSwap = nMax;
1900 nMax = nMin > nTextLen ? nTextLen : nMin;
1901 if (nMin < nSwap || chrg->cpMax == -1)
1902 nMin = 0;
1903 else
1904 nMin = nSwap;
1907 if (!nLen || nMin < 0 || nMax < 0 || nMax < nMin)
1909 if (chrgText)
1910 chrgText->cpMin = chrgText->cpMax = -1;
1911 return -1;
1914 if (flags & FR_DOWN) /* Forward search */
1916 /* If possible, find the character before where the search starts */
1917 if ((flags & FR_WHOLEWORD) && nMin)
1919 ME_CursorFromCharOfs(editor, nMin - 1, &cursor);
1920 wLastChar = *get_text( &cursor.pRun->member.run, cursor.nOffset );
1921 ME_MoveCursorChars(editor, &cursor, 1, FALSE);
1922 } else {
1923 ME_CursorFromCharOfs(editor, nMin, &cursor);
1926 while (cursor.pRun && ME_GetCursorOfs(&cursor) + nLen <= nMax)
1928 ME_DisplayItem *pCurItem = cursor.pRun;
1929 int nCurStart = cursor.nOffset;
1930 int nMatched = 0;
1932 while (pCurItem && ME_CharCompare( *get_text( &pCurItem->member.run, nCurStart + nMatched ), text[nMatched], (flags & FR_MATCHCASE)))
1934 if ((flags & FR_WHOLEWORD) && isalnumW(wLastChar))
1935 break;
1937 nMatched++;
1938 if (nMatched == nLen)
1940 ME_DisplayItem *pNextItem = pCurItem;
1941 int nNextStart = nCurStart;
1942 WCHAR wNextChar;
1944 /* Check to see if next character is a whitespace */
1945 if (flags & FR_WHOLEWORD)
1947 if (nCurStart + nMatched == pCurItem->member.run.len)
1949 pNextItem = ME_FindItemFwd(pCurItem, diRun);
1950 nNextStart = -nMatched;
1953 if (pNextItem)
1954 wNextChar = *get_text( &pNextItem->member.run, nNextStart + nMatched );
1955 else
1956 wNextChar = ' ';
1958 if (isalnumW(wNextChar))
1959 break;
1962 cursor.nOffset += cursor.pPara->member.para.nCharOfs + cursor.pRun->member.run.nCharOfs;
1963 if (chrgText)
1965 chrgText->cpMin = cursor.nOffset;
1966 chrgText->cpMax = cursor.nOffset + nLen;
1968 TRACE("found at %d-%d\n", cursor.nOffset, cursor.nOffset + nLen);
1969 return cursor.nOffset;
1971 if (nCurStart + nMatched == pCurItem->member.run.len)
1973 pCurItem = ME_FindItemFwd(pCurItem, diRun);
1974 nCurStart = -nMatched;
1977 if (pCurItem)
1978 wLastChar = *get_text( &pCurItem->member.run, nCurStart + nMatched );
1979 else
1980 wLastChar = ' ';
1982 cursor.nOffset++;
1983 if (cursor.nOffset == cursor.pRun->member.run.len)
1985 ME_NextRun(&cursor.pPara, &cursor.pRun, TRUE);
1986 cursor.nOffset = 0;
1990 else /* Backward search */
1992 /* If possible, find the character after where the search ends */
1993 if ((flags & FR_WHOLEWORD) && nMax < nTextLen - 1)
1995 ME_CursorFromCharOfs(editor, nMax + 1, &cursor);
1996 wLastChar = *get_text( &cursor.pRun->member.run, cursor.nOffset );
1997 ME_MoveCursorChars(editor, &cursor, -1, FALSE);
1998 } else {
1999 ME_CursorFromCharOfs(editor, nMax, &cursor);
2002 while (cursor.pRun && ME_GetCursorOfs(&cursor) - nLen >= nMin)
2004 ME_DisplayItem *pCurItem = cursor.pRun;
2005 ME_DisplayItem *pCurPara = cursor.pPara;
2006 int nCurEnd = cursor.nOffset;
2007 int nMatched = 0;
2009 if (nCurEnd == 0)
2011 ME_PrevRun(&pCurPara, &pCurItem, TRUE);
2012 nCurEnd = pCurItem->member.run.len;
2015 while (pCurItem && ME_CharCompare( *get_text( &pCurItem->member.run, nCurEnd - nMatched - 1 ),
2016 text[nLen - nMatched - 1], (flags & FR_MATCHCASE) ))
2018 if ((flags & FR_WHOLEWORD) && isalnumW(wLastChar))
2019 break;
2021 nMatched++;
2022 if (nMatched == nLen)
2024 ME_DisplayItem *pPrevItem = pCurItem;
2025 int nPrevEnd = nCurEnd;
2026 WCHAR wPrevChar;
2027 int nStart;
2029 /* Check to see if previous character is a whitespace */
2030 if (flags & FR_WHOLEWORD)
2032 if (nPrevEnd - nMatched == 0)
2034 pPrevItem = ME_FindItemBack(pCurItem, diRun);
2035 if (pPrevItem)
2036 nPrevEnd = pPrevItem->member.run.len + nMatched;
2039 if (pPrevItem)
2040 wPrevChar = *get_text( &pPrevItem->member.run, nPrevEnd - nMatched - 1 );
2041 else
2042 wPrevChar = ' ';
2044 if (isalnumW(wPrevChar))
2045 break;
2048 nStart = pCurPara->member.para.nCharOfs
2049 + pCurItem->member.run.nCharOfs + nCurEnd - nMatched;
2050 if (chrgText)
2052 chrgText->cpMin = nStart;
2053 chrgText->cpMax = nStart + nLen;
2055 TRACE("found at %d-%d\n", nStart, nStart + nLen);
2056 return nStart;
2058 if (nCurEnd - nMatched == 0)
2060 ME_PrevRun(&pCurPara, &pCurItem, TRUE);
2061 /* Don't care about pCurItem becoming NULL here; it's already taken
2062 * care of in the exterior loop condition */
2063 nCurEnd = pCurItem->member.run.len + nMatched;
2066 if (pCurItem)
2067 wLastChar = *get_text( &pCurItem->member.run, nCurEnd - nMatched - 1 );
2068 else
2069 wLastChar = ' ';
2071 cursor.nOffset--;
2072 if (cursor.nOffset < 0)
2074 ME_PrevRun(&cursor.pPara, &cursor.pRun, TRUE);
2075 cursor.nOffset = cursor.pRun->member.run.len;
2079 TRACE("not found\n");
2080 if (chrgText)
2081 chrgText->cpMin = chrgText->cpMax = -1;
2082 return -1;
2085 static int ME_GetTextEx(ME_TextEditor *editor, GETTEXTEX *ex, LPARAM pText)
2087 int nChars;
2088 ME_Cursor start;
2090 if (!ex->cb || !pText) return 0;
2092 if (ex->flags & ~(GT_SELECTION | GT_USECRLF))
2093 FIXME("GETTEXTEX flags 0x%08x not supported\n", ex->flags & ~(GT_SELECTION | GT_USECRLF));
2095 if (ex->flags & GT_SELECTION)
2097 int from, to;
2098 int nStartCur = ME_GetSelectionOfs(editor, &from, &to);
2099 start = editor->pCursors[nStartCur];
2100 nChars = to - from;
2102 else
2104 ME_SetCursorToStart(editor, &start);
2105 nChars = INT_MAX;
2107 if (ex->codepage == CP_UNICODE)
2109 return ME_GetTextW(editor, (LPWSTR)pText, ex->cb / sizeof(WCHAR) - 1,
2110 &start, nChars, ex->flags & GT_USECRLF, FALSE);
2112 else
2114 /* potentially each char may be a CR, why calculate the exact value with O(N) when
2115 we can just take a bigger buffer? :)
2116 The above assumption still holds with CR/LF counters, since CR->CRLF expansion
2117 occurs only in richedit 2.0 mode, in which line breaks have only one CR
2119 int crlfmul = (ex->flags & GT_USECRLF) ? 2 : 1;
2120 DWORD buflen;
2121 LPWSTR buffer;
2122 LRESULT rc;
2124 buflen = min(crlfmul * nChars, ex->cb - 1);
2125 buffer = heap_alloc((buflen + 1) * sizeof(WCHAR));
2127 nChars = ME_GetTextW(editor, buffer, buflen, &start, nChars, ex->flags & GT_USECRLF, FALSE);
2128 rc = WideCharToMultiByte(ex->codepage, 0, buffer, nChars + 1,
2129 (LPSTR)pText, ex->cb, ex->lpDefaultChar, ex->lpUsedDefChar);
2130 if (rc) rc--; /* do not count 0 terminator */
2132 heap_free(buffer);
2133 return rc;
2137 static int ME_GetTextRange(ME_TextEditor *editor, WCHAR *strText,
2138 const ME_Cursor *start, int nLen, BOOL unicode)
2140 if (!strText) return 0;
2141 if (unicode) {
2142 return ME_GetTextW(editor, strText, INT_MAX, start, nLen, FALSE, FALSE);
2143 } else {
2144 int nChars;
2145 WCHAR *p = ALLOC_N_OBJ(WCHAR, nLen+1);
2146 if (!p) return 0;
2147 nChars = ME_GetTextW(editor, p, nLen, start, nLen, FALSE, FALSE);
2148 WideCharToMultiByte(CP_ACP, 0, p, nChars+1, (char *)strText,
2149 nLen+1, NULL, NULL);
2150 FREE_OBJ(p);
2151 return nChars;
2155 static int handle_EM_EXSETSEL( ME_TextEditor *editor, int to, int from )
2157 int end;
2159 TRACE("%d - %d\n", to, from );
2161 ME_InvalidateSelection( editor );
2162 end = ME_SetSelection( editor, to, from );
2163 ME_InvalidateSelection( editor );
2164 ITextHost_TxShowCaret( editor->texthost, FALSE );
2165 ME_ShowCaret( 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 init_paste_formats();
2299 if (ps && ps->dwAspect != DVASPECT_CONTENT)
2300 FIXME("Ignoring aspect %x\n", ps->dwAspect);
2302 hr = OleGetClipboard( &data );
2303 if (hr != S_OK) return FALSE;
2305 if (cf == CF_TEXT) cf = CF_UNICODETEXT;
2307 hr = S_FALSE;
2308 for (format = paste_formats; format->fmt.cfFormat; format++)
2310 if (cf && cf != format->fmt.cfFormat) continue;
2311 hr = IDataObject_QueryGetData( data, &format->fmt );
2312 if (hr == S_OK)
2314 if (!check_only)
2316 hr = IDataObject_GetData( data, &format->fmt, &med );
2317 if (hr != S_OK) goto done;
2318 hr = format->paste( editor, &format->fmt, &med );
2320 break;
2324 done:
2325 IDataObject_Release( data );
2327 return hr == S_OK;
2330 static BOOL ME_Copy(ME_TextEditor *editor, const ME_Cursor *start, int nChars)
2332 LPDATAOBJECT dataObj = NULL;
2333 HRESULT hr = S_OK;
2335 if (editor->cPasswordMask)
2336 return FALSE; /* Copying or Cutting masked text isn't allowed */
2338 if(editor->lpOleCallback)
2340 CHARRANGE range;
2341 range.cpMin = ME_GetCursorOfs(start);
2342 range.cpMax = range.cpMin + nChars;
2343 hr = IRichEditOleCallback_GetClipboardData(editor->lpOleCallback, &range, RECO_COPY, &dataObj);
2345 if(FAILED(hr) || !dataObj)
2346 hr = ME_GetDataObject(editor, start, nChars, &dataObj);
2347 if(SUCCEEDED(hr)) {
2348 hr = OleSetClipboard(dataObj);
2349 IDataObject_Release(dataObj);
2351 return SUCCEEDED(hr);
2354 /* helper to send a msg filter notification */
2355 static BOOL
2356 ME_FilterEvent(ME_TextEditor *editor, UINT msg, WPARAM* wParam, LPARAM* lParam)
2358 MSGFILTER msgf;
2360 if (!editor->hWnd || !editor->hwndParent) return FALSE;
2361 msgf.nmhdr.hwndFrom = editor->hWnd;
2362 msgf.nmhdr.idFrom = GetWindowLongW(editor->hWnd, GWLP_ID);
2363 msgf.nmhdr.code = EN_MSGFILTER;
2364 msgf.msg = msg;
2365 msgf.wParam = *wParam;
2366 msgf.lParam = *lParam;
2367 if (SendMessageW(editor->hwndParent, WM_NOTIFY, msgf.nmhdr.idFrom, (LPARAM)&msgf))
2368 return FALSE;
2369 *wParam = msgf.wParam;
2370 *lParam = msgf.lParam;
2371 msgf.wParam = *wParam;
2373 return TRUE;
2376 static void ME_UpdateSelectionLinkAttribute(ME_TextEditor *editor)
2378 ME_DisplayItem *startPara, *endPara;
2379 ME_DisplayItem *prev_para;
2380 ME_Cursor *from, *to;
2381 ME_Cursor start;
2382 int nChars;
2384 if (!editor->AutoURLDetect_bEnable) return;
2386 ME_GetSelection(editor, &from, &to);
2388 /* Find paragraph previous to the one that contains start cursor */
2389 startPara = from->pPara;
2390 prev_para = startPara->member.para.prev_para;
2391 if (prev_para->type == diParagraph) startPara = prev_para;
2393 /* Find paragraph that contains end cursor */
2394 endPara = to->pPara->member.para.next_para;
2396 start.pPara = startPara;
2397 start.pRun = ME_FindItemFwd(startPara, diRun);
2398 start.nOffset = 0;
2399 nChars = endPara->member.para.nCharOfs - startPara->member.para.nCharOfs;
2401 ME_UpdateLinkAttribute(editor, &start, nChars);
2404 static BOOL
2405 ME_KeyDown(ME_TextEditor *editor, WORD nKey)
2407 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2408 BOOL shift_is_down = GetKeyState(VK_SHIFT) & 0x8000;
2410 if (editor->bMouseCaptured)
2411 return FALSE;
2412 if (nKey != VK_SHIFT && nKey != VK_CONTROL && nKey != VK_MENU)
2413 editor->nSelectionType = stPosition;
2415 switch (nKey)
2417 case VK_LEFT:
2418 case VK_RIGHT:
2419 case VK_HOME:
2420 case VK_END:
2421 editor->nUDArrowX = -1;
2422 /* fall through */
2423 case VK_UP:
2424 case VK_DOWN:
2425 case VK_PRIOR:
2426 case VK_NEXT:
2427 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
2428 ME_ArrowKey(editor, nKey, shift_is_down, ctrl_is_down);
2429 return TRUE;
2430 case VK_BACK:
2431 case VK_DELETE:
2432 editor->nUDArrowX = -1;
2433 /* FIXME backspace and delete aren't the same, they act different wrt paragraph style of the merged paragraph */
2434 if (editor->styleFlags & ES_READONLY)
2435 return FALSE;
2436 if (ME_IsSelection(editor))
2438 ME_DeleteSelection(editor);
2439 ME_CommitUndo(editor);
2441 else if (nKey == VK_DELETE)
2443 /* Delete stops group typing.
2444 * (See MSDN remarks on EM_STOPGROUPTYPING message) */
2445 ME_DeleteTextAtCursor(editor, 1, 1);
2446 ME_CommitUndo(editor);
2448 else if (ME_ArrowKey(editor, VK_LEFT, FALSE, FALSE))
2450 BOOL bDeletionSucceeded;
2451 /* Backspace can be grouped for a single undo */
2452 ME_ContinueCoalescingTransaction(editor);
2453 bDeletionSucceeded = ME_DeleteTextAtCursor(editor, 1, 1);
2454 if (!bDeletionSucceeded && !editor->bEmulateVersion10) { /* v4.1 */
2455 /* Deletion was prevented so the cursor is moved back to where it was.
2456 * (e.g. this happens when trying to delete cell boundaries)
2458 ME_ArrowKey(editor, VK_RIGHT, FALSE, FALSE);
2460 ME_CommitCoalescingUndo(editor);
2462 else
2463 return TRUE;
2464 ME_MoveCursorFromTableRowStartParagraph(editor);
2465 ME_UpdateSelectionLinkAttribute(editor);
2466 ME_UpdateRepaint(editor, FALSE);
2467 ME_SendRequestResize(editor, FALSE);
2468 return TRUE;
2469 case VK_RETURN:
2470 if (editor->bDialogMode)
2472 if (ctrl_is_down)
2473 return TRUE;
2475 if (!(editor->styleFlags & ES_WANTRETURN))
2477 if (editor->hwndParent)
2479 DWORD dw;
2480 dw = SendMessageW(editor->hwndParent, DM_GETDEFID, 0, 0);
2481 if (HIWORD(dw) == DC_HASDEFID)
2483 HWND hwDefCtrl = GetDlgItem(editor->hwndParent, LOWORD(dw));
2484 if (hwDefCtrl)
2486 SendMessageW(editor->hwndParent, WM_NEXTDLGCTL, (WPARAM)hwDefCtrl, TRUE);
2487 PostMessageW(hwDefCtrl, WM_KEYDOWN, VK_RETURN, 0);
2491 return TRUE;
2495 if (editor->styleFlags & ES_MULTILINE)
2497 ME_Cursor cursor = editor->pCursors[0];
2498 ME_DisplayItem *para = cursor.pPara;
2499 int from, to;
2500 const WCHAR endl = '\r';
2501 const WCHAR endlv10[] = {'\r','\n'};
2502 ME_Style *style, *eop_style;
2504 if (editor->styleFlags & ES_READONLY) {
2505 MessageBeep(MB_ICONERROR);
2506 return TRUE;
2509 ME_GetSelectionOfs(editor, &from, &to);
2510 if (editor->nTextLimit > ME_GetTextLength(editor) - (to-from))
2512 if (!editor->bEmulateVersion10) { /* v4.1 */
2513 if (para->member.para.nFlags & MEPF_ROWEND) {
2514 /* Add a new table row after this row. */
2515 para = ME_AppendTableRow(editor, para);
2516 para = para->member.para.next_para;
2517 editor->pCursors[0].pPara = para;
2518 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2519 editor->pCursors[0].nOffset = 0;
2520 editor->pCursors[1] = editor->pCursors[0];
2521 ME_CommitUndo(editor);
2522 ME_CheckTablesForCorruption(editor);
2523 ME_UpdateRepaint(editor, FALSE);
2524 return TRUE;
2526 else if (para == editor->pCursors[1].pPara &&
2527 cursor.nOffset + cursor.pRun->member.run.nCharOfs == 0 &&
2528 para->member.para.prev_para->member.para.nFlags & MEPF_ROWSTART &&
2529 !para->member.para.prev_para->member.para.nCharOfs)
2531 /* Insert a newline before the table. */
2532 para = para->member.para.prev_para;
2533 para->member.para.nFlags &= ~MEPF_ROWSTART;
2534 editor->pCursors[0].pPara = para;
2535 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2536 editor->pCursors[1] = editor->pCursors[0];
2537 ME_InsertTextFromCursor(editor, 0, &endl, 1,
2538 editor->pCursors[0].pRun->member.run.style);
2539 para = editor->pBuffer->pFirst->member.para.next_para;
2540 ME_SetDefaultParaFormat(editor, &para->member.para.fmt);
2541 para->member.para.nFlags = MEPF_REWRAP;
2542 editor->pCursors[0].pPara = para;
2543 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2544 editor->pCursors[1] = editor->pCursors[0];
2545 para->member.para.next_para->member.para.nFlags |= MEPF_ROWSTART;
2546 ME_CommitCoalescingUndo(editor);
2547 ME_CheckTablesForCorruption(editor);
2548 ME_UpdateRepaint(editor, FALSE);
2549 return TRUE;
2551 } else { /* v1.0 - 3.0 */
2552 ME_DisplayItem *para = cursor.pPara;
2553 if (ME_IsInTable(para))
2555 if (cursor.pRun->member.run.nFlags & MERF_ENDPARA)
2557 if (from == to) {
2558 ME_ContinueCoalescingTransaction(editor);
2559 para = ME_AppendTableRow(editor, para);
2560 editor->pCursors[0].pPara = para;
2561 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2562 editor->pCursors[0].nOffset = 0;
2563 editor->pCursors[1] = editor->pCursors[0];
2564 ME_CommitCoalescingUndo(editor);
2565 ME_UpdateRepaint(editor, FALSE);
2566 return TRUE;
2568 } else {
2569 ME_ContinueCoalescingTransaction(editor);
2570 if (cursor.pRun->member.run.nCharOfs + cursor.nOffset == 0 &&
2571 !ME_IsInTable(para->member.para.prev_para))
2573 /* Insert newline before table */
2574 cursor.pRun = ME_FindItemBack(para, diRun);
2575 if (cursor.pRun) {
2576 editor->pCursors[0].pRun = cursor.pRun;
2577 editor->pCursors[0].pPara = para->member.para.prev_para;
2579 editor->pCursors[0].nOffset = 0;
2580 editor->pCursors[1] = editor->pCursors[0];
2581 ME_InsertTextFromCursor(editor, 0, &endl, 1,
2582 editor->pCursors[0].pRun->member.run.style);
2583 } else {
2584 editor->pCursors[1] = editor->pCursors[0];
2585 para = ME_AppendTableRow(editor, para);
2586 editor->pCursors[0].pPara = para;
2587 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2588 editor->pCursors[0].nOffset = 0;
2589 editor->pCursors[1] = editor->pCursors[0];
2591 ME_CommitCoalescingUndo(editor);
2592 ME_UpdateRepaint(editor, FALSE);
2593 return TRUE;
2598 style = ME_GetInsertStyle(editor, 0);
2600 /* Normally the new eop style is the insert style, however in a list it is copied from the existing
2601 eop style (this prevents the list label style changing when the new eop is inserted).
2602 No extra ref is taken here on eop_style. */
2603 if (para->member.para.fmt.wNumbering)
2604 eop_style = para->member.para.eop_run->style;
2605 else
2606 eop_style = style;
2607 ME_ContinueCoalescingTransaction(editor);
2608 if (shift_is_down)
2609 ME_InsertEndRowFromCursor(editor, 0);
2610 else
2611 if (!editor->bEmulateVersion10)
2612 ME_InsertTextFromCursor(editor, 0, &endl, 1, eop_style);
2613 else
2614 ME_InsertTextFromCursor(editor, 0, endlv10, 2, eop_style);
2615 ME_CommitCoalescingUndo(editor);
2616 SetCursor(NULL);
2618 ME_UpdateSelectionLinkAttribute(editor);
2619 ME_UpdateRepaint(editor, FALSE);
2620 ME_SaveTempStyle(editor, style); /* set the temp insert style for the new para */
2621 ME_ReleaseStyle(style);
2623 return TRUE;
2625 break;
2626 case VK_ESCAPE:
2627 if (editor->bDialogMode && editor->hwndParent)
2628 PostMessageW(editor->hwndParent, WM_CLOSE, 0, 0);
2629 return TRUE;
2630 case VK_TAB:
2631 if (editor->bDialogMode && editor->hwndParent)
2632 SendMessageW(editor->hwndParent, WM_NEXTDLGCTL, shift_is_down, 0);
2633 return TRUE;
2634 case 'A':
2635 if (ctrl_is_down)
2637 handle_EM_EXSETSEL( editor, 0, -1 );
2638 return TRUE;
2640 break;
2641 case 'V':
2642 if (ctrl_is_down)
2643 return paste_special( editor, 0, NULL, FALSE );
2644 break;
2645 case 'C':
2646 case 'X':
2647 if (ctrl_is_down)
2649 BOOL result;
2650 int nOfs, nChars;
2651 int nStartCur = ME_GetSelectionOfs(editor, &nOfs, &nChars);
2652 ME_Cursor *selStart = &editor->pCursors[nStartCur];
2654 nChars -= nOfs;
2655 result = ME_Copy(editor, selStart, nChars);
2656 if (result && nKey == 'X')
2658 ME_InternalDeleteText(editor, selStart, nChars, FALSE);
2659 ME_CommitUndo(editor);
2660 ME_UpdateRepaint(editor, TRUE);
2662 return result;
2664 break;
2665 case 'Z':
2666 if (ctrl_is_down)
2668 ME_Undo(editor);
2669 return TRUE;
2671 break;
2672 case 'Y':
2673 if (ctrl_is_down)
2675 ME_Redo(editor);
2676 return TRUE;
2678 break;
2680 default:
2681 if (nKey != VK_SHIFT && nKey != VK_CONTROL && nKey && nKey != VK_MENU)
2682 editor->nUDArrowX = -1;
2683 if (ctrl_is_down)
2685 if (nKey == 'W')
2687 CHARFORMAT2W chf;
2688 char buf[2048];
2689 chf.cbSize = sizeof(chf);
2691 ME_GetSelectionCharFormat(editor, &chf);
2692 ME_DumpStyleToBuf(&chf, buf);
2693 MessageBoxA(NULL, buf, "Style dump", MB_OK);
2695 if (nKey == 'Q')
2697 ME_CheckCharOffsets(editor);
2701 return FALSE;
2704 static LRESULT ME_Char(ME_TextEditor *editor, WPARAM charCode,
2705 LPARAM flags, BOOL unicode)
2707 WCHAR wstr;
2709 if (editor->bMouseCaptured)
2710 return 0;
2712 if (unicode)
2713 wstr = (WCHAR)charCode;
2714 else
2716 CHAR charA = charCode;
2717 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &wstr, 1);
2720 if (editor->styleFlags & ES_READONLY) {
2721 MessageBeep(MB_ICONERROR);
2722 return 0; /* FIXME really 0 ? */
2725 if ((unsigned)wstr >= ' ' || wstr == '\t')
2727 ME_Cursor cursor = editor->pCursors[0];
2728 ME_DisplayItem *para = cursor.pPara;
2729 int from, to;
2730 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2731 ME_GetSelectionOfs(editor, &from, &to);
2732 if (wstr == '\t' &&
2733 /* v4.1 allows tabs to be inserted with ctrl key down */
2734 !(ctrl_is_down && !editor->bEmulateVersion10))
2736 ME_DisplayItem *para;
2737 BOOL bSelectedRow = FALSE;
2739 para = cursor.pPara;
2740 if (ME_IsSelection(editor) &&
2741 cursor.pRun->member.run.nCharOfs + cursor.nOffset == 0 &&
2742 to == ME_GetCursorOfs(&editor->pCursors[0]) &&
2743 para->member.para.prev_para->type == diParagraph)
2745 para = para->member.para.prev_para;
2746 bSelectedRow = TRUE;
2748 if (ME_IsInTable(para))
2750 ME_TabPressedInTable(editor, bSelectedRow);
2751 ME_CommitUndo(editor);
2752 return 0;
2754 } else if (!editor->bEmulateVersion10) { /* v4.1 */
2755 if (para->member.para.nFlags & MEPF_ROWEND) {
2756 if (from == to) {
2757 para = para->member.para.next_para;
2758 if (para->member.para.nFlags & MEPF_ROWSTART)
2759 para = para->member.para.next_para;
2760 editor->pCursors[0].pPara = para;
2761 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2762 editor->pCursors[0].nOffset = 0;
2763 editor->pCursors[1] = editor->pCursors[0];
2766 } else { /* v1.0 - 3.0 */
2767 if (ME_IsInTable(cursor.pRun) &&
2768 cursor.pRun->member.run.nFlags & MERF_ENDPARA &&
2769 from == to)
2771 /* Text should not be inserted at the end of the table. */
2772 MessageBeep(-1);
2773 return 0;
2776 /* FIXME maybe it would make sense to call EM_REPLACESEL instead ? */
2777 /* WM_CHAR is restricted to nTextLimit */
2778 if(editor->nTextLimit > ME_GetTextLength(editor) - (to-from))
2780 ME_Style *style = ME_GetInsertStyle(editor, 0);
2781 ME_ContinueCoalescingTransaction(editor);
2782 ME_InsertTextFromCursor(editor, 0, &wstr, 1, style);
2783 ME_ReleaseStyle(style);
2784 ME_CommitCoalescingUndo(editor);
2785 ITextHost_TxSetCursor(editor->texthost, NULL, FALSE);
2788 ME_UpdateSelectionLinkAttribute(editor);
2789 ME_UpdateRepaint(editor, FALSE);
2791 return 0;
2794 /* Process the message and calculate the new click count.
2796 * returns: The click count if it is mouse down event, else returns 0. */
2797 static int ME_CalculateClickCount(ME_TextEditor *editor, UINT msg, WPARAM wParam,
2798 LPARAM lParam)
2800 static int clickNum = 0;
2801 if (msg < WM_MOUSEFIRST || msg > WM_MOUSELAST)
2802 return 0;
2804 if ((msg == WM_LBUTTONDBLCLK) ||
2805 (msg == WM_RBUTTONDBLCLK) ||
2806 (msg == WM_MBUTTONDBLCLK) ||
2807 (msg == WM_XBUTTONDBLCLK))
2809 msg -= (WM_LBUTTONDBLCLK - WM_LBUTTONDOWN);
2812 if ((msg == WM_LBUTTONDOWN) ||
2813 (msg == WM_RBUTTONDOWN) ||
2814 (msg == WM_MBUTTONDOWN) ||
2815 (msg == WM_XBUTTONDOWN))
2817 static MSG prevClickMsg;
2818 MSG clickMsg;
2819 /* Compare the editor instead of the hwnd so that the this
2820 * can still be done for windowless richedit controls. */
2821 clickMsg.hwnd = (HWND)editor;
2822 clickMsg.message = msg;
2823 clickMsg.wParam = wParam;
2824 clickMsg.lParam = lParam;
2825 clickMsg.time = GetMessageTime();
2826 clickMsg.pt.x = (short)LOWORD(lParam);
2827 clickMsg.pt.y = (short)HIWORD(lParam);
2828 if ((clickNum != 0) &&
2829 (clickMsg.message == prevClickMsg.message) &&
2830 (clickMsg.hwnd == prevClickMsg.hwnd) &&
2831 (clickMsg.wParam == prevClickMsg.wParam) &&
2832 (clickMsg.time - prevClickMsg.time < GetDoubleClickTime()) &&
2833 (abs(clickMsg.pt.x - prevClickMsg.pt.x) < GetSystemMetrics(SM_CXDOUBLECLK)/2) &&
2834 (abs(clickMsg.pt.y - prevClickMsg.pt.y) < GetSystemMetrics(SM_CYDOUBLECLK)/2))
2836 clickNum++;
2837 } else {
2838 clickNum = 1;
2840 prevClickMsg = clickMsg;
2841 } else {
2842 return 0;
2844 return clickNum;
2847 static BOOL is_link( ME_Run *run )
2849 return (run->style->fmt.dwMask & CFM_LINK) && (run->style->fmt.dwEffects & CFE_LINK);
2852 static BOOL ME_SetCursor(ME_TextEditor *editor)
2854 ME_Cursor cursor;
2855 POINT pt;
2856 BOOL isExact;
2857 SCROLLBARINFO sbi;
2858 DWORD messagePos = GetMessagePos();
2859 pt.x = (short)LOWORD(messagePos);
2860 pt.y = (short)HIWORD(messagePos);
2862 if (editor->hWnd)
2864 sbi.cbSize = sizeof(sbi);
2865 GetScrollBarInfo(editor->hWnd, OBJID_HSCROLL, &sbi);
2866 if (!(sbi.rgstate[0] & (STATE_SYSTEM_INVISIBLE|STATE_SYSTEM_OFFSCREEN)) &&
2867 PtInRect(&sbi.rcScrollBar, pt))
2869 ITextHost_TxSetCursor(editor->texthost,
2870 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2871 return TRUE;
2873 sbi.cbSize = sizeof(sbi);
2874 GetScrollBarInfo(editor->hWnd, OBJID_VSCROLL, &sbi);
2875 if (!(sbi.rgstate[0] & (STATE_SYSTEM_INVISIBLE|STATE_SYSTEM_OFFSCREEN)) &&
2876 PtInRect(&sbi.rcScrollBar, pt))
2878 ITextHost_TxSetCursor(editor->texthost,
2879 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2880 return TRUE;
2883 ITextHost_TxScreenToClient(editor->texthost, &pt);
2885 if (editor->nSelectionType == stLine && editor->bMouseCaptured) {
2886 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2887 return TRUE;
2889 if (!editor->bEmulateVersion10 /* v4.1 */ &&
2890 pt.y < editor->rcFormat.top &&
2891 pt.x < editor->rcFormat.left)
2893 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2894 return TRUE;
2896 if (pt.y < editor->rcFormat.top || pt.y > editor->rcFormat.bottom)
2898 if (editor->bEmulateVersion10) /* v1.0 - 3.0 */
2899 ITextHost_TxSetCursor(editor->texthost,
2900 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2901 else /* v4.1 */
2902 ITextHost_TxSetCursor(editor->texthost,
2903 LoadCursorW(NULL, (WCHAR*)IDC_IBEAM), TRUE);
2904 return TRUE;
2906 if (pt.x < editor->rcFormat.left)
2908 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2909 return TRUE;
2911 ME_CharFromPos(editor, pt.x, pt.y, &cursor, &isExact);
2912 if (isExact)
2914 ME_Run *run;
2916 run = &cursor.pRun->member.run;
2917 if (is_link( run ))
2919 ITextHost_TxSetCursor(editor->texthost,
2920 LoadCursorW(NULL, (WCHAR*)IDC_HAND),
2921 FALSE);
2922 return TRUE;
2925 if (ME_IsSelection(editor))
2927 int selStart, selEnd;
2928 int offset = ME_GetCursorOfs(&cursor);
2930 ME_GetSelectionOfs(editor, &selStart, &selEnd);
2931 if (selStart <= offset && selEnd >= offset) {
2932 ITextHost_TxSetCursor(editor->texthost,
2933 LoadCursorW(NULL, (WCHAR*)IDC_ARROW),
2934 FALSE);
2935 return TRUE;
2939 ITextHost_TxSetCursor(editor->texthost,
2940 LoadCursorW(NULL, (WCHAR*)IDC_IBEAM), TRUE);
2941 return TRUE;
2944 static void ME_SetDefaultFormatRect(ME_TextEditor *editor)
2946 ITextHost_TxGetClientRect(editor->texthost, &editor->rcFormat);
2947 editor->rcFormat.top += editor->exStyleFlags & WS_EX_CLIENTEDGE ? 1 : 0;
2948 editor->rcFormat.left += 1 + editor->selofs;
2949 editor->rcFormat.right -= 1;
2952 static BOOL ME_ShowContextMenu(ME_TextEditor *editor, int x, int y)
2954 CHARRANGE selrange;
2955 HMENU menu;
2956 int seltype = 0;
2957 if(!editor->lpOleCallback || !editor->hWnd)
2958 return FALSE;
2959 ME_GetSelectionOfs(editor, &selrange.cpMin, &selrange.cpMax);
2960 if(selrange.cpMin == selrange.cpMax)
2961 seltype |= SEL_EMPTY;
2962 else
2964 /* FIXME: Handle objects */
2965 seltype |= SEL_TEXT;
2966 if(selrange.cpMax-selrange.cpMin > 1)
2967 seltype |= SEL_MULTICHAR;
2969 if(SUCCEEDED(IRichEditOleCallback_GetContextMenu(editor->lpOleCallback, seltype, NULL, &selrange, &menu)))
2971 TrackPopupMenu(menu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, x, y, 0, editor->hwndParent, NULL);
2972 DestroyMenu(menu);
2974 return TRUE;
2977 ME_TextEditor *ME_MakeEditor(ITextHost *texthost, BOOL bEmulateVersion10)
2979 ME_TextEditor *ed = ALLOC_OBJ(ME_TextEditor);
2980 int i;
2981 DWORD props;
2982 LONG selbarwidth;
2984 ed->hWnd = NULL;
2985 ed->hwndParent = NULL;
2986 ed->sizeWindow.cx = ed->sizeWindow.cy = 0;
2987 ed->texthost = texthost;
2988 ed->reOle = NULL;
2989 ed->bEmulateVersion10 = bEmulateVersion10;
2990 ed->styleFlags = 0;
2991 ed->exStyleFlags = 0;
2992 ITextHost_TxGetPropertyBits(texthost,
2993 (TXTBIT_RICHTEXT|TXTBIT_MULTILINE|
2994 TXTBIT_READONLY|TXTBIT_USEPASSWORD|
2995 TXTBIT_HIDESELECTION|TXTBIT_SAVESELECTION|
2996 TXTBIT_AUTOWORDSEL|TXTBIT_VERTICAL|
2997 TXTBIT_WORDWRAP|TXTBIT_DISABLEDRAG),
2998 &props);
2999 ITextHost_TxGetScrollBars(texthost, &ed->styleFlags);
3000 ed->styleFlags &= (WS_VSCROLL|WS_HSCROLL|ES_AUTOVSCROLL|
3001 ES_AUTOHSCROLL|ES_DISABLENOSCROLL);
3002 ed->pBuffer = ME_MakeText();
3003 ed->nZoomNumerator = ed->nZoomDenominator = 0;
3004 ed->nAvailWidth = 0; /* wrap to client area */
3005 ME_MakeFirstParagraph(ed);
3006 /* The four cursors are for:
3007 * 0 - The position where the caret is shown
3008 * 1 - The anchored end of the selection (for normal selection)
3009 * 2 & 3 - The anchored start and end respectively for word, line,
3010 * or paragraph selection.
3012 ed->nCursors = 4;
3013 ed->pCursors = ALLOC_N_OBJ(ME_Cursor, ed->nCursors);
3014 ME_SetCursorToStart(ed, &ed->pCursors[0]);
3015 ed->pCursors[1] = ed->pCursors[0];
3016 ed->pCursors[2] = ed->pCursors[0];
3017 ed->pCursors[3] = ed->pCursors[1];
3018 ed->nLastTotalLength = ed->nTotalLength = 0;
3019 ed->nLastTotalWidth = ed->nTotalWidth = 0;
3020 ed->nUDArrowX = -1;
3021 ed->rgbBackColor = -1;
3022 ed->hbrBackground = GetSysColorBrush(COLOR_WINDOW);
3023 ed->bCaretAtEnd = FALSE;
3024 ed->nEventMask = 0;
3025 ed->nModifyStep = 0;
3026 ed->nTextLimit = TEXT_LIMIT_DEFAULT;
3027 list_init( &ed->undo_stack );
3028 list_init( &ed->redo_stack );
3029 ed->nUndoStackSize = 0;
3030 ed->nUndoLimit = STACK_SIZE_DEFAULT;
3031 ed->nUndoMode = umAddToUndo;
3032 ed->nParagraphs = 1;
3033 ed->nLastSelStart = ed->nLastSelEnd = 0;
3034 ed->pLastSelStartPara = ed->pLastSelEndPara = ed->pCursors[0].pPara;
3035 ed->bHideSelection = FALSE;
3036 ed->pfnWordBreak = NULL;
3037 ed->lpOleCallback = NULL;
3038 ed->mode = TM_MULTILEVELUNDO | TM_MULTICODEPAGE;
3039 ed->mode |= (props & TXTBIT_RICHTEXT) ? TM_RICHTEXT : TM_PLAINTEXT;
3040 ed->AutoURLDetect_bEnable = FALSE;
3041 ed->bHaveFocus = FALSE;
3042 ed->bDialogMode = FALSE;
3043 ed->bMouseCaptured = FALSE;
3044 for (i=0; i<HFONT_CACHE_SIZE; i++)
3046 ed->pFontCache[i].nRefs = 0;
3047 ed->pFontCache[i].nAge = 0;
3048 ed->pFontCache[i].hFont = NULL;
3051 ME_CheckCharOffsets(ed);
3052 SetRectEmpty(&ed->rcFormat);
3053 ed->bDefaultFormatRect = TRUE;
3054 ITextHost_TxGetSelectionBarWidth(ed->texthost, &selbarwidth);
3055 if (selbarwidth) {
3056 /* FIXME: Convert selbarwidth from HIMETRIC to pixels */
3057 ed->selofs = SELECTIONBAR_WIDTH;
3058 ed->styleFlags |= ES_SELECTIONBAR;
3059 } else {
3060 ed->selofs = 0;
3062 ed->nSelectionType = stPosition;
3064 ed->cPasswordMask = 0;
3065 if (props & TXTBIT_USEPASSWORD)
3066 ITextHost_TxGetPasswordChar(texthost, &ed->cPasswordMask);
3068 if (props & TXTBIT_AUTOWORDSEL)
3069 ed->styleFlags |= ECO_AUTOWORDSELECTION;
3070 if (props & TXTBIT_MULTILINE) {
3071 ed->styleFlags |= ES_MULTILINE;
3072 ed->bWordWrap = (props & TXTBIT_WORDWRAP) != 0;
3073 } else {
3074 ed->bWordWrap = FALSE;
3076 if (props & TXTBIT_READONLY)
3077 ed->styleFlags |= ES_READONLY;
3078 if (!(props & TXTBIT_HIDESELECTION))
3079 ed->styleFlags |= ES_NOHIDESEL;
3080 if (props & TXTBIT_SAVESELECTION)
3081 ed->styleFlags |= ES_SAVESEL;
3082 if (props & TXTBIT_VERTICAL)
3083 ed->styleFlags |= ES_VERTICAL;
3084 if (props & TXTBIT_DISABLEDRAG)
3085 ed->styleFlags |= ES_NOOLEDRAGDROP;
3087 ed->notified_cr.cpMin = ed->notified_cr.cpMax = 0;
3089 /* Default scrollbar information */
3090 ed->vert_si.cbSize = sizeof(SCROLLINFO);
3091 ed->vert_si.nMin = 0;
3092 ed->vert_si.nMax = 0;
3093 ed->vert_si.nPage = 0;
3094 ed->vert_si.nPos = 0;
3096 ed->horz_si.cbSize = sizeof(SCROLLINFO);
3097 ed->horz_si.nMin = 0;
3098 ed->horz_si.nMax = 0;
3099 ed->horz_si.nPage = 0;
3100 ed->horz_si.nPos = 0;
3102 ed->wheel_remain = 0;
3104 list_init( &ed->style_list );
3105 OleInitialize(NULL);
3107 return ed;
3110 void ME_DestroyEditor(ME_TextEditor *editor)
3112 ME_DisplayItem *pFirst = editor->pBuffer->pFirst;
3113 ME_DisplayItem *p = pFirst, *pNext = NULL;
3114 ME_Style *s, *cursor2;
3115 int i;
3117 ME_ClearTempStyle(editor);
3118 ME_EmptyUndoStack(editor);
3119 while(p) {
3120 pNext = p->next;
3121 ME_DestroyDisplayItem(p);
3122 p = pNext;
3125 LIST_FOR_EACH_ENTRY_SAFE( s, cursor2, &editor->style_list, ME_Style, entry )
3126 ME_DestroyStyle( s );
3128 ME_ReleaseStyle(editor->pBuffer->pDefaultStyle);
3129 for (i=0; i<HFONT_CACHE_SIZE; i++)
3131 if (editor->pFontCache[i].hFont)
3132 DeleteObject(editor->pFontCache[i].hFont);
3134 if (editor->rgbBackColor != -1)
3135 DeleteObject(editor->hbrBackground);
3136 if(editor->lpOleCallback)
3137 IRichEditOleCallback_Release(editor->lpOleCallback);
3138 ITextHost_Release(editor->texthost);
3139 if (editor->reOle)
3141 IRichEditOle_Release(editor->reOle);
3142 editor->reOle = NULL;
3144 OleUninitialize();
3146 FREE_OBJ(editor->pBuffer);
3147 FREE_OBJ(editor->pCursors);
3149 FREE_OBJ(editor);
3152 BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
3154 TRACE("\n");
3155 switch (fdwReason)
3157 case DLL_PROCESS_ATTACH:
3158 DisableThreadLibraryCalls(hinstDLL);
3159 me_heap = HeapCreate (0, 0x10000, 0);
3160 if (!ME_RegisterEditorClass(hinstDLL)) return FALSE;
3161 hLeft = LoadCursorW(hinstDLL, MAKEINTRESOURCEW(OCR_REVERSE));
3162 LookupInit();
3163 break;
3165 case DLL_PROCESS_DETACH:
3166 if (lpvReserved) break;
3167 UnregisterClassW(RICHEDIT_CLASS20W, 0);
3168 UnregisterClassW(MSFTEDIT_CLASS, 0);
3169 UnregisterClassA(RICHEDIT_CLASS20A, 0);
3170 UnregisterClassA("RichEdit50A", 0);
3171 if (ME_ListBoxRegistered)
3172 UnregisterClassW(REListBox20W, 0);
3173 if (ME_ComboBoxRegistered)
3174 UnregisterClassW(REComboBox20W, 0);
3175 LookupCleanup();
3176 HeapDestroy (me_heap);
3177 release_typelib();
3178 break;
3180 return TRUE;
3183 static inline int get_default_line_height( ME_TextEditor *editor )
3185 int height = 0;
3187 if (editor->pBuffer && editor->pBuffer->pDefaultStyle)
3188 height = editor->pBuffer->pDefaultStyle->tm.tmHeight;
3189 if (height <= 0) height = 24;
3191 return height;
3194 static inline int calc_wheel_change( int *remain, int amount_per_click )
3196 int change = amount_per_click * (float)*remain / WHEEL_DELTA;
3197 *remain -= WHEEL_DELTA * change / amount_per_click;
3198 return change;
3201 static const char * const edit_messages[] = {
3202 "EM_GETSEL",
3203 "EM_SETSEL",
3204 "EM_GETRECT",
3205 "EM_SETRECT",
3206 "EM_SETRECTNP",
3207 "EM_SCROLL",
3208 "EM_LINESCROLL",
3209 "EM_SCROLLCARET",
3210 "EM_GETMODIFY",
3211 "EM_SETMODIFY",
3212 "EM_GETLINECOUNT",
3213 "EM_LINEINDEX",
3214 "EM_SETHANDLE",
3215 "EM_GETHANDLE",
3216 "EM_GETTHUMB",
3217 "EM_UNKNOWN_BF",
3218 "EM_UNKNOWN_C0",
3219 "EM_LINELENGTH",
3220 "EM_REPLACESEL",
3221 "EM_UNKNOWN_C3",
3222 "EM_GETLINE",
3223 "EM_LIMITTEXT",
3224 "EM_CANUNDO",
3225 "EM_UNDO",
3226 "EM_FMTLINES",
3227 "EM_LINEFROMCHAR",
3228 "EM_UNKNOWN_CA",
3229 "EM_SETTABSTOPS",
3230 "EM_SETPASSWORDCHAR",
3231 "EM_EMPTYUNDOBUFFER",
3232 "EM_GETFIRSTVISIBLELINE",
3233 "EM_SETREADONLY",
3234 "EM_SETWORDBREAKPROC",
3235 "EM_GETWORDBREAKPROC",
3236 "EM_GETPASSWORDCHAR",
3237 "EM_SETMARGINS",
3238 "EM_GETMARGINS",
3239 "EM_GETLIMITTEXT",
3240 "EM_POSFROMCHAR",
3241 "EM_CHARFROMPOS",
3242 "EM_SETIMESTATUS",
3243 "EM_GETIMESTATUS"
3246 static const char * const richedit_messages[] = {
3247 "EM_CANPASTE",
3248 "EM_DISPLAYBAND",
3249 "EM_EXGETSEL",
3250 "EM_EXLIMITTEXT",
3251 "EM_EXLINEFROMCHAR",
3252 "EM_EXSETSEL",
3253 "EM_FINDTEXT",
3254 "EM_FORMATRANGE",
3255 "EM_GETCHARFORMAT",
3256 "EM_GETEVENTMASK",
3257 "EM_GETOLEINTERFACE",
3258 "EM_GETPARAFORMAT",
3259 "EM_GETSELTEXT",
3260 "EM_HIDESELECTION",
3261 "EM_PASTESPECIAL",
3262 "EM_REQUESTRESIZE",
3263 "EM_SELECTIONTYPE",
3264 "EM_SETBKGNDCOLOR",
3265 "EM_SETCHARFORMAT",
3266 "EM_SETEVENTMASK",
3267 "EM_SETOLECALLBACK",
3268 "EM_SETPARAFORMAT",
3269 "EM_SETTARGETDEVICE",
3270 "EM_STREAMIN",
3271 "EM_STREAMOUT",
3272 "EM_GETTEXTRANGE",
3273 "EM_FINDWORDBREAK",
3274 "EM_SETOPTIONS",
3275 "EM_GETOPTIONS",
3276 "EM_FINDTEXTEX",
3277 "EM_GETWORDBREAKPROCEX",
3278 "EM_SETWORDBREAKPROCEX",
3279 "EM_SETUNDOLIMIT",
3280 "EM_UNKNOWN_USER_83",
3281 "EM_REDO",
3282 "EM_CANREDO",
3283 "EM_GETUNDONAME",
3284 "EM_GETREDONAME",
3285 "EM_STOPGROUPTYPING",
3286 "EM_SETTEXTMODE",
3287 "EM_GETTEXTMODE",
3288 "EM_AUTOURLDETECT",
3289 "EM_GETAUTOURLDETECT",
3290 "EM_SETPALETTE",
3291 "EM_GETTEXTEX",
3292 "EM_GETTEXTLENGTHEX",
3293 "EM_SHOWSCROLLBAR",
3294 "EM_SETTEXTEX",
3295 "EM_UNKNOWN_USER_98",
3296 "EM_UNKNOWN_USER_99",
3297 "EM_SETPUNCTUATION",
3298 "EM_GETPUNCTUATION",
3299 "EM_SETWORDWRAPMODE",
3300 "EM_GETWORDWRAPMODE",
3301 "EM_SETIMECOLOR",
3302 "EM_GETIMECOLOR",
3303 "EM_SETIMEOPTIONS",
3304 "EM_GETIMEOPTIONS",
3305 "EM_CONVPOSITION",
3306 "EM_UNKNOWN_USER_109",
3307 "EM_UNKNOWN_USER_110",
3308 "EM_UNKNOWN_USER_111",
3309 "EM_UNKNOWN_USER_112",
3310 "EM_UNKNOWN_USER_113",
3311 "EM_UNKNOWN_USER_114",
3312 "EM_UNKNOWN_USER_115",
3313 "EM_UNKNOWN_USER_116",
3314 "EM_UNKNOWN_USER_117",
3315 "EM_UNKNOWN_USER_118",
3316 "EM_UNKNOWN_USER_119",
3317 "EM_SETLANGOPTIONS",
3318 "EM_GETLANGOPTIONS",
3319 "EM_GETIMECOMPMODE",
3320 "EM_FINDTEXTW",
3321 "EM_FINDTEXTEXW",
3322 "EM_RECONVERSION",
3323 "EM_SETIMEMODEBIAS",
3324 "EM_GETIMEMODEBIAS"
3327 static const char *
3328 get_msg_name(UINT msg)
3330 if (msg >= EM_GETSEL && msg <= EM_CHARFROMPOS)
3331 return edit_messages[msg - EM_GETSEL];
3332 if (msg >= EM_CANPASTE && msg <= EM_GETIMEMODEBIAS)
3333 return richedit_messages[msg - EM_CANPASTE];
3334 return "";
3337 static void ME_LinkNotify(ME_TextEditor *editor, UINT msg, WPARAM wParam, LPARAM lParam)
3339 int x,y;
3340 BOOL isExact;
3341 ME_Cursor cursor; /* The start of the clicked text. */
3343 ENLINK info;
3344 x = (short)LOWORD(lParam);
3345 y = (short)HIWORD(lParam);
3346 ME_CharFromPos(editor, x, y, &cursor, &isExact);
3347 if (!isExact) return;
3349 if (is_link( &cursor.pRun->member.run ))
3350 { /* The clicked run has CFE_LINK set */
3351 ME_DisplayItem *di;
3353 info.nmhdr.hwndFrom = NULL;
3354 info.nmhdr.idFrom = 0;
3355 info.nmhdr.code = EN_LINK;
3356 info.msg = msg;
3357 info.wParam = wParam;
3358 info.lParam = lParam;
3359 cursor.nOffset = 0;
3361 /* find the first contiguous run with CFE_LINK set */
3362 info.chrg.cpMin = ME_GetCursorOfs(&cursor);
3363 di = cursor.pRun;
3364 while (ME_PrevRun( NULL, &di, FALSE ) && is_link( &di->member.run ))
3365 info.chrg.cpMin -= di->member.run.len;
3367 /* find the last contiguous run with CFE_LINK set */
3368 info.chrg.cpMax = ME_GetCursorOfs(&cursor) + cursor.pRun->member.run.len;
3369 di = cursor.pRun;
3370 while (ME_NextRun( NULL, &di, FALSE ) && is_link( &di->member.run ))
3371 info.chrg.cpMax += di->member.run.len;
3373 ITextHost_TxNotify(editor->texthost, info.nmhdr.code, &info);
3377 void ME_ReplaceSel(ME_TextEditor *editor, BOOL can_undo, const WCHAR *str, int len)
3379 int from, to, nStartCursor;
3380 ME_Style *style;
3382 nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3383 style = ME_GetSelectionInsertStyle(editor);
3384 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to-from, FALSE);
3385 ME_InsertTextFromCursor(editor, 0, str, len, style);
3386 ME_ReleaseStyle(style);
3387 /* drop temporary style if line end */
3389 * FIXME question: does abc\n mean: put abc,
3390 * clear temp style, put \n? (would require a change)
3392 if (len>0 && str[len-1] == '\n')
3393 ME_ClearTempStyle(editor);
3394 ME_CommitUndo(editor);
3395 ME_UpdateSelectionLinkAttribute(editor);
3396 if (!can_undo)
3397 ME_EmptyUndoStack(editor);
3398 ME_UpdateRepaint(editor, FALSE);
3401 static void ME_SetText(ME_TextEditor *editor, void *text, BOOL unicode)
3403 LONG codepage = unicode ? CP_UNICODE : CP_ACP;
3404 int textLen;
3406 LPWSTR wszText = ME_ToUnicode(codepage, text, &textLen);
3408 if (textLen > 0)
3410 int len = -1;
3412 /* uses default style! */
3413 if (!(editor->styleFlags & ES_MULTILINE))
3415 WCHAR *p = wszText;
3417 while (*p != '\0' && *p != '\r' && *p != '\n') p++;
3418 len = p - wszText;
3420 ME_InsertTextFromCursor(editor, 0, wszText, len, editor->pBuffer->pDefaultStyle);
3422 ME_EndToUnicode(codepage, wszText);
3425 static LRESULT ME_WmCreate(ME_TextEditor *editor, LPARAM lParam, BOOL unicode)
3427 CREATESTRUCTW *createW = (CREATESTRUCTW*)lParam;
3428 CREATESTRUCTA *createA = (CREATESTRUCTA*)lParam;
3429 void *text = NULL;
3430 INT max;
3432 if (lParam)
3433 text = unicode ? (void*)createW->lpszName : (void*)createA->lpszName;
3435 ME_SetDefaultFormatRect(editor);
3437 max = (editor->styleFlags & ES_DISABLENOSCROLL) ? 1 : 0;
3438 if (~editor->styleFlags & ES_DISABLENOSCROLL || editor->styleFlags & WS_VSCROLL)
3439 ITextHost_TxSetScrollRange(editor->texthost, SB_VERT, 0, max, TRUE);
3441 if (~editor->styleFlags & ES_DISABLENOSCROLL || editor->styleFlags & WS_HSCROLL)
3442 ITextHost_TxSetScrollRange(editor->texthost, SB_HORZ, 0, max, TRUE);
3444 if (editor->styleFlags & ES_DISABLENOSCROLL)
3446 if (editor->styleFlags & WS_VSCROLL)
3448 ITextHost_TxEnableScrollBar(editor->texthost, SB_VERT, ESB_DISABLE_BOTH);
3449 ITextHost_TxShowScrollBar(editor->texthost, SB_VERT, TRUE);
3451 if (editor->styleFlags & WS_HSCROLL)
3453 ITextHost_TxEnableScrollBar(editor->texthost, SB_HORZ, ESB_DISABLE_BOTH);
3454 ITextHost_TxShowScrollBar(editor->texthost, SB_HORZ, TRUE);
3458 if (text)
3460 ME_SetText(editor, text, unicode);
3461 ME_SetCursorToStart(editor, &editor->pCursors[0]);
3462 ME_SetCursorToStart(editor, &editor->pCursors[1]);
3465 ME_CommitUndo(editor);
3466 ME_WrapMarkedParagraphs(editor);
3467 ME_MoveCaret(editor);
3468 return 0;
3472 #define UNSUPPORTED_MSG(e) \
3473 case e: \
3474 FIXME(#e ": stub\n"); \
3475 *phresult = S_FALSE; \
3476 return 0;
3478 /* Handle messages for windowless and windowed richedit controls.
3480 * The LRESULT that is returned is a return value for window procs,
3481 * and the phresult parameter is the COM return code needed by the
3482 * text services interface. */
3483 LRESULT ME_HandleMessage(ME_TextEditor *editor, UINT msg, WPARAM wParam,
3484 LPARAM lParam, BOOL unicode, HRESULT* phresult)
3486 *phresult = S_OK;
3488 switch(msg) {
3490 UNSUPPORTED_MSG(EM_DISPLAYBAND)
3491 UNSUPPORTED_MSG(EM_FINDWORDBREAK)
3492 UNSUPPORTED_MSG(EM_FMTLINES)
3493 UNSUPPORTED_MSG(EM_FORMATRANGE)
3494 UNSUPPORTED_MSG(EM_GETBIDIOPTIONS)
3495 UNSUPPORTED_MSG(EM_GETEDITSTYLE)
3496 UNSUPPORTED_MSG(EM_GETIMECOMPMODE)
3497 UNSUPPORTED_MSG(EM_GETIMESTATUS)
3498 UNSUPPORTED_MSG(EM_SETIMESTATUS)
3499 UNSUPPORTED_MSG(EM_GETLANGOPTIONS)
3500 UNSUPPORTED_MSG(EM_GETREDONAME)
3501 UNSUPPORTED_MSG(EM_GETTYPOGRAPHYOPTIONS)
3502 UNSUPPORTED_MSG(EM_GETUNDONAME)
3503 UNSUPPORTED_MSG(EM_GETWORDBREAKPROCEX)
3504 UNSUPPORTED_MSG(EM_SELECTIONTYPE)
3505 UNSUPPORTED_MSG(EM_SETBIDIOPTIONS)
3506 UNSUPPORTED_MSG(EM_SETEDITSTYLE)
3507 UNSUPPORTED_MSG(EM_SETLANGOPTIONS)
3508 UNSUPPORTED_MSG(EM_SETMARGINS)
3509 UNSUPPORTED_MSG(EM_SETPALETTE)
3510 UNSUPPORTED_MSG(EM_SETTABSTOPS)
3511 UNSUPPORTED_MSG(EM_SETTYPOGRAPHYOPTIONS)
3512 UNSUPPORTED_MSG(EM_SETWORDBREAKPROCEX)
3514 /* Messages specific to Richedit controls */
3516 case EM_STREAMIN:
3517 return ME_StreamIn(editor, wParam, (EDITSTREAM*)lParam, TRUE);
3518 case EM_STREAMOUT:
3519 return ME_StreamOut(editor, wParam, (EDITSTREAM *)lParam);
3520 case WM_GETDLGCODE:
3522 UINT code = DLGC_WANTCHARS|DLGC_WANTTAB|DLGC_WANTARROWS;
3524 if (lParam)
3525 editor->bDialogMode = TRUE;
3526 if (editor->styleFlags & ES_MULTILINE)
3527 code |= DLGC_WANTMESSAGE;
3528 if (!(editor->styleFlags & ES_SAVESEL))
3529 code |= DLGC_HASSETSEL;
3530 return code;
3532 case EM_EMPTYUNDOBUFFER:
3533 ME_EmptyUndoStack(editor);
3534 return 0;
3535 case EM_GETSEL:
3537 /* Note: wParam/lParam can be NULL */
3538 UINT from, to;
3539 PUINT pfrom = wParam ? (PUINT)wParam : &from;
3540 PUINT pto = lParam ? (PUINT)lParam : &to;
3541 ME_GetSelectionOfs(editor, (int *)pfrom, (int *)pto);
3542 if ((*pfrom|*pto) & 0xFFFF0000)
3543 return -1;
3544 return MAKELONG(*pfrom,*pto);
3546 case EM_EXGETSEL:
3548 CHARRANGE *pRange = (CHARRANGE *)lParam;
3549 ME_GetSelectionOfs(editor, &pRange->cpMin, &pRange->cpMax);
3550 TRACE("EM_EXGETSEL = (%d,%d)\n", pRange->cpMin, pRange->cpMax);
3551 return 0;
3553 case EM_SETUNDOLIMIT:
3555 if ((int)wParam < 0)
3556 editor->nUndoLimit = STACK_SIZE_DEFAULT;
3557 else
3558 editor->nUndoLimit = min(wParam, STACK_SIZE_MAX);
3559 /* Setting a max stack size keeps wine from getting killed
3560 for hogging memory. Windows allocates all this memory at once, so
3561 no program would realistically set a value above our maximum. */
3562 return editor->nUndoLimit;
3564 case EM_CANUNDO:
3565 return !list_empty( &editor->undo_stack );
3566 case EM_CANREDO:
3567 return !list_empty( &editor->redo_stack );
3568 case WM_UNDO: /* FIXME: actually not the same */
3569 case EM_UNDO:
3570 return ME_Undo(editor);
3571 case EM_REDO:
3572 return ME_Redo(editor);
3573 case EM_GETOPTIONS:
3575 /* these flags are equivalent to the ES_* counterparts */
3576 DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
3577 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN | ECO_SELECTIONBAR;
3578 DWORD settings = editor->styleFlags & mask;
3580 return settings;
3582 case EM_SETFONTSIZE:
3584 CHARFORMAT2W cf;
3585 LONG tmp_size, size;
3586 BOOL is_increase = ((LONG)wParam > 0);
3588 if (editor->mode & TM_PLAINTEXT)
3589 return FALSE;
3591 cf.cbSize = sizeof(cf);
3592 cf.dwMask = CFM_SIZE;
3593 ME_GetSelectionCharFormat(editor, &cf);
3594 tmp_size = (cf.yHeight / 20) + wParam;
3596 if (tmp_size <= 1)
3597 size = 1;
3598 else if (tmp_size > 12 && tmp_size < 28 && tmp_size % 2)
3599 size = tmp_size + (is_increase ? 1 : -1);
3600 else if (tmp_size > 28 && tmp_size < 36)
3601 size = is_increase ? 36 : 28;
3602 else if (tmp_size > 36 && tmp_size < 48)
3603 size = is_increase ? 48 : 36;
3604 else if (tmp_size > 48 && tmp_size < 72)
3605 size = is_increase ? 72 : 48;
3606 else if (tmp_size > 72 && tmp_size < 80)
3607 size = is_increase ? 80 : 72;
3608 else if (tmp_size > 80 && tmp_size < 1638)
3609 size = 10 * (is_increase ? (tmp_size / 10 + 1) : (tmp_size / 10));
3610 else if (tmp_size >= 1638)
3611 size = 1638;
3612 else
3613 size = tmp_size;
3615 cf.yHeight = size * 20; /* convert twips to points */
3616 ME_SetSelectionCharFormat(editor, &cf);
3617 ME_CommitUndo(editor);
3618 ME_WrapMarkedParagraphs(editor);
3619 ME_UpdateScrollBar(editor);
3620 ME_Repaint(editor);
3622 return TRUE;
3624 case EM_SETOPTIONS:
3626 /* these flags are equivalent to ES_* counterparts, except for
3627 * ECO_AUTOWORDSELECTION that doesn't have an ES_* counterpart,
3628 * but is still stored in editor->styleFlags. */
3629 const DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
3630 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN |
3631 ECO_SELECTIONBAR | ECO_AUTOWORDSELECTION;
3632 DWORD settings = mask & editor->styleFlags;
3633 DWORD oldSettings = settings;
3634 DWORD changedSettings;
3636 switch(wParam)
3638 case ECOOP_SET:
3639 settings = lParam;
3640 break;
3641 case ECOOP_OR:
3642 settings |= lParam;
3643 break;
3644 case ECOOP_AND:
3645 settings &= lParam;
3646 break;
3647 case ECOOP_XOR:
3648 settings ^= lParam;
3650 changedSettings = oldSettings ^ settings;
3652 if (changedSettings) {
3653 editor->styleFlags = (editor->styleFlags & ~mask) | (settings & mask);
3655 if (changedSettings & ECO_SELECTIONBAR)
3657 ITextHost_TxInvalidateRect(editor->texthost, &editor->rcFormat, TRUE);
3658 if (settings & ECO_SELECTIONBAR) {
3659 assert(!editor->selofs);
3660 editor->selofs = SELECTIONBAR_WIDTH;
3661 editor->rcFormat.left += editor->selofs;
3662 } else {
3663 editor->rcFormat.left -= editor->selofs;
3664 editor->selofs = 0;
3666 ME_RewrapRepaint(editor);
3669 if ((changedSettings & settings & ES_NOHIDESEL) && !editor->bHaveFocus)
3670 ME_InvalidateSelection( editor );
3672 if (changedSettings & settings & ECO_VERTICAL)
3673 FIXME("ECO_VERTICAL not implemented yet!\n");
3674 if (changedSettings & settings & ECO_AUTOHSCROLL)
3675 FIXME("ECO_AUTOHSCROLL not implemented yet!\n");
3676 if (changedSettings & settings & ECO_AUTOVSCROLL)
3677 FIXME("ECO_AUTOVSCROLL not implemented yet!\n");
3678 if (changedSettings & settings & ECO_WANTRETURN)
3679 FIXME("ECO_WANTRETURN not implemented yet!\n");
3680 if (changedSettings & settings & ECO_AUTOWORDSELECTION)
3681 FIXME("ECO_AUTOWORDSELECTION not implemented yet!\n");
3684 return settings;
3686 case EM_SETSEL:
3688 return handle_EM_EXSETSEL( editor, wParam, lParam );
3690 case EM_SETSCROLLPOS:
3692 POINT *point = (POINT *)lParam;
3693 ME_ScrollAbs(editor, point->x, point->y);
3694 return 0;
3696 case EM_AUTOURLDETECT:
3698 if (wParam==1 || wParam ==0)
3700 editor->AutoURLDetect_bEnable = (BOOL)wParam;
3701 return 0;
3703 return E_INVALIDARG;
3705 case EM_GETAUTOURLDETECT:
3707 return editor->AutoURLDetect_bEnable;
3709 case EM_EXSETSEL:
3711 CHARRANGE range = *(CHARRANGE *)lParam;
3713 return handle_EM_EXSETSEL( editor, range.cpMin, range.cpMax );
3715 case EM_SHOWSCROLLBAR:
3717 DWORD flags;
3719 switch (wParam)
3721 case SB_HORZ:
3722 flags = WS_HSCROLL;
3723 break;
3724 case SB_VERT:
3725 flags = WS_VSCROLL;
3726 break;
3727 case SB_BOTH:
3728 flags = WS_HSCROLL|WS_VSCROLL;
3729 break;
3730 default:
3731 return 0;
3734 if (lParam) {
3735 editor->styleFlags |= flags;
3736 if (flags & WS_HSCROLL)
3737 ITextHost_TxShowScrollBar(editor->texthost, SB_HORZ,
3738 editor->nTotalWidth > editor->sizeWindow.cx);
3739 if (flags & WS_VSCROLL)
3740 ITextHost_TxShowScrollBar(editor->texthost, SB_VERT,
3741 editor->nTotalLength > editor->sizeWindow.cy);
3742 } else {
3743 editor->styleFlags &= ~flags;
3744 ITextHost_TxShowScrollBar(editor->texthost, wParam, FALSE);
3746 return 0;
3748 case EM_SETTEXTEX:
3750 LPWSTR wszText;
3751 SETTEXTEX *pStruct = (SETTEXTEX *)wParam;
3752 int from, to, len;
3753 ME_Style *style;
3754 BOOL bRtf, bUnicode, bSelection, bUTF8;
3755 int oldModify = editor->nModifyStep;
3756 static const char utf8_bom[] = {0xef, 0xbb, 0xbf};
3758 if (!pStruct) return 0;
3760 /* If we detect ascii rtf at the start of the string,
3761 * we know it isn't unicode. */
3762 bRtf = (lParam && (!strncmp((char *)lParam, "{\\rtf", 5) ||
3763 !strncmp((char *)lParam, "{\\urtf", 6)));
3764 bUnicode = !bRtf && pStruct->codepage == CP_UNICODE;
3765 bUTF8 = (lParam && (!strncmp((char *)lParam, utf8_bom, 3)));
3767 TRACE("EM_SETTEXTEX - %s, flags %d, cp %d\n",
3768 bUnicode ? debugstr_w((LPCWSTR)lParam) : debugstr_a((LPCSTR)lParam),
3769 pStruct->flags, pStruct->codepage);
3771 bSelection = (pStruct->flags & ST_SELECTION) != 0;
3772 if (bSelection) {
3773 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3774 style = ME_GetSelectionInsertStyle(editor);
3775 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to - from, FALSE);
3776 } else {
3777 ME_Cursor start;
3778 ME_SetCursorToStart(editor, &start);
3779 ME_InternalDeleteText(editor, &start, ME_GetTextLength(editor), FALSE);
3780 style = editor->pBuffer->pDefaultStyle;
3783 if (bRtf) {
3784 ME_StreamInRTFString(editor, bSelection, (char *)lParam);
3785 if (bSelection) {
3786 /* FIXME: The length returned doesn't include the rtf control
3787 * characters, only the actual text. */
3788 len = lParam ? strlen((char *)lParam) : 0;
3790 } else {
3791 if (bUTF8 && !bUnicode) {
3792 wszText = ME_ToUnicode(CP_UTF8, (void *)(lParam+3), &len);
3793 ME_InsertTextFromCursor(editor, 0, wszText, len, style);
3794 ME_EndToUnicode(CP_UTF8, wszText);
3795 } else {
3796 wszText = ME_ToUnicode(pStruct->codepage, (void *)lParam, &len);
3797 ME_InsertTextFromCursor(editor, 0, wszText, len, style);
3798 ME_EndToUnicode(pStruct->codepage, wszText);
3802 if (bSelection) {
3803 ME_ReleaseStyle(style);
3804 ME_UpdateSelectionLinkAttribute(editor);
3805 } else {
3806 ME_Cursor cursor;
3807 len = 1;
3808 ME_SetCursorToStart(editor, &cursor);
3809 ME_UpdateLinkAttribute(editor, &cursor, INT_MAX);
3811 ME_CommitUndo(editor);
3812 if (!(pStruct->flags & ST_KEEPUNDO))
3814 editor->nModifyStep = oldModify;
3815 ME_EmptyUndoStack(editor);
3817 ME_UpdateRepaint(editor, FALSE);
3818 return len;
3820 case EM_SETBKGNDCOLOR:
3822 LRESULT lColor;
3823 if (editor->rgbBackColor != -1) {
3824 DeleteObject(editor->hbrBackground);
3825 lColor = editor->rgbBackColor;
3827 else lColor = ITextHost_TxGetSysColor(editor->texthost, COLOR_WINDOW);
3829 if (wParam)
3831 editor->rgbBackColor = -1;
3832 editor->hbrBackground = GetSysColorBrush(COLOR_WINDOW);
3834 else
3836 editor->rgbBackColor = lParam;
3837 editor->hbrBackground = CreateSolidBrush(editor->rgbBackColor);
3839 ITextHost_TxInvalidateRect(editor->texthost, NULL, TRUE);
3840 return lColor;
3842 case EM_GETMODIFY:
3843 return editor->nModifyStep == 0 ? 0 : -1;
3844 case EM_SETMODIFY:
3846 if (wParam)
3847 editor->nModifyStep = 1;
3848 else
3849 editor->nModifyStep = 0;
3851 return 0;
3853 case EM_SETREADONLY:
3855 if (wParam)
3856 editor->styleFlags |= ES_READONLY;
3857 else
3858 editor->styleFlags &= ~ES_READONLY;
3859 return 1;
3861 case EM_SETEVENTMASK:
3863 DWORD nOldMask = editor->nEventMask;
3865 editor->nEventMask = lParam;
3866 return nOldMask;
3868 case EM_GETEVENTMASK:
3869 return editor->nEventMask;
3870 case EM_SETCHARFORMAT:
3872 CHARFORMAT2W buf, *p;
3873 BOOL bRepaint = TRUE;
3874 p = ME_ToCF2W(&buf, (CHARFORMAT2W *)lParam);
3875 if (p == NULL) return 0;
3876 if (wParam & SCF_ALL) {
3877 if (editor->mode & TM_PLAINTEXT) {
3878 ME_SetDefaultCharFormat(editor, p);
3879 } else {
3880 ME_Cursor start;
3881 ME_SetCursorToStart(editor, &start);
3882 ME_SetCharFormat(editor, &start, NULL, p);
3883 editor->nModifyStep = 1;
3885 } else if (wParam & SCF_SELECTION) {
3886 if (editor->mode & TM_PLAINTEXT)
3887 return 0;
3888 if (wParam & SCF_WORD) {
3889 ME_Cursor start;
3890 ME_Cursor end = editor->pCursors[0];
3891 ME_MoveCursorWords(editor, &end, +1);
3892 start = end;
3893 ME_MoveCursorWords(editor, &start, -1);
3894 ME_SetCharFormat(editor, &start, &end, p);
3896 bRepaint = ME_IsSelection(editor);
3897 ME_SetSelectionCharFormat(editor, p);
3898 if (bRepaint) editor->nModifyStep = 1;
3899 } else { /* SCF_DEFAULT */
3900 ME_SetDefaultCharFormat(editor, p);
3902 ME_CommitUndo(editor);
3903 if (bRepaint)
3905 ME_WrapMarkedParagraphs(editor);
3906 ME_UpdateScrollBar(editor);
3907 ME_Repaint(editor);
3909 return 1;
3911 case EM_GETCHARFORMAT:
3913 CHARFORMAT2W tmp, *dst = (CHARFORMAT2W *)lParam;
3914 if (dst->cbSize != sizeof(CHARFORMATA) &&
3915 dst->cbSize != sizeof(CHARFORMATW) &&
3916 dst->cbSize != sizeof(CHARFORMAT2A) &&
3917 dst->cbSize != sizeof(CHARFORMAT2W))
3918 return 0;
3919 tmp.cbSize = sizeof(tmp);
3920 if (!wParam)
3921 ME_GetDefaultCharFormat(editor, &tmp);
3922 else
3923 ME_GetSelectionCharFormat(editor, &tmp);
3924 ME_CopyToCFAny(dst, &tmp);
3925 return tmp.dwMask;
3927 case EM_SETPARAFORMAT:
3929 BOOL result = ME_SetSelectionParaFormat(editor, (PARAFORMAT2 *)lParam);
3930 ME_WrapMarkedParagraphs(editor);
3931 ME_UpdateScrollBar(editor);
3932 ME_Repaint(editor);
3933 ME_CommitUndo(editor);
3934 return result;
3936 case EM_GETPARAFORMAT:
3937 ME_GetSelectionParaFormat(editor, (PARAFORMAT2 *)lParam);
3938 return ((PARAFORMAT2 *)lParam)->dwMask;
3939 case EM_GETFIRSTVISIBLELINE:
3941 ME_DisplayItem *p = editor->pBuffer->pFirst;
3942 int y = editor->vert_si.nPos;
3943 int ypara = 0;
3944 int count = 0;
3945 int ystart, yend;
3946 while(p) {
3947 p = ME_FindItemFwd(p, diStartRowOrParagraphOrEnd);
3948 if (p->type == diTextEnd)
3949 break;
3950 if (p->type == diParagraph) {
3951 ypara = p->member.para.pt.y;
3952 continue;
3954 ystart = ypara + p->member.row.pt.y;
3955 yend = ystart + p->member.row.nHeight;
3956 if (y < yend) {
3957 break;
3959 count++;
3961 return count;
3963 case EM_HIDESELECTION:
3965 editor->bHideSelection = (wParam != 0);
3966 ME_InvalidateSelection(editor);
3967 return 0;
3969 case EM_LINESCROLL:
3971 if (!(editor->styleFlags & ES_MULTILINE))
3972 return FALSE;
3973 ME_ScrollDown( editor, lParam * get_default_line_height( editor ) );
3974 return TRUE;
3976 case WM_CLEAR:
3978 int from, to;
3979 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3980 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to-from, FALSE);
3981 ME_CommitUndo(editor);
3982 ME_UpdateRepaint(editor, TRUE);
3983 return 0;
3985 case EM_REPLACESEL:
3987 int len = 0;
3988 LONG codepage = unicode ? CP_UNICODE : CP_ACP;
3989 LPWSTR wszText = ME_ToUnicode(codepage, (void *)lParam, &len);
3991 TRACE("EM_REPLACESEL - %s\n", debugstr_w(wszText));
3993 ME_ReplaceSel(editor, !!wParam, wszText, len);
3994 ME_EndToUnicode(codepage, wszText);
3995 return len;
3997 case EM_SCROLLCARET:
3998 ME_EnsureVisible(editor, &editor->pCursors[0]);
3999 return 0;
4000 case WM_SETFONT:
4002 LOGFONTW lf;
4003 CHARFORMAT2W fmt;
4004 HDC hDC;
4005 BOOL bRepaint = LOWORD(lParam);
4007 if (!wParam)
4008 wParam = (WPARAM)GetStockObject(SYSTEM_FONT);
4010 if (!GetObjectW((HGDIOBJ)wParam, sizeof(LOGFONTW), &lf))
4011 return 0;
4013 hDC = ITextHost_TxGetDC(editor->texthost);
4014 ME_CharFormatFromLogFont(hDC, &lf, &fmt);
4015 ITextHost_TxReleaseDC(editor->texthost, hDC);
4016 if (editor->mode & TM_RICHTEXT) {
4017 ME_Cursor start;
4018 ME_SetCursorToStart(editor, &start);
4019 ME_SetCharFormat(editor, &start, NULL, &fmt);
4021 ME_SetDefaultCharFormat(editor, &fmt);
4023 ME_CommitUndo(editor);
4024 ME_MarkAllForWrapping(editor);
4025 ME_WrapMarkedParagraphs(editor);
4026 ME_UpdateScrollBar(editor);
4027 if (bRepaint)
4028 ME_Repaint(editor);
4029 return 0;
4031 case WM_SETTEXT:
4033 ME_Cursor cursor;
4034 ME_SetCursorToStart(editor, &cursor);
4035 ME_InternalDeleteText(editor, &cursor, ME_GetTextLength(editor), FALSE);
4036 if (lParam)
4038 TRACE("WM_SETTEXT lParam==%lx\n",lParam);
4039 if (!strncmp((char *)lParam, "{\\rtf", 5) ||
4040 !strncmp((char *)lParam, "{\\urtf", 6))
4042 /* Undocumented: WM_SETTEXT supports RTF text */
4043 ME_StreamInRTFString(editor, 0, (char *)lParam);
4045 else
4046 ME_SetText(editor, (void*)lParam, unicode);
4048 else
4049 TRACE("WM_SETTEXT - NULL\n");
4050 ME_SetCursorToStart(editor, &cursor);
4051 ME_UpdateLinkAttribute(editor, &cursor, INT_MAX);
4052 ME_SetSelection(editor, 0, 0);
4053 editor->nModifyStep = 0;
4054 ME_CommitUndo(editor);
4055 ME_EmptyUndoStack(editor);
4056 ME_UpdateRepaint(editor, FALSE);
4057 return 1;
4059 case EM_CANPASTE:
4060 return paste_special( editor, 0, NULL, TRUE );
4061 case WM_PASTE:
4062 case WM_MBUTTONDOWN:
4063 wParam = 0;
4064 lParam = 0;
4065 /* fall through */
4066 case EM_PASTESPECIAL:
4067 paste_special( editor, wParam, (REPASTESPECIAL *)lParam, FALSE );
4068 return 0;
4069 case WM_CUT:
4070 case WM_COPY:
4072 int nFrom, nTo, nStartCur = ME_GetSelectionOfs(editor, &nFrom, &nTo);
4073 int nChars = nTo - nFrom;
4074 ME_Cursor *selStart = &editor->pCursors[nStartCur];
4076 if (ME_Copy(editor, selStart, nChars) && msg == WM_CUT)
4078 ME_InternalDeleteText(editor, selStart, nChars, FALSE);
4079 ME_CommitUndo(editor);
4080 ME_UpdateRepaint(editor, TRUE);
4082 return 0;
4084 case WM_GETTEXTLENGTH:
4086 GETTEXTLENGTHEX how;
4088 /* CR/LF conversion required in 2.0 mode, verbatim in 1.0 mode */
4089 how.flags = GTL_CLOSE | (editor->bEmulateVersion10 ? 0 : GTL_USECRLF) | GTL_NUMCHARS;
4090 how.codepage = unicode ? CP_UNICODE : CP_ACP;
4091 return ME_GetTextLengthEx(editor, &how);
4093 case EM_GETTEXTLENGTHEX:
4094 return ME_GetTextLengthEx(editor, (GETTEXTLENGTHEX *)wParam);
4095 case WM_GETTEXT:
4097 GETTEXTEX ex;
4098 ex.cb = wParam * (unicode ? sizeof(WCHAR) : sizeof(CHAR));
4099 ex.flags = GT_USECRLF;
4100 ex.codepage = unicode ? CP_UNICODE : CP_ACP;
4101 ex.lpDefaultChar = NULL;
4102 ex.lpUsedDefChar = NULL;
4103 return ME_GetTextEx(editor, &ex, lParam);
4105 case EM_GETTEXTEX:
4106 return ME_GetTextEx(editor, (GETTEXTEX*)wParam, lParam);
4107 case EM_GETSELTEXT:
4109 int nFrom, nTo, nStartCur = ME_GetSelectionOfs(editor, &nFrom, &nTo);
4110 ME_Cursor *from = &editor->pCursors[nStartCur];
4111 return ME_GetTextRange(editor, (WCHAR *)lParam, from,
4112 nTo - nFrom, unicode);
4114 case EM_GETSCROLLPOS:
4116 POINT *point = (POINT *)lParam;
4117 point->x = editor->horz_si.nPos;
4118 point->y = editor->vert_si.nPos;
4119 /* 16-bit scaled value is returned as stored in scrollinfo */
4120 if (editor->horz_si.nMax > 0xffff)
4121 point->x = MulDiv(point->x, 0xffff, editor->horz_si.nMax);
4122 if (editor->vert_si.nMax > 0xffff)
4123 point->y = MulDiv(point->y, 0xffff, editor->vert_si.nMax);
4124 return 1;
4126 case EM_GETTEXTRANGE:
4128 TEXTRANGEW *rng = (TEXTRANGEW *)lParam;
4129 ME_Cursor start;
4130 int nStart = rng->chrg.cpMin;
4131 int nEnd = rng->chrg.cpMax;
4132 int textlength = ME_GetTextLength(editor);
4134 TRACE("EM_GETTEXTRANGE min=%d max=%d unicode=%d textlength=%d\n",
4135 rng->chrg.cpMin, rng->chrg.cpMax, unicode, textlength);
4136 if (nStart < 0) return 0;
4137 if ((nStart == 0 && nEnd == -1) || nEnd > textlength)
4138 nEnd = textlength;
4139 if (nStart >= nEnd) return 0;
4141 ME_CursorFromCharOfs(editor, nStart, &start);
4142 return ME_GetTextRange(editor, rng->lpstrText, &start, nEnd - nStart, unicode);
4144 case EM_GETLINE:
4146 ME_DisplayItem *run;
4147 const unsigned int nMaxChars = *(WORD *) lParam;
4148 unsigned int nCharsLeft = nMaxChars;
4149 char *dest = (char *) lParam;
4150 BOOL wroteNull = FALSE;
4152 TRACE("EM_GETLINE: row=%d, nMaxChars=%d (%s)\n", (int) wParam, nMaxChars,
4153 unicode ? "Unicode" : "Ansi");
4155 run = ME_FindRowWithNumber(editor, wParam);
4156 if (run == NULL)
4157 return 0;
4159 while (nCharsLeft && (run = ME_FindItemFwd(run, diRunOrStartRow))
4160 && run->type == diRun)
4162 WCHAR *str = get_text( &run->member.run, 0 );
4163 unsigned int nCopy;
4165 nCopy = min(nCharsLeft, run->member.run.len);
4167 if (unicode)
4168 memcpy(dest, str, nCopy * sizeof(WCHAR));
4169 else
4170 nCopy = WideCharToMultiByte(CP_ACP, 0, str, nCopy, dest,
4171 nCharsLeft, NULL, NULL);
4172 dest += nCopy * (unicode ? sizeof(WCHAR) : 1);
4173 nCharsLeft -= nCopy;
4176 /* append line termination, space allowing */
4177 if (nCharsLeft > 0)
4179 if (unicode)
4180 *((WCHAR *)dest) = '\0';
4181 else
4182 *dest = '\0';
4183 nCharsLeft--;
4184 wroteNull = TRUE;
4187 TRACE("EM_GETLINE: got %u characters\n", nMaxChars - nCharsLeft);
4188 return nMaxChars - nCharsLeft - (wroteNull ? 1 : 0);
4190 case EM_GETLINECOUNT:
4192 ME_DisplayItem *item = editor->pBuffer->pFirst->next;
4193 int nRows = 0;
4195 ME_DisplayItem *prev_para = NULL, *last_para = NULL;
4197 while (item != editor->pBuffer->pLast)
4199 assert(item->type == diParagraph);
4200 prev_para = ME_FindItemBack(item, diRun);
4201 if (prev_para) {
4202 assert(prev_para->member.run.nFlags & MERF_ENDPARA);
4204 nRows += item->member.para.nRows;
4205 item = item->member.para.next_para;
4207 last_para = ME_FindItemBack(item, diRun);
4208 assert(last_para);
4209 assert(last_para->member.run.nFlags & MERF_ENDPARA);
4210 if (editor->bEmulateVersion10 && prev_para &&
4211 last_para->member.run.nCharOfs == 0 &&
4212 prev_para->member.run.len == 1 &&
4213 *get_text( &prev_para->member.run, 0 ) == '\r')
4215 /* In 1.0 emulation, the last solitary \r at the very end of the text
4216 (if one exists) is NOT a line break.
4217 FIXME: this is an ugly hack. This should have a more regular model. */
4218 nRows--;
4221 TRACE("EM_GETLINECOUNT: nRows==%d\n", nRows);
4222 return max(1, nRows);
4224 case EM_LINEFROMCHAR:
4226 if (wParam == -1)
4227 return ME_RowNumberFromCharOfs(editor, ME_GetCursorOfs(&editor->pCursors[1]));
4228 else
4229 return ME_RowNumberFromCharOfs(editor, wParam);
4231 case EM_EXLINEFROMCHAR:
4233 if (lParam == -1)
4234 return ME_RowNumberFromCharOfs(editor, ME_GetCursorOfs(&editor->pCursors[1]));
4235 else
4236 return ME_RowNumberFromCharOfs(editor, lParam);
4238 case EM_LINEINDEX:
4240 ME_DisplayItem *item, *para;
4241 int nCharOfs;
4243 if (wParam == -1)
4244 item = ME_FindItemBack(editor->pCursors[0].pRun, diStartRow);
4245 else
4246 item = ME_FindRowWithNumber(editor, wParam);
4247 if (!item)
4248 return -1;
4249 para = ME_GetParagraph(item);
4250 item = ME_FindItemFwd(item, diRun);
4251 nCharOfs = para->member.para.nCharOfs + item->member.run.nCharOfs;
4252 TRACE("EM_LINEINDEX: nCharOfs==%d\n", nCharOfs);
4253 return nCharOfs;
4255 case EM_LINELENGTH:
4257 ME_DisplayItem *item, *item_end;
4258 int nChars = 0, nThisLineOfs = 0, nNextLineOfs = 0;
4259 ME_DisplayItem *para, *run;
4261 if (wParam > ME_GetTextLength(editor))
4262 return 0;
4263 if (wParam == -1)
4265 FIXME("EM_LINELENGTH: returning number of unselected characters on lines with selection unsupported.\n");
4266 return 0;
4268 ME_RunOfsFromCharOfs(editor, wParam, &para, &run, NULL);
4269 item = ME_RowStart(run);
4270 nThisLineOfs = ME_CharOfsFromRunOfs(editor, para, ME_FindItemFwd(item, diRun), 0);
4271 item_end = ME_FindItemFwd(item, diStartRowOrParagraphOrEnd);
4272 if (item_end->type == diStartRow) {
4273 nNextLineOfs = ME_CharOfsFromRunOfs(editor, para, ME_FindItemFwd(item_end, diRun), 0);
4274 } else {
4275 ME_DisplayItem *endRun = ME_FindItemBack(item_end, diRun);
4276 assert(endRun && endRun->member.run.nFlags & MERF_ENDPARA);
4277 nNextLineOfs = item_end->member.para.nCharOfs - endRun->member.run.len;
4279 nChars = nNextLineOfs - nThisLineOfs;
4280 TRACE("EM_LINELENGTH(%ld)==%d\n",wParam, nChars);
4281 return nChars;
4283 case EM_EXLIMITTEXT:
4285 if ((int)lParam < 0)
4286 return 0;
4287 if (lParam == 0)
4288 editor->nTextLimit = 65536;
4289 else
4290 editor->nTextLimit = (int) lParam;
4291 return 0;
4293 case EM_LIMITTEXT:
4295 if (wParam == 0)
4296 editor->nTextLimit = 65536;
4297 else
4298 editor->nTextLimit = (int) wParam;
4299 return 0;
4301 case EM_GETLIMITTEXT:
4303 return editor->nTextLimit;
4305 case EM_FINDTEXT:
4307 LRESULT r;
4308 if(!unicode){
4309 FINDTEXTA *ft = (FINDTEXTA *)lParam;
4310 int nChars = MultiByteToWideChar(CP_ACP, 0, ft->lpstrText, -1, NULL, 0);
4311 WCHAR *tmp;
4313 if ((tmp = ALLOC_N_OBJ(WCHAR, nChars)) != NULL)
4314 MultiByteToWideChar(CP_ACP, 0, ft->lpstrText, -1, tmp, nChars);
4315 r = ME_FindText(editor, wParam, &ft->chrg, tmp, NULL);
4316 FREE_OBJ( tmp );
4317 }else{
4318 FINDTEXTW *ft = (FINDTEXTW *)lParam;
4319 r = ME_FindText(editor, wParam, &ft->chrg, ft->lpstrText, NULL);
4321 return r;
4323 case EM_FINDTEXTEX:
4325 LRESULT r;
4326 if(!unicode){
4327 FINDTEXTEXA *ex = (FINDTEXTEXA *)lParam;
4328 int nChars = MultiByteToWideChar(CP_ACP, 0, ex->lpstrText, -1, NULL, 0);
4329 WCHAR *tmp;
4331 if ((tmp = ALLOC_N_OBJ(WCHAR, nChars)) != NULL)
4332 MultiByteToWideChar(CP_ACP, 0, ex->lpstrText, -1, tmp, nChars);
4333 r = ME_FindText(editor, wParam, &ex->chrg, tmp, &ex->chrgText);
4334 FREE_OBJ( tmp );
4335 }else{
4336 FINDTEXTEXW *ex = (FINDTEXTEXW *)lParam;
4337 r = ME_FindText(editor, wParam, &ex->chrg, ex->lpstrText, &ex->chrgText);
4339 return r;
4341 case EM_FINDTEXTW:
4343 FINDTEXTW *ft = (FINDTEXTW *)lParam;
4344 return ME_FindText(editor, wParam, &ft->chrg, ft->lpstrText, NULL);
4346 case EM_FINDTEXTEXW:
4348 FINDTEXTEXW *ex = (FINDTEXTEXW *)lParam;
4349 return ME_FindText(editor, wParam, &ex->chrg, ex->lpstrText, &ex->chrgText);
4351 case EM_GETZOOM:
4352 if (!wParam || !lParam)
4353 return FALSE;
4354 *(int *)wParam = editor->nZoomNumerator;
4355 *(int *)lParam = editor->nZoomDenominator;
4356 return TRUE;
4357 case EM_SETZOOM:
4358 return ME_SetZoom(editor, wParam, lParam);
4359 case EM_CHARFROMPOS:
4361 ME_Cursor cursor;
4362 if (ME_CharFromPos(editor, ((POINTL *)lParam)->x, ((POINTL *)lParam)->y,
4363 &cursor, NULL))
4364 return ME_GetCursorOfs(&cursor);
4365 else
4366 return -1;
4368 case EM_POSFROMCHAR:
4370 ME_DisplayItem *pPara, *pRun;
4371 int nCharOfs, nOffset, nLength;
4372 POINTL pt = {0,0};
4374 nCharOfs = wParam;
4375 /* detect which API version we're dealing with */
4376 if (wParam >= 0x40000)
4377 nCharOfs = lParam;
4378 nLength = ME_GetTextLength(editor);
4379 nCharOfs = min(nCharOfs, nLength);
4380 nCharOfs = max(nCharOfs, 0);
4382 ME_RunOfsFromCharOfs(editor, nCharOfs, &pPara, &pRun, &nOffset);
4383 assert(pRun->type == diRun);
4384 pt.y = pRun->member.run.pt.y;
4385 pt.x = pRun->member.run.pt.x + ME_PointFromChar(editor, &pRun->member.run, nOffset, TRUE);
4386 pt.y += pPara->member.para.pt.y + editor->rcFormat.top;
4387 pt.x += editor->rcFormat.left;
4389 pt.x -= editor->horz_si.nPos;
4390 pt.y -= editor->vert_si.nPos;
4392 if (wParam >= 0x40000) {
4393 *(POINTL *)wParam = pt;
4395 return (wParam >= 0x40000) ? 0 : MAKELONG( pt.x, pt.y );
4397 case WM_CREATE:
4398 return ME_WmCreate(editor, lParam, unicode);
4399 case WM_DESTROY:
4400 ME_DestroyEditor(editor);
4401 return 0;
4402 case WM_SETCURSOR:
4404 POINT cursor_pos;
4405 if (wParam == (WPARAM)editor->hWnd && GetCursorPos(&cursor_pos) &&
4406 ScreenToClient(editor->hWnd, &cursor_pos))
4407 ME_LinkNotify(editor, msg, 0, MAKELPARAM(cursor_pos.x, cursor_pos.y));
4408 return ME_SetCursor(editor);
4410 case WM_LBUTTONDBLCLK:
4411 case WM_LBUTTONDOWN:
4413 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4414 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4415 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4416 return 0;
4417 ITextHost_TxSetFocus(editor->texthost);
4418 ME_LButtonDown(editor, (short)LOWORD(lParam), (short)HIWORD(lParam),
4419 ME_CalculateClickCount(editor, msg, wParam, lParam));
4420 ITextHost_TxSetCapture(editor->texthost, TRUE);
4421 editor->bMouseCaptured = TRUE;
4422 ME_LinkNotify(editor, msg, wParam, lParam);
4423 if (!ME_SetCursor(editor)) goto do_default;
4424 break;
4426 case WM_MOUSEMOVE:
4427 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4428 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4429 return 0;
4430 if (editor->bMouseCaptured)
4431 ME_MouseMove(editor, (short)LOWORD(lParam), (short)HIWORD(lParam));
4432 else
4433 ME_LinkNotify(editor, msg, wParam, lParam);
4434 /* Set cursor if mouse is captured, since WM_SETCURSOR won't be received. */
4435 if (editor->bMouseCaptured)
4436 ME_SetCursor(editor);
4437 break;
4438 case WM_LBUTTONUP:
4439 if (editor->bMouseCaptured) {
4440 ITextHost_TxSetCapture(editor->texthost, FALSE);
4441 editor->bMouseCaptured = FALSE;
4443 if (editor->nSelectionType == stDocument)
4444 editor->nSelectionType = stPosition;
4445 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4446 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4447 return 0;
4448 else
4450 ME_SetCursor(editor);
4451 ME_LinkNotify(editor, msg, wParam, lParam);
4453 break;
4454 case WM_RBUTTONUP:
4455 case WM_RBUTTONDOWN:
4456 case WM_RBUTTONDBLCLK:
4457 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4458 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4459 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4460 return 0;
4461 ME_LinkNotify(editor, msg, wParam, lParam);
4462 goto do_default;
4463 case WM_CONTEXTMENU:
4464 if (!ME_ShowContextMenu(editor, (short)LOWORD(lParam), (short)HIWORD(lParam)))
4465 goto do_default;
4466 break;
4467 case WM_SETFOCUS:
4468 editor->bHaveFocus = TRUE;
4469 ME_ShowCaret(editor);
4470 ME_SendOldNotify(editor, EN_SETFOCUS);
4471 if (!editor->bHideSelection && !(editor->styleFlags & ES_NOHIDESEL))
4472 ME_InvalidateSelection( editor );
4473 return 0;
4474 case WM_KILLFOCUS:
4475 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4476 editor->bHaveFocus = FALSE;
4477 editor->wheel_remain = 0;
4478 ME_HideCaret(editor);
4479 ME_SendOldNotify(editor, EN_KILLFOCUS);
4480 if (!editor->bHideSelection && !(editor->styleFlags & ES_NOHIDESEL))
4481 ME_InvalidateSelection( editor );
4482 return 0;
4483 case WM_COMMAND:
4484 TRACE("editor wnd command = %d\n", LOWORD(wParam));
4485 return 0;
4486 case WM_KEYUP:
4487 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4488 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4489 return 0;
4490 goto do_default;
4491 case WM_KEYDOWN:
4492 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4493 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4494 return 0;
4495 if (ME_KeyDown(editor, LOWORD(wParam)))
4496 return 0;
4497 goto do_default;
4498 case WM_CHAR:
4499 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4500 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4501 return 0;
4502 return ME_Char(editor, wParam, lParam, unicode);
4503 case WM_UNICHAR:
4504 if (unicode)
4506 if(wParam == UNICODE_NOCHAR) return TRUE;
4507 if(wParam <= 0x000fffff)
4509 if(wParam > 0xffff) /* convert to surrogates */
4511 wParam -= 0x10000;
4512 ME_Char(editor, (wParam >> 10) + 0xd800, 0, TRUE);
4513 ME_Char(editor, (wParam & 0x03ff) + 0xdc00, 0, TRUE);
4514 } else {
4515 ME_Char(editor, wParam, 0, TRUE);
4518 return 0;
4520 break;
4521 case EM_STOPGROUPTYPING:
4522 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4523 return 0;
4524 case WM_HSCROLL:
4526 const int scrollUnit = 7;
4528 switch(LOWORD(wParam))
4530 case SB_LEFT:
4531 ME_ScrollAbs(editor, 0, 0);
4532 break;
4533 case SB_RIGHT:
4534 ME_ScrollAbs(editor,
4535 editor->horz_si.nMax - (int)editor->horz_si.nPage,
4536 editor->vert_si.nMax - (int)editor->vert_si.nPage);
4537 break;
4538 case SB_LINELEFT:
4539 ME_ScrollLeft(editor, scrollUnit);
4540 break;
4541 case SB_LINERIGHT:
4542 ME_ScrollRight(editor, scrollUnit);
4543 break;
4544 case SB_PAGELEFT:
4545 ME_ScrollLeft(editor, editor->sizeWindow.cx);
4546 break;
4547 case SB_PAGERIGHT:
4548 ME_ScrollRight(editor, editor->sizeWindow.cx);
4549 break;
4550 case SB_THUMBTRACK:
4551 case SB_THUMBPOSITION:
4553 int pos = HIWORD(wParam);
4554 if (editor->horz_si.nMax > 0xffff)
4555 pos = MulDiv(pos, editor->horz_si.nMax, 0xffff);
4556 ME_HScrollAbs(editor, pos);
4557 break;
4560 break;
4562 case EM_SCROLL: /* fall through */
4563 case WM_VSCROLL:
4565 int origNPos;
4566 int lineHeight = get_default_line_height( editor );
4568 origNPos = editor->vert_si.nPos;
4570 switch(LOWORD(wParam))
4572 case SB_TOP:
4573 ME_ScrollAbs(editor, 0, 0);
4574 break;
4575 case SB_BOTTOM:
4576 ME_ScrollAbs(editor,
4577 editor->horz_si.nMax - (int)editor->horz_si.nPage,
4578 editor->vert_si.nMax - (int)editor->vert_si.nPage);
4579 break;
4580 case SB_LINEUP:
4581 ME_ScrollUp(editor,lineHeight);
4582 break;
4583 case SB_LINEDOWN:
4584 ME_ScrollDown(editor,lineHeight);
4585 break;
4586 case SB_PAGEUP:
4587 ME_ScrollUp(editor,editor->sizeWindow.cy);
4588 break;
4589 case SB_PAGEDOWN:
4590 ME_ScrollDown(editor,editor->sizeWindow.cy);
4591 break;
4592 case SB_THUMBTRACK:
4593 case SB_THUMBPOSITION:
4595 int pos = HIWORD(wParam);
4596 if (editor->vert_si.nMax > 0xffff)
4597 pos = MulDiv(pos, editor->vert_si.nMax, 0xffff);
4598 ME_VScrollAbs(editor, pos);
4599 break;
4602 if (msg == EM_SCROLL)
4603 return 0x00010000 | (((editor->vert_si.nPos - origNPos)/lineHeight) & 0xffff);
4604 break;
4606 case WM_MOUSEWHEEL:
4608 int delta;
4609 BOOL ctrl_is_down;
4611 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4612 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4613 return 0;
4615 ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
4617 delta = GET_WHEEL_DELTA_WPARAM(wParam);
4619 /* if scrolling changes direction, ignore left overs */
4620 if ((delta < 0 && editor->wheel_remain < 0) ||
4621 (delta > 0 && editor->wheel_remain > 0))
4622 editor->wheel_remain += delta;
4623 else
4624 editor->wheel_remain = delta;
4626 if (editor->wheel_remain)
4628 if (ctrl_is_down) {
4629 int numerator;
4630 if (!editor->nZoomNumerator || !editor->nZoomDenominator)
4632 numerator = 100;
4633 } else {
4634 numerator = editor->nZoomNumerator * 100 / editor->nZoomDenominator;
4636 numerator += calc_wheel_change( &editor->wheel_remain, 10 );
4637 if (numerator >= 10 && numerator <= 500)
4638 ME_SetZoom(editor, numerator, 100);
4639 } else {
4640 UINT max_lines = 3;
4641 int lines = 0;
4643 SystemParametersInfoW( SPI_GETWHEELSCROLLLINES, 0, &max_lines, 0 );
4644 if (max_lines)
4645 lines = calc_wheel_change( &editor->wheel_remain, (int)max_lines );
4646 if (lines)
4647 ME_ScrollDown( editor, -lines * get_default_line_height( editor ) );
4650 break;
4652 case EM_GETRECT:
4654 *((RECT *)lParam) = editor->rcFormat;
4655 if (editor->bDefaultFormatRect)
4656 ((RECT *)lParam)->left -= editor->selofs;
4657 return 0;
4659 case EM_SETRECT:
4660 case EM_SETRECTNP:
4662 if (lParam)
4664 int border = 0;
4665 RECT clientRect;
4666 RECT *rc = (RECT *)lParam;
4668 border = editor->exStyleFlags & WS_EX_CLIENTEDGE ? 1 : 0;
4669 ITextHost_TxGetClientRect(editor->texthost, &clientRect);
4670 if (wParam == 0)
4672 editor->rcFormat.top = max(0, rc->top - border);
4673 editor->rcFormat.left = max(0, rc->left - border);
4674 editor->rcFormat.bottom = min(clientRect.bottom, rc->bottom);
4675 editor->rcFormat.right = min(clientRect.right, rc->right + border);
4676 } else if (wParam == 1) {
4677 /* MSDN incorrectly says a wParam value of 1 causes the
4678 * lParam rect to be used as a relative offset,
4679 * however, the tests show it just prevents min/max bound
4680 * checking. */
4681 editor->rcFormat.top = rc->top - border;
4682 editor->rcFormat.left = rc->left - border;
4683 editor->rcFormat.bottom = rc->bottom;
4684 editor->rcFormat.right = rc->right + border;
4685 } else {
4686 return 0;
4688 editor->bDefaultFormatRect = FALSE;
4690 else
4692 ME_SetDefaultFormatRect(editor);
4693 editor->bDefaultFormatRect = TRUE;
4695 ME_MarkAllForWrapping(editor);
4696 ME_WrapMarkedParagraphs(editor);
4697 ME_UpdateScrollBar(editor);
4698 if (msg != EM_SETRECTNP)
4699 ME_Repaint(editor);
4700 return 0;
4702 case EM_REQUESTRESIZE:
4703 ME_SendRequestResize(editor, TRUE);
4704 return 0;
4705 case WM_SETREDRAW:
4706 goto do_default;
4707 case WM_WINDOWPOSCHANGED:
4709 RECT clientRect;
4710 WINDOWPOS *winpos = (WINDOWPOS *)lParam;
4712 if (winpos->flags & SWP_NOCLIENTSIZE) goto do_default;
4713 ITextHost_TxGetClientRect(editor->texthost, &clientRect);
4714 if (editor->bDefaultFormatRect) {
4715 ME_SetDefaultFormatRect(editor);
4716 } else {
4717 editor->rcFormat.right += clientRect.right - editor->prevClientRect.right;
4718 editor->rcFormat.bottom += clientRect.bottom - editor->prevClientRect.bottom;
4720 editor->prevClientRect = clientRect;
4721 ME_RewrapRepaint(editor);
4722 goto do_default;
4724 /* IME messages to make richedit controls IME aware */
4725 case WM_IME_SETCONTEXT:
4726 case WM_IME_CONTROL:
4727 case WM_IME_SELECT:
4728 case WM_IME_COMPOSITIONFULL:
4729 return 0;
4730 case WM_IME_STARTCOMPOSITION:
4732 editor->imeStartIndex=ME_GetCursorOfs(&editor->pCursors[0]);
4733 ME_DeleteSelection(editor);
4734 ME_CommitUndo(editor);
4735 ME_UpdateRepaint(editor, FALSE);
4736 return 0;
4738 case WM_IME_COMPOSITION:
4740 HIMC hIMC;
4742 ME_Style *style = ME_GetInsertStyle(editor, 0);
4743 hIMC = ITextHost_TxImmGetContext(editor->texthost);
4744 ME_DeleteSelection(editor);
4745 ME_SaveTempStyle(editor, style);
4746 if (lParam & (GCS_RESULTSTR|GCS_COMPSTR))
4748 LPWSTR lpCompStr = NULL;
4749 DWORD dwBufLen;
4750 DWORD dwIndex = lParam & GCS_RESULTSTR;
4751 if (!dwIndex)
4752 dwIndex = GCS_COMPSTR;
4754 dwBufLen = ImmGetCompositionStringW(hIMC, dwIndex, NULL, 0);
4755 lpCompStr = HeapAlloc(GetProcessHeap(),0,dwBufLen + sizeof(WCHAR));
4756 ImmGetCompositionStringW(hIMC, dwIndex, lpCompStr, dwBufLen);
4757 lpCompStr[dwBufLen/sizeof(WCHAR)] = 0;
4758 ME_InsertTextFromCursor(editor,0,lpCompStr,dwBufLen/sizeof(WCHAR),style);
4759 HeapFree(GetProcessHeap(), 0, lpCompStr);
4761 if (dwIndex == GCS_COMPSTR)
4762 ME_SetSelection(editor,editor->imeStartIndex,
4763 editor->imeStartIndex + dwBufLen/sizeof(WCHAR));
4765 ME_ReleaseStyle(style);
4766 ME_CommitUndo(editor);
4767 ME_UpdateRepaint(editor, FALSE);
4768 return 0;
4770 case WM_IME_ENDCOMPOSITION:
4772 ME_DeleteSelection(editor);
4773 editor->imeStartIndex=-1;
4774 return 0;
4776 case EM_GETOLEINTERFACE:
4778 if (!editor->reOle)
4779 if (!CreateIRichEditOle(NULL, editor, (LPVOID *)&editor->reOle))
4780 return 0;
4781 *(LPVOID *)lParam = editor->reOle;
4782 IRichEditOle_AddRef(editor->reOle);
4783 return 1;
4785 case EM_GETPASSWORDCHAR:
4787 return editor->cPasswordMask;
4789 case EM_SETOLECALLBACK:
4790 if(editor->lpOleCallback)
4791 IRichEditOleCallback_Release(editor->lpOleCallback);
4792 editor->lpOleCallback = (IRichEditOleCallback*)lParam;
4793 if(editor->lpOleCallback)
4794 IRichEditOleCallback_AddRef(editor->lpOleCallback);
4795 return TRUE;
4796 case EM_GETWORDBREAKPROC:
4797 return (LRESULT)editor->pfnWordBreak;
4798 case EM_SETWORDBREAKPROC:
4800 EDITWORDBREAKPROCW pfnOld = editor->pfnWordBreak;
4802 editor->pfnWordBreak = (EDITWORDBREAKPROCW)lParam;
4803 return (LRESULT)pfnOld;
4805 case EM_GETTEXTMODE:
4806 return editor->mode;
4807 case EM_SETTEXTMODE:
4809 int mask = 0;
4810 int changes = 0;
4812 if (ME_GetTextLength(editor) ||
4813 !list_empty( &editor->undo_stack ) || !list_empty( &editor->redo_stack ))
4814 return E_UNEXPECTED;
4816 /* Check for mutually exclusive flags in adjacent bits of wParam */
4817 if ((wParam & (TM_RICHTEXT | TM_MULTILEVELUNDO | TM_MULTICODEPAGE)) &
4818 (wParam & (TM_PLAINTEXT | TM_SINGLELEVELUNDO | TM_SINGLECODEPAGE)) << 1)
4819 return E_INVALIDARG;
4821 if (wParam & (TM_RICHTEXT | TM_PLAINTEXT))
4823 mask |= TM_RICHTEXT | TM_PLAINTEXT;
4824 changes |= wParam & (TM_RICHTEXT | TM_PLAINTEXT);
4825 if (wParam & TM_PLAINTEXT) {
4826 /* Clear selection since it should be possible to select the
4827 * end of text run for rich text */
4828 ME_InvalidateSelection(editor);
4829 ME_SetCursorToStart(editor, &editor->pCursors[0]);
4830 editor->pCursors[1] = editor->pCursors[0];
4831 /* plain text can only have the default style. */
4832 ME_ClearTempStyle(editor);
4833 ME_AddRefStyle(editor->pBuffer->pDefaultStyle);
4834 ME_ReleaseStyle(editor->pCursors[0].pRun->member.run.style);
4835 editor->pCursors[0].pRun->member.run.style = editor->pBuffer->pDefaultStyle;
4838 /* FIXME: Currently no support for undo level and code page options */
4839 editor->mode = (editor->mode & ~mask) | changes;
4840 return 0;
4842 case EM_SETPASSWORDCHAR:
4844 editor->cPasswordMask = wParam;
4845 ME_RewrapRepaint(editor);
4846 return 0;
4848 case EM_SETTARGETDEVICE:
4849 if (wParam == 0)
4851 BOOL new = (lParam == 0 && (editor->styleFlags & ES_MULTILINE));
4852 if (editor->nAvailWidth || editor->bWordWrap != new)
4854 editor->bWordWrap = new;
4855 editor->nAvailWidth = 0; /* wrap to client area */
4856 ME_RewrapRepaint(editor);
4858 } else {
4859 int width = max(0, lParam);
4860 if ((editor->styleFlags & ES_MULTILINE) &&
4861 (!editor->bWordWrap || editor->nAvailWidth != width))
4863 editor->nAvailWidth = width;
4864 editor->bWordWrap = TRUE;
4865 ME_RewrapRepaint(editor);
4867 FIXME("EM_SETTARGETDEVICE doesn't use non-NULL target devices\n");
4869 return TRUE;
4870 default:
4871 do_default:
4872 *phresult = S_FALSE;
4873 break;
4875 return 0L;
4878 static BOOL create_windowed_editor(HWND hwnd, CREATESTRUCTW *create, BOOL emulate_10)
4880 ITextHost *host = ME_CreateTextHost( hwnd, create, emulate_10 );
4881 ME_TextEditor *editor;
4883 if (!host) return FALSE;
4885 editor = ME_MakeEditor( host, emulate_10 );
4886 if (!editor)
4888 ITextHost_Release( host );
4889 return FALSE;
4892 editor->exStyleFlags = GetWindowLongW( hwnd, GWL_EXSTYLE );
4893 editor->styleFlags |= GetWindowLongW( hwnd, GWL_STYLE ) & ES_WANTRETURN;
4894 editor->hWnd = hwnd; /* FIXME: Remove editor's dependence on hWnd */
4895 editor->hwndParent = create->hwndParent;
4897 SetWindowLongPtrW( hwnd, 0, (LONG_PTR)editor );
4899 return TRUE;
4902 static LRESULT RichEditWndProc_common(HWND hWnd, UINT msg, WPARAM wParam,
4903 LPARAM lParam, BOOL unicode)
4905 ME_TextEditor *editor;
4906 HRESULT hresult;
4907 LRESULT lresult = 0;
4909 TRACE("enter hwnd %p msg %04x (%s) %lx %lx, unicode %d\n",
4910 hWnd, msg, get_msg_name(msg), wParam, lParam, unicode);
4912 editor = (ME_TextEditor *)GetWindowLongPtrW(hWnd, 0);
4913 if (!editor)
4915 if (msg == WM_NCCREATE)
4917 CREATESTRUCTW *pcs = (CREATESTRUCTW *)lParam;
4919 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd, pcs->style);
4920 return create_windowed_editor( hWnd, pcs, FALSE );
4922 else
4924 return DefWindowProcW(hWnd, msg, wParam, lParam);
4928 switch (msg)
4930 case WM_PAINT:
4932 HDC hDC;
4933 RECT rc;
4934 PAINTSTRUCT ps;
4936 hDC = BeginPaint(editor->hWnd, &ps);
4937 if (!editor->bEmulateVersion10 || (editor->nEventMask & ENM_UPDATE))
4938 ME_SendOldNotify(editor, EN_UPDATE);
4939 /* Erase area outside of the formatting rectangle */
4940 if (ps.rcPaint.top < editor->rcFormat.top)
4942 rc = ps.rcPaint;
4943 rc.bottom = editor->rcFormat.top;
4944 FillRect(hDC, &rc, editor->hbrBackground);
4945 ps.rcPaint.top = editor->rcFormat.top;
4947 if (ps.rcPaint.bottom > editor->rcFormat.bottom) {
4948 rc = ps.rcPaint;
4949 rc.top = editor->rcFormat.bottom;
4950 FillRect(hDC, &rc, editor->hbrBackground);
4951 ps.rcPaint.bottom = editor->rcFormat.bottom;
4953 if (ps.rcPaint.left < editor->rcFormat.left) {
4954 rc = ps.rcPaint;
4955 rc.right = editor->rcFormat.left;
4956 FillRect(hDC, &rc, editor->hbrBackground);
4957 ps.rcPaint.left = editor->rcFormat.left;
4959 if (ps.rcPaint.right > editor->rcFormat.right) {
4960 rc = ps.rcPaint;
4961 rc.left = editor->rcFormat.right;
4962 FillRect(hDC, &rc, editor->hbrBackground);
4963 ps.rcPaint.right = editor->rcFormat.right;
4966 ME_PaintContent(editor, hDC, &ps.rcPaint);
4967 EndPaint(editor->hWnd, &ps);
4968 return 0;
4970 case WM_ERASEBKGND:
4972 HDC hDC = (HDC)wParam;
4973 RECT rc;
4975 if (GetUpdateRect(editor->hWnd, &rc, TRUE))
4976 FillRect(hDC, &rc, editor->hbrBackground);
4977 return 1;
4979 case EM_SETOPTIONS:
4981 DWORD dwStyle;
4982 const DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
4983 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN |
4984 ECO_SELECTIONBAR;
4985 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
4986 dwStyle = GetWindowLongW(hWnd, GWL_STYLE);
4987 dwStyle = (dwStyle & ~mask) | (lresult & mask);
4988 SetWindowLongW(hWnd, GWL_STYLE, dwStyle);
4989 return lresult;
4991 case EM_SETREADONLY:
4993 DWORD dwStyle;
4994 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
4995 dwStyle = GetWindowLongW(hWnd, GWL_STYLE);
4996 dwStyle &= ~ES_READONLY;
4997 if (wParam)
4998 dwStyle |= ES_READONLY;
4999 SetWindowLongW(hWnd, GWL_STYLE, dwStyle);
5000 return lresult;
5002 default:
5003 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
5006 if (hresult == S_FALSE)
5007 lresult = DefWindowProcW(hWnd, msg, wParam, lParam);
5009 TRACE("exit hwnd %p msg %04x (%s) %lx %lx, unicode %d -> %lu\n",
5010 hWnd, msg, get_msg_name(msg), wParam, lParam, unicode, lresult);
5012 return lresult;
5015 static LRESULT WINAPI RichEditWndProcW(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5017 BOOL unicode = TRUE;
5019 /* Under Win9x RichEdit20W returns ANSI strings, see the tests. */
5020 if (msg == WM_GETTEXT && (GetVersion() & 0x80000000))
5021 unicode = FALSE;
5023 return RichEditWndProc_common(hWnd, msg, wParam, lParam, unicode);
5026 static LRESULT WINAPI RichEditWndProcA(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5028 return RichEditWndProc_common(hWnd, msg, wParam, lParam, FALSE);
5031 /******************************************************************
5032 * RichEditANSIWndProc (RICHED20.10)
5034 LRESULT WINAPI RichEditANSIWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5036 return RichEditWndProcA(hWnd, msg, wParam, lParam);
5039 /******************************************************************
5040 * RichEdit10ANSIWndProc (RICHED20.9)
5042 LRESULT WINAPI RichEdit10ANSIWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5044 if (msg == WM_NCCREATE && !GetWindowLongPtrW(hWnd, 0))
5046 CREATESTRUCTW *pcs = (CREATESTRUCTW *)lParam;
5048 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd, pcs->style);
5049 return create_windowed_editor( hWnd, pcs, TRUE );
5051 return RichEditANSIWndProc(hWnd, msg, wParam, lParam);
5054 void ME_SendOldNotify(ME_TextEditor *editor, int nCode)
5056 ITextHost_TxNotify(editor->texthost, nCode, NULL);
5059 /* Fill buffer with srcChars unicode characters from the start cursor.
5061 * buffer: destination buffer
5062 * buflen: length of buffer in characters excluding the NULL terminator.
5063 * start: start of editor text to copy into buffer.
5064 * srcChars: Number of characters to use from the editor text.
5065 * bCRLF: if true, replaces all end of lines with \r\n pairs.
5067 * returns the number of characters written excluding the NULL terminator.
5069 * The written text is always NULL terminated.
5071 int ME_GetTextW(ME_TextEditor *editor, WCHAR *buffer, int buflen,
5072 const ME_Cursor *start, int srcChars, BOOL bCRLF,
5073 BOOL bEOP)
5075 ME_DisplayItem *pRun, *pNextRun;
5076 const WCHAR *pStart = buffer;
5077 const WCHAR cr_lf[] = {'\r', '\n', 0};
5078 const WCHAR *str;
5079 int nLen;
5081 /* bCRLF flag is only honored in 2.0 and up. 1.0 must always return text verbatim */
5082 if (editor->bEmulateVersion10) bCRLF = FALSE;
5084 pRun = start->pRun;
5085 assert(pRun);
5086 pNextRun = ME_FindItemFwd(pRun, diRun);
5088 nLen = pRun->member.run.len - start->nOffset;
5089 str = get_text( &pRun->member.run, start->nOffset );
5091 while (srcChars && buflen && pNextRun)
5093 int nFlags = pRun->member.run.nFlags;
5095 if (bCRLF && nFlags & MERF_ENDPARA && ~nFlags & MERF_ENDCELL)
5097 if (buflen == 1) break;
5098 /* FIXME: native fails to reduce srcChars here for WM_GETTEXT or
5099 * EM_GETTEXTEX, however, this is done for copying text which
5100 * also uses this function. */
5101 srcChars -= min(nLen, srcChars);
5102 nLen = 2;
5103 str = cr_lf;
5104 } else {
5105 nLen = min(nLen, srcChars);
5106 srcChars -= nLen;
5109 nLen = min(nLen, buflen);
5110 buflen -= nLen;
5112 CopyMemory(buffer, str, sizeof(WCHAR) * nLen);
5114 buffer += nLen;
5116 pRun = pNextRun;
5117 pNextRun = ME_FindItemFwd(pRun, diRun);
5119 nLen = pRun->member.run.len;
5120 str = get_text( &pRun->member.run, 0 );
5122 /* append '\r' to the last paragraph. */
5123 if (pRun->next->type == diTextEnd && bEOP)
5125 *buffer = '\r';
5126 buffer ++;
5128 *buffer = 0;
5129 return buffer - pStart;
5132 static BOOL ME_RegisterEditorClass(HINSTANCE hInstance)
5134 WNDCLASSW wcW;
5135 WNDCLASSA wcA;
5137 wcW.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS;
5138 wcW.lpfnWndProc = RichEditWndProcW;
5139 wcW.cbClsExtra = 0;
5140 wcW.cbWndExtra = sizeof(ME_TextEditor *);
5141 wcW.hInstance = NULL; /* hInstance would register DLL-local class */
5142 wcW.hIcon = NULL;
5143 wcW.hCursor = LoadCursorW(NULL, (LPWSTR)IDC_IBEAM);
5144 wcW.hbrBackground = GetStockObject(NULL_BRUSH);
5145 wcW.lpszMenuName = NULL;
5147 if (is_version_nt())
5149 wcW.lpszClassName = RICHEDIT_CLASS20W;
5150 if (!RegisterClassW(&wcW)) return FALSE;
5151 wcW.lpszClassName = MSFTEDIT_CLASS;
5152 if (!RegisterClassW(&wcW)) return FALSE;
5154 else
5156 /* WNDCLASSA/W have the same layout */
5157 wcW.lpszClassName = (LPCWSTR)"RichEdit20W";
5158 if (!RegisterClassA((WNDCLASSA *)&wcW)) return FALSE;
5159 wcW.lpszClassName = (LPCWSTR)"RichEdit50W";
5160 if (!RegisterClassA((WNDCLASSA *)&wcW)) return FALSE;
5163 wcA.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS;
5164 wcA.lpfnWndProc = RichEditWndProcA;
5165 wcA.cbClsExtra = 0;
5166 wcA.cbWndExtra = sizeof(ME_TextEditor *);
5167 wcA.hInstance = NULL; /* hInstance would register DLL-local class */
5168 wcA.hIcon = NULL;
5169 wcA.hCursor = LoadCursorW(NULL, (LPWSTR)IDC_IBEAM);
5170 wcA.hbrBackground = GetStockObject(NULL_BRUSH);
5171 wcA.lpszMenuName = NULL;
5172 wcA.lpszClassName = RICHEDIT_CLASS20A;
5173 if (!RegisterClassA(&wcA)) return FALSE;
5174 wcA.lpszClassName = "RichEdit50A";
5175 if (!RegisterClassA(&wcA)) return FALSE;
5177 return TRUE;
5180 static LRESULT WINAPI REComboWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
5181 /* FIXME: Not implemented */
5182 TRACE("hWnd %p msg %04x (%s) %08lx %08lx\n",
5183 hWnd, msg, get_msg_name(msg), wParam, lParam);
5184 return DefWindowProcW(hWnd, msg, wParam, lParam);
5187 static LRESULT WINAPI REListWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
5188 /* FIXME: Not implemented */
5189 TRACE("hWnd %p msg %04x (%s) %08lx %08lx\n",
5190 hWnd, msg, get_msg_name(msg), wParam, lParam);
5191 return DefWindowProcW(hWnd, msg, wParam, lParam);
5194 /******************************************************************
5195 * REExtendedRegisterClass (RICHED20.8)
5197 * FIXME undocumented
5198 * Need to check for errors and implement controls and callbacks
5200 LRESULT WINAPI REExtendedRegisterClass(void)
5202 WNDCLASSW wcW;
5203 UINT result;
5205 FIXME("semi stub\n");
5207 wcW.cbClsExtra = 0;
5208 wcW.cbWndExtra = 4;
5209 wcW.hInstance = NULL;
5210 wcW.hIcon = NULL;
5211 wcW.hCursor = NULL;
5212 wcW.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
5213 wcW.lpszMenuName = NULL;
5215 if (!ME_ListBoxRegistered)
5217 wcW.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS;
5218 wcW.lpfnWndProc = REListWndProc;
5219 wcW.lpszClassName = REListBox20W;
5220 if (RegisterClassW(&wcW)) ME_ListBoxRegistered = TRUE;
5223 if (!ME_ComboBoxRegistered)
5225 wcW.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS | CS_VREDRAW | CS_HREDRAW;
5226 wcW.lpfnWndProc = REComboWndProc;
5227 wcW.lpszClassName = REComboBox20W;
5228 if (RegisterClassW(&wcW)) ME_ComboBoxRegistered = TRUE;
5231 result = 0;
5232 if (ME_ListBoxRegistered)
5233 result += 1;
5234 if (ME_ComboBoxRegistered)
5235 result += 2;
5237 return result;
5240 static int wchar_comp( const void *key, const void *elem )
5242 return *(const WCHAR *)key - *(const WCHAR *)elem;
5245 /* neutral characters end the url if the next non-neutral character is a space character,
5246 otherwise they are included in the url. */
5247 static BOOL isurlneutral( WCHAR c )
5249 /* NB this list is sorted */
5250 static const WCHAR neutral_chars[] = {'!','\"','\'','(',')',',','-','.',':',';','<','>','?','[',']','{','}'};
5252 /* Some shortcuts */
5253 if (isalnum( c )) return FALSE;
5254 if (c > neutral_chars[sizeof(neutral_chars) / sizeof(neutral_chars[0]) - 1]) return FALSE;
5256 return !!bsearch( &c, neutral_chars, sizeof(neutral_chars) / sizeof(neutral_chars[0]),
5257 sizeof(c), wchar_comp );
5261 * This proc takes a selection, and scans it forward in order to select the span
5262 * of a possible URL candidate. A possible URL candidate must start with isalnum
5263 * or one of the following special characters: *|/\+%#@ and must consist entirely
5264 * of the characters allowed to start the URL, plus : (colon) which may occur
5265 * at most once, and not at either end.
5267 static BOOL ME_FindNextURLCandidate(ME_TextEditor *editor,
5268 const ME_Cursor *start,
5269 int nChars,
5270 ME_Cursor *candidate_min,
5271 ME_Cursor *candidate_max)
5273 ME_Cursor cursor = *start, neutral_end, space_end;
5274 BOOL candidateStarted = FALSE, quoted = FALSE;
5275 WCHAR c;
5277 while (nChars > 0)
5279 WCHAR *str = get_text( &cursor.pRun->member.run, 0 );
5280 int run_len = cursor.pRun->member.run.len;
5282 nChars -= run_len - cursor.nOffset;
5284 /* Find start of candidate */
5285 if (!candidateStarted)
5287 while (cursor.nOffset < run_len)
5289 c = str[cursor.nOffset];
5290 if (!isspaceW( c ) && !isurlneutral( c ))
5292 *candidate_min = cursor;
5293 candidateStarted = TRUE;
5294 neutral_end.pPara = NULL;
5295 space_end.pPara = NULL;
5296 cursor.nOffset++;
5297 break;
5299 quoted = (c == '<');
5300 cursor.nOffset++;
5304 /* Find end of candidate */
5305 if (candidateStarted)
5307 while (cursor.nOffset < run_len)
5309 c = str[cursor.nOffset];
5310 if (isspaceW( c ))
5312 if (quoted && c != '\r')
5314 if (!space_end.pPara)
5316 if (neutral_end.pPara)
5317 space_end = neutral_end;
5318 else
5319 space_end = cursor;
5322 else
5323 goto done;
5325 else if (isurlneutral( c ))
5327 if (quoted && c == '>')
5329 neutral_end.pPara = NULL;
5330 space_end.pPara = NULL;
5331 goto done;
5333 if (!neutral_end.pPara)
5334 neutral_end = cursor;
5336 else
5337 neutral_end.pPara = NULL;
5339 cursor.nOffset++;
5343 cursor.nOffset = 0;
5344 if (!ME_NextRun(&cursor.pPara, &cursor.pRun, TRUE))
5345 goto done;
5348 done:
5349 if (candidateStarted)
5351 if (space_end.pPara)
5352 *candidate_max = space_end;
5353 else if (neutral_end.pPara)
5354 *candidate_max = neutral_end;
5355 else
5356 *candidate_max = cursor;
5357 return TRUE;
5359 *candidate_max = *candidate_min = cursor;
5360 return FALSE;
5364 * This proc evaluates the selection and returns TRUE if it can be considered an URL
5366 static BOOL ME_IsCandidateAnURL(ME_TextEditor *editor, const ME_Cursor *start, int nChars)
5368 #define MAX_PREFIX_LEN 9
5369 struct prefix_s {
5370 const WCHAR text[MAX_PREFIX_LEN];
5371 int length;
5372 }prefixes[] = {
5373 {{'p','r','o','s','p','e','r','o',':'}, 9},
5374 {{'t','e','l','n','e','t',':'}, 7},
5375 {{'g','o','p','h','e','r',':'}, 7},
5376 {{'m','a','i','l','t','o',':'}, 7},
5377 {{'h','t','t','p','s',':'}, 6},
5378 {{'f','i','l','e',':'}, 5},
5379 {{'n','e','w','s',':'}, 5},
5380 {{'w','a','i','s',':'}, 5},
5381 {{'n','n','t','p',':'}, 5},
5382 {{'h','t','t','p',':'}, 5},
5383 {{'w','w','w','.'}, 4},
5384 {{'f','t','p',':'}, 4},
5386 WCHAR bufferW[MAX_PREFIX_LEN + 1];
5387 unsigned int i;
5389 ME_GetTextW(editor, bufferW, MAX_PREFIX_LEN, start, nChars, FALSE, FALSE);
5390 for (i = 0; i < sizeof(prefixes) / sizeof(*prefixes); i++)
5392 if (nChars < prefixes[i].length) continue;
5393 if (!memcmp(prefixes[i].text, bufferW, prefixes[i].length * sizeof(WCHAR)))
5394 return TRUE;
5396 return FALSE;
5397 #undef MAX_PREFIX_LEN
5401 * This proc walks through the indicated selection and evaluates whether each
5402 * section identified by ME_FindNextURLCandidate and in-between sections have
5403 * their proper CFE_LINK attributes set or unset. If the CFE_LINK attribute is
5404 * not what it is supposed to be, this proc sets or unsets it as appropriate.
5406 * Since this function can cause runs to be split, do not depend on the value
5407 * of the start cursor at the end of the function.
5409 * nChars may be set to INT_MAX to update to the end of the text.
5411 * Returns TRUE if at least one section was modified.
5413 static BOOL ME_UpdateLinkAttribute(ME_TextEditor *editor, ME_Cursor *start, int nChars)
5415 BOOL modified = FALSE;
5416 ME_Cursor startCur = *start;
5418 if (!editor->AutoURLDetect_bEnable) return FALSE;
5422 CHARFORMAT2W link;
5423 ME_Cursor candidateStart, candidateEnd;
5425 if (ME_FindNextURLCandidate(editor, &startCur, nChars,
5426 &candidateStart, &candidateEnd))
5428 /* Section before candidate is not an URL */
5429 int cMin = ME_GetCursorOfs(&candidateStart);
5430 int cMax = ME_GetCursorOfs(&candidateEnd);
5432 if (!ME_IsCandidateAnURL(editor, &candidateStart, cMax - cMin))
5433 candidateStart = candidateEnd;
5434 nChars -= cMax - ME_GetCursorOfs(&startCur);
5436 else
5438 /* No more candidates until end of selection */
5439 nChars = 0;
5442 if (startCur.pRun != candidateStart.pRun ||
5443 startCur.nOffset != candidateStart.nOffset)
5445 /* CFE_LINK effect should be consistently unset */
5446 link.cbSize = sizeof(link);
5447 ME_GetCharFormat(editor, &startCur, &candidateStart, &link);
5448 if (!(link.dwMask & CFM_LINK) || (link.dwEffects & CFE_LINK))
5450 /* CFE_LINK must be unset from this range */
5451 memset(&link, 0, sizeof(CHARFORMAT2W));
5452 link.cbSize = sizeof(link);
5453 link.dwMask = CFM_LINK;
5454 link.dwEffects = 0;
5455 ME_SetCharFormat(editor, &startCur, &candidateStart, &link);
5456 /* Update candidateEnd since setting character formats may split
5457 * runs, which can cause a cursor to be at an invalid offset within
5458 * a split run. */
5459 while (candidateEnd.nOffset >= candidateEnd.pRun->member.run.len)
5461 candidateEnd.nOffset -= candidateEnd.pRun->member.run.len;
5462 candidateEnd.pRun = ME_FindItemFwd(candidateEnd.pRun, diRun);
5464 modified = TRUE;
5467 if (candidateStart.pRun != candidateEnd.pRun ||
5468 candidateStart.nOffset != candidateEnd.nOffset)
5470 /* CFE_LINK effect should be consistently set */
5471 link.cbSize = sizeof(link);
5472 ME_GetCharFormat(editor, &candidateStart, &candidateEnd, &link);
5473 if (!(link.dwMask & CFM_LINK) || !(link.dwEffects & CFE_LINK))
5475 /* CFE_LINK must be set on this range */
5476 memset(&link, 0, sizeof(CHARFORMAT2W));
5477 link.cbSize = sizeof(link);
5478 link.dwMask = CFM_LINK;
5479 link.dwEffects = CFE_LINK;
5480 ME_SetCharFormat(editor, &candidateStart, &candidateEnd, &link);
5481 modified = TRUE;
5484 startCur = candidateEnd;
5485 } while (nChars > 0);
5486 return modified;