riched20: Use the implementation of EM_PASTESPECIAL for EM_CANPASTE.
[wine.git] / dlls / riched20 / editor.c
blobb66d8e8a44032ddcc880a309bf20fcd14e9c9be5
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 struct paste_format
2246 FORMATETC fmt;
2247 HRESULT (*paste)(ME_TextEditor *, FORMATETC *, STGMEDIUM *);
2248 const WCHAR *name;
2249 } paste_formats[] =
2251 {{ -1, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, paste_rtf, rtfW },
2252 {{ CF_UNICODETEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, paste_text },
2253 {{ 0 }}
2256 static void init_paste_formats(void)
2258 struct paste_format *format;
2259 static int done;
2261 if (!done)
2263 for (format = paste_formats; format->fmt.cfFormat; format++)
2265 if (format->name)
2266 format->fmt.cfFormat = RegisterClipboardFormatW( format->name );
2268 done = 1;
2272 static BOOL paste_special(ME_TextEditor *editor, UINT cf, REPASTESPECIAL *ps, BOOL check_only)
2274 HRESULT hr;
2275 STGMEDIUM med;
2276 struct paste_format *format;
2277 IDataObject *data;
2279 init_paste_formats();
2281 if (ps && ps->dwAspect != DVASPECT_CONTENT)
2282 FIXME("Ignoring aspect %x\n", ps->dwAspect);
2284 hr = OleGetClipboard( &data );
2285 if (hr != S_OK) return FALSE;
2287 if (cf == CF_TEXT) cf = CF_UNICODETEXT;
2289 hr = S_FALSE;
2290 for (format = paste_formats; format->fmt.cfFormat; format++)
2292 if (cf && cf != format->fmt.cfFormat) continue;
2293 hr = IDataObject_QueryGetData( data, &format->fmt );
2294 if (hr == S_OK)
2296 if (!check_only)
2298 hr = IDataObject_GetData( data, &format->fmt, &med );
2299 if (hr != S_OK) goto done;
2300 hr = format->paste( editor, &format->fmt, &med );
2302 break;
2306 done:
2307 IDataObject_Release( data );
2309 return hr == S_OK;
2312 static BOOL ME_Copy(ME_TextEditor *editor, const ME_Cursor *start, int nChars)
2314 LPDATAOBJECT dataObj = NULL;
2315 HRESULT hr = S_OK;
2317 if (editor->cPasswordMask)
2318 return FALSE; /* Copying or Cutting masked text isn't allowed */
2320 if(editor->lpOleCallback)
2322 CHARRANGE range;
2323 range.cpMin = ME_GetCursorOfs(start);
2324 range.cpMax = range.cpMin + nChars;
2325 hr = IRichEditOleCallback_GetClipboardData(editor->lpOleCallback, &range, RECO_COPY, &dataObj);
2327 if(FAILED(hr) || !dataObj)
2328 hr = ME_GetDataObject(editor, start, nChars, &dataObj);
2329 if(SUCCEEDED(hr)) {
2330 hr = OleSetClipboard(dataObj);
2331 IDataObject_Release(dataObj);
2333 return SUCCEEDED(hr);
2336 /* helper to send a msg filter notification */
2337 static BOOL
2338 ME_FilterEvent(ME_TextEditor *editor, UINT msg, WPARAM* wParam, LPARAM* lParam)
2340 MSGFILTER msgf;
2342 if (!editor->hWnd || !editor->hwndParent) return FALSE;
2343 msgf.nmhdr.hwndFrom = editor->hWnd;
2344 msgf.nmhdr.idFrom = GetWindowLongW(editor->hWnd, GWLP_ID);
2345 msgf.nmhdr.code = EN_MSGFILTER;
2346 msgf.msg = msg;
2347 msgf.wParam = *wParam;
2348 msgf.lParam = *lParam;
2349 if (SendMessageW(editor->hwndParent, WM_NOTIFY, msgf.nmhdr.idFrom, (LPARAM)&msgf))
2350 return FALSE;
2351 *wParam = msgf.wParam;
2352 *lParam = msgf.lParam;
2353 msgf.wParam = *wParam;
2355 return TRUE;
2358 static void ME_UpdateSelectionLinkAttribute(ME_TextEditor *editor)
2360 ME_DisplayItem *startPara, *endPara;
2361 ME_DisplayItem *prev_para;
2362 ME_Cursor *from, *to;
2363 ME_Cursor start;
2364 int nChars;
2366 if (!editor->AutoURLDetect_bEnable) return;
2368 ME_GetSelection(editor, &from, &to);
2370 /* Find paragraph previous to the one that contains start cursor */
2371 startPara = from->pPara;
2372 prev_para = startPara->member.para.prev_para;
2373 if (prev_para->type == diParagraph) startPara = prev_para;
2375 /* Find paragraph that contains end cursor */
2376 endPara = to->pPara->member.para.next_para;
2378 start.pPara = startPara;
2379 start.pRun = ME_FindItemFwd(startPara, diRun);
2380 start.nOffset = 0;
2381 nChars = endPara->member.para.nCharOfs - startPara->member.para.nCharOfs;
2383 ME_UpdateLinkAttribute(editor, &start, nChars);
2386 static BOOL
2387 ME_KeyDown(ME_TextEditor *editor, WORD nKey)
2389 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2390 BOOL shift_is_down = GetKeyState(VK_SHIFT) & 0x8000;
2392 if (editor->bMouseCaptured)
2393 return FALSE;
2394 if (nKey != VK_SHIFT && nKey != VK_CONTROL && nKey != VK_MENU)
2395 editor->nSelectionType = stPosition;
2397 switch (nKey)
2399 case VK_LEFT:
2400 case VK_RIGHT:
2401 case VK_HOME:
2402 case VK_END:
2403 editor->nUDArrowX = -1;
2404 /* fall through */
2405 case VK_UP:
2406 case VK_DOWN:
2407 case VK_PRIOR:
2408 case VK_NEXT:
2409 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
2410 ME_ArrowKey(editor, nKey, shift_is_down, ctrl_is_down);
2411 return TRUE;
2412 case VK_BACK:
2413 case VK_DELETE:
2414 editor->nUDArrowX = -1;
2415 /* FIXME backspace and delete aren't the same, they act different wrt paragraph style of the merged paragraph */
2416 if (editor->styleFlags & ES_READONLY)
2417 return FALSE;
2418 if (ME_IsSelection(editor))
2420 ME_DeleteSelection(editor);
2421 ME_CommitUndo(editor);
2423 else if (nKey == VK_DELETE)
2425 /* Delete stops group typing.
2426 * (See MSDN remarks on EM_STOPGROUPTYPING message) */
2427 ME_DeleteTextAtCursor(editor, 1, 1);
2428 ME_CommitUndo(editor);
2430 else if (ME_ArrowKey(editor, VK_LEFT, FALSE, FALSE))
2432 BOOL bDeletionSucceeded;
2433 /* Backspace can be grouped for a single undo */
2434 ME_ContinueCoalescingTransaction(editor);
2435 bDeletionSucceeded = ME_DeleteTextAtCursor(editor, 1, 1);
2436 if (!bDeletionSucceeded && !editor->bEmulateVersion10) { /* v4.1 */
2437 /* Deletion was prevented so the cursor is moved back to where it was.
2438 * (e.g. this happens when trying to delete cell boundaries)
2440 ME_ArrowKey(editor, VK_RIGHT, FALSE, FALSE);
2442 ME_CommitCoalescingUndo(editor);
2444 else
2445 return TRUE;
2446 ME_MoveCursorFromTableRowStartParagraph(editor);
2447 ME_UpdateSelectionLinkAttribute(editor);
2448 ME_UpdateRepaint(editor, FALSE);
2449 ME_SendRequestResize(editor, FALSE);
2450 return TRUE;
2451 case VK_RETURN:
2452 if (editor->bDialogMode)
2454 if (ctrl_is_down)
2455 return TRUE;
2457 if (!(editor->styleFlags & ES_WANTRETURN))
2459 if (editor->hwndParent)
2461 DWORD dw;
2462 dw = SendMessageW(editor->hwndParent, DM_GETDEFID, 0, 0);
2463 if (HIWORD(dw) == DC_HASDEFID)
2465 HWND hwDefCtrl = GetDlgItem(editor->hwndParent, LOWORD(dw));
2466 if (hwDefCtrl)
2468 SendMessageW(editor->hwndParent, WM_NEXTDLGCTL, (WPARAM)hwDefCtrl, TRUE);
2469 PostMessageW(hwDefCtrl, WM_KEYDOWN, VK_RETURN, 0);
2473 return TRUE;
2477 if (editor->styleFlags & ES_MULTILINE)
2479 ME_Cursor cursor = editor->pCursors[0];
2480 ME_DisplayItem *para = cursor.pPara;
2481 int from, to;
2482 const WCHAR endl = '\r';
2483 const WCHAR endlv10[] = {'\r','\n'};
2484 ME_Style *style, *eop_style;
2486 if (editor->styleFlags & ES_READONLY) {
2487 MessageBeep(MB_ICONERROR);
2488 return TRUE;
2491 ME_GetSelectionOfs(editor, &from, &to);
2492 if (editor->nTextLimit > ME_GetTextLength(editor) - (to-from))
2494 if (!editor->bEmulateVersion10) { /* v4.1 */
2495 if (para->member.para.nFlags & MEPF_ROWEND) {
2496 /* Add a new table row after this row. */
2497 para = ME_AppendTableRow(editor, para);
2498 para = para->member.para.next_para;
2499 editor->pCursors[0].pPara = para;
2500 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2501 editor->pCursors[0].nOffset = 0;
2502 editor->pCursors[1] = editor->pCursors[0];
2503 ME_CommitUndo(editor);
2504 ME_CheckTablesForCorruption(editor);
2505 ME_UpdateRepaint(editor, FALSE);
2506 return TRUE;
2508 else if (para == editor->pCursors[1].pPara &&
2509 cursor.nOffset + cursor.pRun->member.run.nCharOfs == 0 &&
2510 para->member.para.prev_para->member.para.nFlags & MEPF_ROWSTART &&
2511 !para->member.para.prev_para->member.para.nCharOfs)
2513 /* Insert a newline before the table. */
2514 para = para->member.para.prev_para;
2515 para->member.para.nFlags &= ~MEPF_ROWSTART;
2516 editor->pCursors[0].pPara = para;
2517 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2518 editor->pCursors[1] = editor->pCursors[0];
2519 ME_InsertTextFromCursor(editor, 0, &endl, 1,
2520 editor->pCursors[0].pRun->member.run.style);
2521 para = editor->pBuffer->pFirst->member.para.next_para;
2522 ME_SetDefaultParaFormat(editor, &para->member.para.fmt);
2523 para->member.para.nFlags = MEPF_REWRAP;
2524 editor->pCursors[0].pPara = para;
2525 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2526 editor->pCursors[1] = editor->pCursors[0];
2527 para->member.para.next_para->member.para.nFlags |= MEPF_ROWSTART;
2528 ME_CommitCoalescingUndo(editor);
2529 ME_CheckTablesForCorruption(editor);
2530 ME_UpdateRepaint(editor, FALSE);
2531 return TRUE;
2533 } else { /* v1.0 - 3.0 */
2534 ME_DisplayItem *para = cursor.pPara;
2535 if (ME_IsInTable(para))
2537 if (cursor.pRun->member.run.nFlags & MERF_ENDPARA)
2539 if (from == to) {
2540 ME_ContinueCoalescingTransaction(editor);
2541 para = ME_AppendTableRow(editor, para);
2542 editor->pCursors[0].pPara = para;
2543 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2544 editor->pCursors[0].nOffset = 0;
2545 editor->pCursors[1] = editor->pCursors[0];
2546 ME_CommitCoalescingUndo(editor);
2547 ME_UpdateRepaint(editor, FALSE);
2548 return TRUE;
2550 } else {
2551 ME_ContinueCoalescingTransaction(editor);
2552 if (cursor.pRun->member.run.nCharOfs + cursor.nOffset == 0 &&
2553 !ME_IsInTable(para->member.para.prev_para))
2555 /* Insert newline before table */
2556 cursor.pRun = ME_FindItemBack(para, diRun);
2557 if (cursor.pRun) {
2558 editor->pCursors[0].pRun = cursor.pRun;
2559 editor->pCursors[0].pPara = para->member.para.prev_para;
2561 editor->pCursors[0].nOffset = 0;
2562 editor->pCursors[1] = editor->pCursors[0];
2563 ME_InsertTextFromCursor(editor, 0, &endl, 1,
2564 editor->pCursors[0].pRun->member.run.style);
2565 } else {
2566 editor->pCursors[1] = editor->pCursors[0];
2567 para = ME_AppendTableRow(editor, para);
2568 editor->pCursors[0].pPara = para;
2569 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2570 editor->pCursors[0].nOffset = 0;
2571 editor->pCursors[1] = editor->pCursors[0];
2573 ME_CommitCoalescingUndo(editor);
2574 ME_UpdateRepaint(editor, FALSE);
2575 return TRUE;
2580 style = ME_GetInsertStyle(editor, 0);
2582 /* Normally the new eop style is the insert style, however in a list it is copied from the existing
2583 eop style (this prevents the list label style changing when the new eop is inserted).
2584 No extra ref is taken here on eop_style. */
2585 if (para->member.para.fmt.wNumbering)
2586 eop_style = para->member.para.eop_run->style;
2587 else
2588 eop_style = style;
2589 ME_ContinueCoalescingTransaction(editor);
2590 if (shift_is_down)
2591 ME_InsertEndRowFromCursor(editor, 0);
2592 else
2593 if (!editor->bEmulateVersion10)
2594 ME_InsertTextFromCursor(editor, 0, &endl, 1, eop_style);
2595 else
2596 ME_InsertTextFromCursor(editor, 0, endlv10, 2, eop_style);
2597 ME_CommitCoalescingUndo(editor);
2598 SetCursor(NULL);
2600 ME_UpdateSelectionLinkAttribute(editor);
2601 ME_UpdateRepaint(editor, FALSE);
2602 ME_SaveTempStyle(editor, style); /* set the temp insert style for the new para */
2603 ME_ReleaseStyle(style);
2605 return TRUE;
2607 break;
2608 case VK_ESCAPE:
2609 if (editor->bDialogMode && editor->hwndParent)
2610 PostMessageW(editor->hwndParent, WM_CLOSE, 0, 0);
2611 return TRUE;
2612 case VK_TAB:
2613 if (editor->bDialogMode && editor->hwndParent)
2614 SendMessageW(editor->hwndParent, WM_NEXTDLGCTL, shift_is_down, 0);
2615 return TRUE;
2616 case 'A':
2617 if (ctrl_is_down)
2619 handle_EM_EXSETSEL( editor, 0, -1 );
2620 return TRUE;
2622 break;
2623 case 'V':
2624 if (ctrl_is_down)
2625 return paste_special( editor, 0, NULL, FALSE );
2626 break;
2627 case 'C':
2628 case 'X':
2629 if (ctrl_is_down)
2631 BOOL result;
2632 int nOfs, nChars;
2633 int nStartCur = ME_GetSelectionOfs(editor, &nOfs, &nChars);
2634 ME_Cursor *selStart = &editor->pCursors[nStartCur];
2636 nChars -= nOfs;
2637 result = ME_Copy(editor, selStart, nChars);
2638 if (result && nKey == 'X')
2640 ME_InternalDeleteText(editor, selStart, nChars, FALSE);
2641 ME_CommitUndo(editor);
2642 ME_UpdateRepaint(editor, TRUE);
2644 return result;
2646 break;
2647 case 'Z':
2648 if (ctrl_is_down)
2650 ME_Undo(editor);
2651 return TRUE;
2653 break;
2654 case 'Y':
2655 if (ctrl_is_down)
2657 ME_Redo(editor);
2658 return TRUE;
2660 break;
2662 default:
2663 if (nKey != VK_SHIFT && nKey != VK_CONTROL && nKey && nKey != VK_MENU)
2664 editor->nUDArrowX = -1;
2665 if (ctrl_is_down)
2667 if (nKey == 'W')
2669 CHARFORMAT2W chf;
2670 char buf[2048];
2671 chf.cbSize = sizeof(chf);
2673 ME_GetSelectionCharFormat(editor, &chf);
2674 ME_DumpStyleToBuf(&chf, buf);
2675 MessageBoxA(NULL, buf, "Style dump", MB_OK);
2677 if (nKey == 'Q')
2679 ME_CheckCharOffsets(editor);
2683 return FALSE;
2686 static LRESULT ME_Char(ME_TextEditor *editor, WPARAM charCode,
2687 LPARAM flags, BOOL unicode)
2689 WCHAR wstr;
2691 if (editor->bMouseCaptured)
2692 return 0;
2694 if (unicode)
2695 wstr = (WCHAR)charCode;
2696 else
2698 CHAR charA = charCode;
2699 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &wstr, 1);
2702 if (editor->styleFlags & ES_READONLY) {
2703 MessageBeep(MB_ICONERROR);
2704 return 0; /* FIXME really 0 ? */
2707 if ((unsigned)wstr >= ' ' || wstr == '\t')
2709 ME_Cursor cursor = editor->pCursors[0];
2710 ME_DisplayItem *para = cursor.pPara;
2711 int from, to;
2712 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2713 ME_GetSelectionOfs(editor, &from, &to);
2714 if (wstr == '\t' &&
2715 /* v4.1 allows tabs to be inserted with ctrl key down */
2716 !(ctrl_is_down && !editor->bEmulateVersion10))
2718 ME_DisplayItem *para;
2719 BOOL bSelectedRow = FALSE;
2721 para = cursor.pPara;
2722 if (ME_IsSelection(editor) &&
2723 cursor.pRun->member.run.nCharOfs + cursor.nOffset == 0 &&
2724 to == ME_GetCursorOfs(&editor->pCursors[0]) &&
2725 para->member.para.prev_para->type == diParagraph)
2727 para = para->member.para.prev_para;
2728 bSelectedRow = TRUE;
2730 if (ME_IsInTable(para))
2732 ME_TabPressedInTable(editor, bSelectedRow);
2733 ME_CommitUndo(editor);
2734 return 0;
2736 } else if (!editor->bEmulateVersion10) { /* v4.1 */
2737 if (para->member.para.nFlags & MEPF_ROWEND) {
2738 if (from == to) {
2739 para = para->member.para.next_para;
2740 if (para->member.para.nFlags & MEPF_ROWSTART)
2741 para = para->member.para.next_para;
2742 editor->pCursors[0].pPara = para;
2743 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2744 editor->pCursors[0].nOffset = 0;
2745 editor->pCursors[1] = editor->pCursors[0];
2748 } else { /* v1.0 - 3.0 */
2749 if (ME_IsInTable(cursor.pRun) &&
2750 cursor.pRun->member.run.nFlags & MERF_ENDPARA &&
2751 from == to)
2753 /* Text should not be inserted at the end of the table. */
2754 MessageBeep(-1);
2755 return 0;
2758 /* FIXME maybe it would make sense to call EM_REPLACESEL instead ? */
2759 /* WM_CHAR is restricted to nTextLimit */
2760 if(editor->nTextLimit > ME_GetTextLength(editor) - (to-from))
2762 ME_Style *style = ME_GetInsertStyle(editor, 0);
2763 ME_ContinueCoalescingTransaction(editor);
2764 ME_InsertTextFromCursor(editor, 0, &wstr, 1, style);
2765 ME_ReleaseStyle(style);
2766 ME_CommitCoalescingUndo(editor);
2767 ITextHost_TxSetCursor(editor->texthost, NULL, FALSE);
2770 ME_UpdateSelectionLinkAttribute(editor);
2771 ME_UpdateRepaint(editor, FALSE);
2773 return 0;
2776 /* Process the message and calculate the new click count.
2778 * returns: The click count if it is mouse down event, else returns 0. */
2779 static int ME_CalculateClickCount(ME_TextEditor *editor, UINT msg, WPARAM wParam,
2780 LPARAM lParam)
2782 static int clickNum = 0;
2783 if (msg < WM_MOUSEFIRST || msg > WM_MOUSELAST)
2784 return 0;
2786 if ((msg == WM_LBUTTONDBLCLK) ||
2787 (msg == WM_RBUTTONDBLCLK) ||
2788 (msg == WM_MBUTTONDBLCLK) ||
2789 (msg == WM_XBUTTONDBLCLK))
2791 msg -= (WM_LBUTTONDBLCLK - WM_LBUTTONDOWN);
2794 if ((msg == WM_LBUTTONDOWN) ||
2795 (msg == WM_RBUTTONDOWN) ||
2796 (msg == WM_MBUTTONDOWN) ||
2797 (msg == WM_XBUTTONDOWN))
2799 static MSG prevClickMsg;
2800 MSG clickMsg;
2801 /* Compare the editor instead of the hwnd so that the this
2802 * can still be done for windowless richedit controls. */
2803 clickMsg.hwnd = (HWND)editor;
2804 clickMsg.message = msg;
2805 clickMsg.wParam = wParam;
2806 clickMsg.lParam = lParam;
2807 clickMsg.time = GetMessageTime();
2808 clickMsg.pt.x = (short)LOWORD(lParam);
2809 clickMsg.pt.y = (short)HIWORD(lParam);
2810 if ((clickNum != 0) &&
2811 (clickMsg.message == prevClickMsg.message) &&
2812 (clickMsg.hwnd == prevClickMsg.hwnd) &&
2813 (clickMsg.wParam == prevClickMsg.wParam) &&
2814 (clickMsg.time - prevClickMsg.time < GetDoubleClickTime()) &&
2815 (abs(clickMsg.pt.x - prevClickMsg.pt.x) < GetSystemMetrics(SM_CXDOUBLECLK)/2) &&
2816 (abs(clickMsg.pt.y - prevClickMsg.pt.y) < GetSystemMetrics(SM_CYDOUBLECLK)/2))
2818 clickNum++;
2819 } else {
2820 clickNum = 1;
2822 prevClickMsg = clickMsg;
2823 } else {
2824 return 0;
2826 return clickNum;
2829 static BOOL is_link( ME_Run *run )
2831 return (run->style->fmt.dwMask & CFM_LINK) && (run->style->fmt.dwEffects & CFE_LINK);
2834 static BOOL ME_SetCursor(ME_TextEditor *editor)
2836 ME_Cursor cursor;
2837 POINT pt;
2838 BOOL isExact;
2839 SCROLLBARINFO sbi;
2840 DWORD messagePos = GetMessagePos();
2841 pt.x = (short)LOWORD(messagePos);
2842 pt.y = (short)HIWORD(messagePos);
2844 if (editor->hWnd)
2846 sbi.cbSize = sizeof(sbi);
2847 GetScrollBarInfo(editor->hWnd, OBJID_HSCROLL, &sbi);
2848 if (!(sbi.rgstate[0] & (STATE_SYSTEM_INVISIBLE|STATE_SYSTEM_OFFSCREEN)) &&
2849 PtInRect(&sbi.rcScrollBar, pt))
2851 ITextHost_TxSetCursor(editor->texthost,
2852 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2853 return TRUE;
2855 sbi.cbSize = sizeof(sbi);
2856 GetScrollBarInfo(editor->hWnd, OBJID_VSCROLL, &sbi);
2857 if (!(sbi.rgstate[0] & (STATE_SYSTEM_INVISIBLE|STATE_SYSTEM_OFFSCREEN)) &&
2858 PtInRect(&sbi.rcScrollBar, pt))
2860 ITextHost_TxSetCursor(editor->texthost,
2861 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2862 return TRUE;
2865 ITextHost_TxScreenToClient(editor->texthost, &pt);
2867 if (editor->nSelectionType == stLine && editor->bMouseCaptured) {
2868 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2869 return TRUE;
2871 if (!editor->bEmulateVersion10 /* v4.1 */ &&
2872 pt.y < editor->rcFormat.top &&
2873 pt.x < editor->rcFormat.left)
2875 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2876 return TRUE;
2878 if (pt.y < editor->rcFormat.top || pt.y > editor->rcFormat.bottom)
2880 if (editor->bEmulateVersion10) /* v1.0 - 3.0 */
2881 ITextHost_TxSetCursor(editor->texthost,
2882 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2883 else /* v4.1 */
2884 ITextHost_TxSetCursor(editor->texthost,
2885 LoadCursorW(NULL, (WCHAR*)IDC_IBEAM), TRUE);
2886 return TRUE;
2888 if (pt.x < editor->rcFormat.left)
2890 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2891 return TRUE;
2893 ME_CharFromPos(editor, pt.x, pt.y, &cursor, &isExact);
2894 if (isExact)
2896 ME_Run *run;
2898 run = &cursor.pRun->member.run;
2899 if (is_link( run ))
2901 ITextHost_TxSetCursor(editor->texthost,
2902 LoadCursorW(NULL, (WCHAR*)IDC_HAND),
2903 FALSE);
2904 return TRUE;
2907 if (ME_IsSelection(editor))
2909 int selStart, selEnd;
2910 int offset = ME_GetCursorOfs(&cursor);
2912 ME_GetSelectionOfs(editor, &selStart, &selEnd);
2913 if (selStart <= offset && selEnd >= offset) {
2914 ITextHost_TxSetCursor(editor->texthost,
2915 LoadCursorW(NULL, (WCHAR*)IDC_ARROW),
2916 FALSE);
2917 return TRUE;
2921 ITextHost_TxSetCursor(editor->texthost,
2922 LoadCursorW(NULL, (WCHAR*)IDC_IBEAM), TRUE);
2923 return TRUE;
2926 static void ME_SetDefaultFormatRect(ME_TextEditor *editor)
2928 ITextHost_TxGetClientRect(editor->texthost, &editor->rcFormat);
2929 editor->rcFormat.top += editor->exStyleFlags & WS_EX_CLIENTEDGE ? 1 : 0;
2930 editor->rcFormat.left += 1 + editor->selofs;
2931 editor->rcFormat.right -= 1;
2934 static BOOL ME_ShowContextMenu(ME_TextEditor *editor, int x, int y)
2936 CHARRANGE selrange;
2937 HMENU menu;
2938 int seltype = 0;
2939 if(!editor->lpOleCallback || !editor->hWnd)
2940 return FALSE;
2941 ME_GetSelectionOfs(editor, &selrange.cpMin, &selrange.cpMax);
2942 if(selrange.cpMin == selrange.cpMax)
2943 seltype |= SEL_EMPTY;
2944 else
2946 /* FIXME: Handle objects */
2947 seltype |= SEL_TEXT;
2948 if(selrange.cpMax-selrange.cpMin > 1)
2949 seltype |= SEL_MULTICHAR;
2951 if(SUCCEEDED(IRichEditOleCallback_GetContextMenu(editor->lpOleCallback, seltype, NULL, &selrange, &menu)))
2953 TrackPopupMenu(menu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, x, y, 0, editor->hwndParent, NULL);
2954 DestroyMenu(menu);
2956 return TRUE;
2959 ME_TextEditor *ME_MakeEditor(ITextHost *texthost, BOOL bEmulateVersion10)
2961 ME_TextEditor *ed = ALLOC_OBJ(ME_TextEditor);
2962 int i;
2963 DWORD props;
2964 LONG selbarwidth;
2966 ed->hWnd = NULL;
2967 ed->hwndParent = NULL;
2968 ed->sizeWindow.cx = ed->sizeWindow.cy = 0;
2969 ed->texthost = texthost;
2970 ed->reOle = NULL;
2971 ed->bEmulateVersion10 = bEmulateVersion10;
2972 ed->styleFlags = 0;
2973 ed->exStyleFlags = 0;
2974 ITextHost_TxGetPropertyBits(texthost,
2975 (TXTBIT_RICHTEXT|TXTBIT_MULTILINE|
2976 TXTBIT_READONLY|TXTBIT_USEPASSWORD|
2977 TXTBIT_HIDESELECTION|TXTBIT_SAVESELECTION|
2978 TXTBIT_AUTOWORDSEL|TXTBIT_VERTICAL|
2979 TXTBIT_WORDWRAP|TXTBIT_DISABLEDRAG),
2980 &props);
2981 ITextHost_TxGetScrollBars(texthost, &ed->styleFlags);
2982 ed->styleFlags &= (WS_VSCROLL|WS_HSCROLL|ES_AUTOVSCROLL|
2983 ES_AUTOHSCROLL|ES_DISABLENOSCROLL);
2984 ed->pBuffer = ME_MakeText();
2985 ed->nZoomNumerator = ed->nZoomDenominator = 0;
2986 ed->nAvailWidth = 0; /* wrap to client area */
2987 ME_MakeFirstParagraph(ed);
2988 /* The four cursors are for:
2989 * 0 - The position where the caret is shown
2990 * 1 - The anchored end of the selection (for normal selection)
2991 * 2 & 3 - The anchored start and end respectively for word, line,
2992 * or paragraph selection.
2994 ed->nCursors = 4;
2995 ed->pCursors = ALLOC_N_OBJ(ME_Cursor, ed->nCursors);
2996 ME_SetCursorToStart(ed, &ed->pCursors[0]);
2997 ed->pCursors[1] = ed->pCursors[0];
2998 ed->pCursors[2] = ed->pCursors[0];
2999 ed->pCursors[3] = ed->pCursors[1];
3000 ed->nLastTotalLength = ed->nTotalLength = 0;
3001 ed->nLastTotalWidth = ed->nTotalWidth = 0;
3002 ed->nUDArrowX = -1;
3003 ed->rgbBackColor = -1;
3004 ed->hbrBackground = GetSysColorBrush(COLOR_WINDOW);
3005 ed->bCaretAtEnd = FALSE;
3006 ed->nEventMask = 0;
3007 ed->nModifyStep = 0;
3008 ed->nTextLimit = TEXT_LIMIT_DEFAULT;
3009 list_init( &ed->undo_stack );
3010 list_init( &ed->redo_stack );
3011 ed->nUndoStackSize = 0;
3012 ed->nUndoLimit = STACK_SIZE_DEFAULT;
3013 ed->nUndoMode = umAddToUndo;
3014 ed->nParagraphs = 1;
3015 ed->nLastSelStart = ed->nLastSelEnd = 0;
3016 ed->pLastSelStartPara = ed->pLastSelEndPara = ed->pCursors[0].pPara;
3017 ed->bHideSelection = FALSE;
3018 ed->pfnWordBreak = NULL;
3019 ed->lpOleCallback = NULL;
3020 ed->mode = TM_MULTILEVELUNDO | TM_MULTICODEPAGE;
3021 ed->mode |= (props & TXTBIT_RICHTEXT) ? TM_RICHTEXT : TM_PLAINTEXT;
3022 ed->AutoURLDetect_bEnable = FALSE;
3023 ed->bHaveFocus = FALSE;
3024 ed->bDialogMode = FALSE;
3025 ed->bMouseCaptured = FALSE;
3026 for (i=0; i<HFONT_CACHE_SIZE; i++)
3028 ed->pFontCache[i].nRefs = 0;
3029 ed->pFontCache[i].nAge = 0;
3030 ed->pFontCache[i].hFont = NULL;
3033 ME_CheckCharOffsets(ed);
3034 SetRectEmpty(&ed->rcFormat);
3035 ed->bDefaultFormatRect = TRUE;
3036 ITextHost_TxGetSelectionBarWidth(ed->texthost, &selbarwidth);
3037 if (selbarwidth) {
3038 /* FIXME: Convert selbarwidth from HIMETRIC to pixels */
3039 ed->selofs = SELECTIONBAR_WIDTH;
3040 ed->styleFlags |= ES_SELECTIONBAR;
3041 } else {
3042 ed->selofs = 0;
3044 ed->nSelectionType = stPosition;
3046 ed->cPasswordMask = 0;
3047 if (props & TXTBIT_USEPASSWORD)
3048 ITextHost_TxGetPasswordChar(texthost, &ed->cPasswordMask);
3050 if (props & TXTBIT_AUTOWORDSEL)
3051 ed->styleFlags |= ECO_AUTOWORDSELECTION;
3052 if (props & TXTBIT_MULTILINE) {
3053 ed->styleFlags |= ES_MULTILINE;
3054 ed->bWordWrap = (props & TXTBIT_WORDWRAP) != 0;
3055 } else {
3056 ed->bWordWrap = FALSE;
3058 if (props & TXTBIT_READONLY)
3059 ed->styleFlags |= ES_READONLY;
3060 if (!(props & TXTBIT_HIDESELECTION))
3061 ed->styleFlags |= ES_NOHIDESEL;
3062 if (props & TXTBIT_SAVESELECTION)
3063 ed->styleFlags |= ES_SAVESEL;
3064 if (props & TXTBIT_VERTICAL)
3065 ed->styleFlags |= ES_VERTICAL;
3066 if (props & TXTBIT_DISABLEDRAG)
3067 ed->styleFlags |= ES_NOOLEDRAGDROP;
3069 ed->notified_cr.cpMin = ed->notified_cr.cpMax = 0;
3071 /* Default scrollbar information */
3072 ed->vert_si.cbSize = sizeof(SCROLLINFO);
3073 ed->vert_si.nMin = 0;
3074 ed->vert_si.nMax = 0;
3075 ed->vert_si.nPage = 0;
3076 ed->vert_si.nPos = 0;
3078 ed->horz_si.cbSize = sizeof(SCROLLINFO);
3079 ed->horz_si.nMin = 0;
3080 ed->horz_si.nMax = 0;
3081 ed->horz_si.nPage = 0;
3082 ed->horz_si.nPos = 0;
3084 ed->wheel_remain = 0;
3086 list_init( &ed->style_list );
3087 OleInitialize(NULL);
3089 return ed;
3092 void ME_DestroyEditor(ME_TextEditor *editor)
3094 ME_DisplayItem *pFirst = editor->pBuffer->pFirst;
3095 ME_DisplayItem *p = pFirst, *pNext = NULL;
3096 ME_Style *s, *cursor2;
3097 int i;
3099 ME_ClearTempStyle(editor);
3100 ME_EmptyUndoStack(editor);
3101 while(p) {
3102 pNext = p->next;
3103 ME_DestroyDisplayItem(p);
3104 p = pNext;
3107 LIST_FOR_EACH_ENTRY_SAFE( s, cursor2, &editor->style_list, ME_Style, entry )
3108 ME_DestroyStyle( s );
3110 ME_ReleaseStyle(editor->pBuffer->pDefaultStyle);
3111 for (i=0; i<HFONT_CACHE_SIZE; i++)
3113 if (editor->pFontCache[i].hFont)
3114 DeleteObject(editor->pFontCache[i].hFont);
3116 if (editor->rgbBackColor != -1)
3117 DeleteObject(editor->hbrBackground);
3118 if(editor->lpOleCallback)
3119 IRichEditOleCallback_Release(editor->lpOleCallback);
3120 ITextHost_Release(editor->texthost);
3121 if (editor->reOle)
3123 IRichEditOle_Release(editor->reOle);
3124 editor->reOle = NULL;
3126 OleUninitialize();
3128 FREE_OBJ(editor->pBuffer);
3129 FREE_OBJ(editor->pCursors);
3131 FREE_OBJ(editor);
3134 BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
3136 TRACE("\n");
3137 switch (fdwReason)
3139 case DLL_PROCESS_ATTACH:
3140 DisableThreadLibraryCalls(hinstDLL);
3141 me_heap = HeapCreate (0, 0x10000, 0);
3142 if (!ME_RegisterEditorClass(hinstDLL)) return FALSE;
3143 hLeft = LoadCursorW(hinstDLL, MAKEINTRESOURCEW(OCR_REVERSE));
3144 LookupInit();
3145 break;
3147 case DLL_PROCESS_DETACH:
3148 if (lpvReserved) break;
3149 UnregisterClassW(RICHEDIT_CLASS20W, 0);
3150 UnregisterClassW(MSFTEDIT_CLASS, 0);
3151 UnregisterClassA(RICHEDIT_CLASS20A, 0);
3152 UnregisterClassA("RichEdit50A", 0);
3153 if (ME_ListBoxRegistered)
3154 UnregisterClassW(REListBox20W, 0);
3155 if (ME_ComboBoxRegistered)
3156 UnregisterClassW(REComboBox20W, 0);
3157 LookupCleanup();
3158 HeapDestroy (me_heap);
3159 release_typelib();
3160 break;
3162 return TRUE;
3165 static inline int get_default_line_height( ME_TextEditor *editor )
3167 int height = 0;
3169 if (editor->pBuffer && editor->pBuffer->pDefaultStyle)
3170 height = editor->pBuffer->pDefaultStyle->tm.tmHeight;
3171 if (height <= 0) height = 24;
3173 return height;
3176 static inline int calc_wheel_change( int *remain, int amount_per_click )
3178 int change = amount_per_click * (float)*remain / WHEEL_DELTA;
3179 *remain -= WHEEL_DELTA * change / amount_per_click;
3180 return change;
3183 static const char * const edit_messages[] = {
3184 "EM_GETSEL",
3185 "EM_SETSEL",
3186 "EM_GETRECT",
3187 "EM_SETRECT",
3188 "EM_SETRECTNP",
3189 "EM_SCROLL",
3190 "EM_LINESCROLL",
3191 "EM_SCROLLCARET",
3192 "EM_GETMODIFY",
3193 "EM_SETMODIFY",
3194 "EM_GETLINECOUNT",
3195 "EM_LINEINDEX",
3196 "EM_SETHANDLE",
3197 "EM_GETHANDLE",
3198 "EM_GETTHUMB",
3199 "EM_UNKNOWN_BF",
3200 "EM_UNKNOWN_C0",
3201 "EM_LINELENGTH",
3202 "EM_REPLACESEL",
3203 "EM_UNKNOWN_C3",
3204 "EM_GETLINE",
3205 "EM_LIMITTEXT",
3206 "EM_CANUNDO",
3207 "EM_UNDO",
3208 "EM_FMTLINES",
3209 "EM_LINEFROMCHAR",
3210 "EM_UNKNOWN_CA",
3211 "EM_SETTABSTOPS",
3212 "EM_SETPASSWORDCHAR",
3213 "EM_EMPTYUNDOBUFFER",
3214 "EM_GETFIRSTVISIBLELINE",
3215 "EM_SETREADONLY",
3216 "EM_SETWORDBREAKPROC",
3217 "EM_GETWORDBREAKPROC",
3218 "EM_GETPASSWORDCHAR",
3219 "EM_SETMARGINS",
3220 "EM_GETMARGINS",
3221 "EM_GETLIMITTEXT",
3222 "EM_POSFROMCHAR",
3223 "EM_CHARFROMPOS",
3224 "EM_SETIMESTATUS",
3225 "EM_GETIMESTATUS"
3228 static const char * const richedit_messages[] = {
3229 "EM_CANPASTE",
3230 "EM_DISPLAYBAND",
3231 "EM_EXGETSEL",
3232 "EM_EXLIMITTEXT",
3233 "EM_EXLINEFROMCHAR",
3234 "EM_EXSETSEL",
3235 "EM_FINDTEXT",
3236 "EM_FORMATRANGE",
3237 "EM_GETCHARFORMAT",
3238 "EM_GETEVENTMASK",
3239 "EM_GETOLEINTERFACE",
3240 "EM_GETPARAFORMAT",
3241 "EM_GETSELTEXT",
3242 "EM_HIDESELECTION",
3243 "EM_PASTESPECIAL",
3244 "EM_REQUESTRESIZE",
3245 "EM_SELECTIONTYPE",
3246 "EM_SETBKGNDCOLOR",
3247 "EM_SETCHARFORMAT",
3248 "EM_SETEVENTMASK",
3249 "EM_SETOLECALLBACK",
3250 "EM_SETPARAFORMAT",
3251 "EM_SETTARGETDEVICE",
3252 "EM_STREAMIN",
3253 "EM_STREAMOUT",
3254 "EM_GETTEXTRANGE",
3255 "EM_FINDWORDBREAK",
3256 "EM_SETOPTIONS",
3257 "EM_GETOPTIONS",
3258 "EM_FINDTEXTEX",
3259 "EM_GETWORDBREAKPROCEX",
3260 "EM_SETWORDBREAKPROCEX",
3261 "EM_SETUNDOLIMIT",
3262 "EM_UNKNOWN_USER_83",
3263 "EM_REDO",
3264 "EM_CANREDO",
3265 "EM_GETUNDONAME",
3266 "EM_GETREDONAME",
3267 "EM_STOPGROUPTYPING",
3268 "EM_SETTEXTMODE",
3269 "EM_GETTEXTMODE",
3270 "EM_AUTOURLDETECT",
3271 "EM_GETAUTOURLDETECT",
3272 "EM_SETPALETTE",
3273 "EM_GETTEXTEX",
3274 "EM_GETTEXTLENGTHEX",
3275 "EM_SHOWSCROLLBAR",
3276 "EM_SETTEXTEX",
3277 "EM_UNKNOWN_USER_98",
3278 "EM_UNKNOWN_USER_99",
3279 "EM_SETPUNCTUATION",
3280 "EM_GETPUNCTUATION",
3281 "EM_SETWORDWRAPMODE",
3282 "EM_GETWORDWRAPMODE",
3283 "EM_SETIMECOLOR",
3284 "EM_GETIMECOLOR",
3285 "EM_SETIMEOPTIONS",
3286 "EM_GETIMEOPTIONS",
3287 "EM_CONVPOSITION",
3288 "EM_UNKNOWN_USER_109",
3289 "EM_UNKNOWN_USER_110",
3290 "EM_UNKNOWN_USER_111",
3291 "EM_UNKNOWN_USER_112",
3292 "EM_UNKNOWN_USER_113",
3293 "EM_UNKNOWN_USER_114",
3294 "EM_UNKNOWN_USER_115",
3295 "EM_UNKNOWN_USER_116",
3296 "EM_UNKNOWN_USER_117",
3297 "EM_UNKNOWN_USER_118",
3298 "EM_UNKNOWN_USER_119",
3299 "EM_SETLANGOPTIONS",
3300 "EM_GETLANGOPTIONS",
3301 "EM_GETIMECOMPMODE",
3302 "EM_FINDTEXTW",
3303 "EM_FINDTEXTEXW",
3304 "EM_RECONVERSION",
3305 "EM_SETIMEMODEBIAS",
3306 "EM_GETIMEMODEBIAS"
3309 static const char *
3310 get_msg_name(UINT msg)
3312 if (msg >= EM_GETSEL && msg <= EM_CHARFROMPOS)
3313 return edit_messages[msg - EM_GETSEL];
3314 if (msg >= EM_CANPASTE && msg <= EM_GETIMEMODEBIAS)
3315 return richedit_messages[msg - EM_CANPASTE];
3316 return "";
3319 static void ME_LinkNotify(ME_TextEditor *editor, UINT msg, WPARAM wParam, LPARAM lParam)
3321 int x,y;
3322 BOOL isExact;
3323 ME_Cursor cursor; /* The start of the clicked text. */
3325 ENLINK info;
3326 x = (short)LOWORD(lParam);
3327 y = (short)HIWORD(lParam);
3328 ME_CharFromPos(editor, x, y, &cursor, &isExact);
3329 if (!isExact) return;
3331 if (is_link( &cursor.pRun->member.run ))
3332 { /* The clicked run has CFE_LINK set */
3333 ME_DisplayItem *di;
3335 info.nmhdr.hwndFrom = NULL;
3336 info.nmhdr.idFrom = 0;
3337 info.nmhdr.code = EN_LINK;
3338 info.msg = msg;
3339 info.wParam = wParam;
3340 info.lParam = lParam;
3341 cursor.nOffset = 0;
3343 /* find the first contiguous run with CFE_LINK set */
3344 info.chrg.cpMin = ME_GetCursorOfs(&cursor);
3345 di = cursor.pRun;
3346 while (ME_PrevRun( NULL, &di, FALSE ) && is_link( &di->member.run ))
3347 info.chrg.cpMin -= di->member.run.len;
3349 /* find the last contiguous run with CFE_LINK set */
3350 info.chrg.cpMax = ME_GetCursorOfs(&cursor) + cursor.pRun->member.run.len;
3351 di = cursor.pRun;
3352 while (ME_NextRun( NULL, &di, FALSE ) && is_link( &di->member.run ))
3353 info.chrg.cpMax += di->member.run.len;
3355 ITextHost_TxNotify(editor->texthost, info.nmhdr.code, &info);
3359 void ME_ReplaceSel(ME_TextEditor *editor, BOOL can_undo, const WCHAR *str, int len)
3361 int from, to, nStartCursor;
3362 ME_Style *style;
3364 nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3365 style = ME_GetSelectionInsertStyle(editor);
3366 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to-from, FALSE);
3367 ME_InsertTextFromCursor(editor, 0, str, len, style);
3368 ME_ReleaseStyle(style);
3369 /* drop temporary style if line end */
3371 * FIXME question: does abc\n mean: put abc,
3372 * clear temp style, put \n? (would require a change)
3374 if (len>0 && str[len-1] == '\n')
3375 ME_ClearTempStyle(editor);
3376 ME_CommitUndo(editor);
3377 ME_UpdateSelectionLinkAttribute(editor);
3378 if (!can_undo)
3379 ME_EmptyUndoStack(editor);
3380 ME_UpdateRepaint(editor, FALSE);
3383 static void ME_SetText(ME_TextEditor *editor, void *text, BOOL unicode)
3385 LONG codepage = unicode ? CP_UNICODE : CP_ACP;
3386 int textLen;
3388 LPWSTR wszText = ME_ToUnicode(codepage, text, &textLen);
3390 if (textLen > 0)
3392 int len = -1;
3394 /* uses default style! */
3395 if (!(editor->styleFlags & ES_MULTILINE))
3397 WCHAR *p = wszText;
3399 while (*p != '\0' && *p != '\r' && *p != '\n') p++;
3400 len = p - wszText;
3402 ME_InsertTextFromCursor(editor, 0, wszText, len, editor->pBuffer->pDefaultStyle);
3404 ME_EndToUnicode(codepage, wszText);
3407 static LRESULT ME_WmCreate(ME_TextEditor *editor, LPARAM lParam, BOOL unicode)
3409 CREATESTRUCTW *createW = (CREATESTRUCTW*)lParam;
3410 CREATESTRUCTA *createA = (CREATESTRUCTA*)lParam;
3411 void *text = NULL;
3412 INT max;
3414 if (lParam)
3415 text = unicode ? (void*)createW->lpszName : (void*)createA->lpszName;
3417 ME_SetDefaultFormatRect(editor);
3419 max = (editor->styleFlags & ES_DISABLENOSCROLL) ? 1 : 0;
3420 if (~editor->styleFlags & ES_DISABLENOSCROLL || editor->styleFlags & WS_VSCROLL)
3421 ITextHost_TxSetScrollRange(editor->texthost, SB_VERT, 0, max, TRUE);
3423 if (~editor->styleFlags & ES_DISABLENOSCROLL || editor->styleFlags & WS_HSCROLL)
3424 ITextHost_TxSetScrollRange(editor->texthost, SB_HORZ, 0, max, TRUE);
3426 if (editor->styleFlags & ES_DISABLENOSCROLL)
3428 if (editor->styleFlags & WS_VSCROLL)
3430 ITextHost_TxEnableScrollBar(editor->texthost, SB_VERT, ESB_DISABLE_BOTH);
3431 ITextHost_TxShowScrollBar(editor->texthost, SB_VERT, TRUE);
3433 if (editor->styleFlags & WS_HSCROLL)
3435 ITextHost_TxEnableScrollBar(editor->texthost, SB_HORZ, ESB_DISABLE_BOTH);
3436 ITextHost_TxShowScrollBar(editor->texthost, SB_HORZ, TRUE);
3440 if (text)
3442 ME_SetText(editor, text, unicode);
3443 ME_SetCursorToStart(editor, &editor->pCursors[0]);
3444 ME_SetCursorToStart(editor, &editor->pCursors[1]);
3447 ME_CommitUndo(editor);
3448 ME_WrapMarkedParagraphs(editor);
3449 ME_MoveCaret(editor);
3450 return 0;
3454 #define UNSUPPORTED_MSG(e) \
3455 case e: \
3456 FIXME(#e ": stub\n"); \
3457 *phresult = S_FALSE; \
3458 return 0;
3460 /* Handle messages for windowless and windowed richedit controls.
3462 * The LRESULT that is returned is a return value for window procs,
3463 * and the phresult parameter is the COM return code needed by the
3464 * text services interface. */
3465 LRESULT ME_HandleMessage(ME_TextEditor *editor, UINT msg, WPARAM wParam,
3466 LPARAM lParam, BOOL unicode, HRESULT* phresult)
3468 *phresult = S_OK;
3470 switch(msg) {
3472 UNSUPPORTED_MSG(EM_DISPLAYBAND)
3473 UNSUPPORTED_MSG(EM_FINDWORDBREAK)
3474 UNSUPPORTED_MSG(EM_FMTLINES)
3475 UNSUPPORTED_MSG(EM_FORMATRANGE)
3476 UNSUPPORTED_MSG(EM_GETBIDIOPTIONS)
3477 UNSUPPORTED_MSG(EM_GETEDITSTYLE)
3478 UNSUPPORTED_MSG(EM_GETIMECOMPMODE)
3479 UNSUPPORTED_MSG(EM_GETIMESTATUS)
3480 UNSUPPORTED_MSG(EM_SETIMESTATUS)
3481 UNSUPPORTED_MSG(EM_GETLANGOPTIONS)
3482 UNSUPPORTED_MSG(EM_GETREDONAME)
3483 UNSUPPORTED_MSG(EM_GETTYPOGRAPHYOPTIONS)
3484 UNSUPPORTED_MSG(EM_GETUNDONAME)
3485 UNSUPPORTED_MSG(EM_GETWORDBREAKPROCEX)
3486 UNSUPPORTED_MSG(EM_SELECTIONTYPE)
3487 UNSUPPORTED_MSG(EM_SETBIDIOPTIONS)
3488 UNSUPPORTED_MSG(EM_SETEDITSTYLE)
3489 UNSUPPORTED_MSG(EM_SETLANGOPTIONS)
3490 UNSUPPORTED_MSG(EM_SETMARGINS)
3491 UNSUPPORTED_MSG(EM_SETPALETTE)
3492 UNSUPPORTED_MSG(EM_SETTABSTOPS)
3493 UNSUPPORTED_MSG(EM_SETTYPOGRAPHYOPTIONS)
3494 UNSUPPORTED_MSG(EM_SETWORDBREAKPROCEX)
3496 /* Messages specific to Richedit controls */
3498 case EM_STREAMIN:
3499 return ME_StreamIn(editor, wParam, (EDITSTREAM*)lParam, TRUE);
3500 case EM_STREAMOUT:
3501 return ME_StreamOut(editor, wParam, (EDITSTREAM *)lParam);
3502 case WM_GETDLGCODE:
3504 UINT code = DLGC_WANTCHARS|DLGC_WANTTAB|DLGC_WANTARROWS;
3506 if (lParam)
3507 editor->bDialogMode = TRUE;
3508 if (editor->styleFlags & ES_MULTILINE)
3509 code |= DLGC_WANTMESSAGE;
3510 if (!(editor->styleFlags & ES_SAVESEL))
3511 code |= DLGC_HASSETSEL;
3512 return code;
3514 case EM_EMPTYUNDOBUFFER:
3515 ME_EmptyUndoStack(editor);
3516 return 0;
3517 case EM_GETSEL:
3519 /* Note: wParam/lParam can be NULL */
3520 UINT from, to;
3521 PUINT pfrom = wParam ? (PUINT)wParam : &from;
3522 PUINT pto = lParam ? (PUINT)lParam : &to;
3523 ME_GetSelectionOfs(editor, (int *)pfrom, (int *)pto);
3524 if ((*pfrom|*pto) & 0xFFFF0000)
3525 return -1;
3526 return MAKELONG(*pfrom,*pto);
3528 case EM_EXGETSEL:
3530 CHARRANGE *pRange = (CHARRANGE *)lParam;
3531 ME_GetSelectionOfs(editor, &pRange->cpMin, &pRange->cpMax);
3532 TRACE("EM_EXGETSEL = (%d,%d)\n", pRange->cpMin, pRange->cpMax);
3533 return 0;
3535 case EM_SETUNDOLIMIT:
3537 if ((int)wParam < 0)
3538 editor->nUndoLimit = STACK_SIZE_DEFAULT;
3539 else
3540 editor->nUndoLimit = min(wParam, STACK_SIZE_MAX);
3541 /* Setting a max stack size keeps wine from getting killed
3542 for hogging memory. Windows allocates all this memory at once, so
3543 no program would realistically set a value above our maximum. */
3544 return editor->nUndoLimit;
3546 case EM_CANUNDO:
3547 return !list_empty( &editor->undo_stack );
3548 case EM_CANREDO:
3549 return !list_empty( &editor->redo_stack );
3550 case WM_UNDO: /* FIXME: actually not the same */
3551 case EM_UNDO:
3552 return ME_Undo(editor);
3553 case EM_REDO:
3554 return ME_Redo(editor);
3555 case EM_GETOPTIONS:
3557 /* these flags are equivalent to the ES_* counterparts */
3558 DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
3559 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN | ECO_SELECTIONBAR;
3560 DWORD settings = editor->styleFlags & mask;
3562 return settings;
3564 case EM_SETFONTSIZE:
3566 CHARFORMAT2W cf;
3567 LONG tmp_size, size;
3568 BOOL is_increase = ((LONG)wParam > 0);
3570 if (editor->mode & TM_PLAINTEXT)
3571 return FALSE;
3573 cf.cbSize = sizeof(cf);
3574 cf.dwMask = CFM_SIZE;
3575 ME_GetSelectionCharFormat(editor, &cf);
3576 tmp_size = (cf.yHeight / 20) + wParam;
3578 if (tmp_size <= 1)
3579 size = 1;
3580 else if (tmp_size > 12 && tmp_size < 28 && tmp_size % 2)
3581 size = tmp_size + (is_increase ? 1 : -1);
3582 else if (tmp_size > 28 && tmp_size < 36)
3583 size = is_increase ? 36 : 28;
3584 else if (tmp_size > 36 && tmp_size < 48)
3585 size = is_increase ? 48 : 36;
3586 else if (tmp_size > 48 && tmp_size < 72)
3587 size = is_increase ? 72 : 48;
3588 else if (tmp_size > 72 && tmp_size < 80)
3589 size = is_increase ? 80 : 72;
3590 else if (tmp_size > 80 && tmp_size < 1638)
3591 size = 10 * (is_increase ? (tmp_size / 10 + 1) : (tmp_size / 10));
3592 else if (tmp_size >= 1638)
3593 size = 1638;
3594 else
3595 size = tmp_size;
3597 cf.yHeight = size * 20; /* convert twips to points */
3598 ME_SetSelectionCharFormat(editor, &cf);
3599 ME_CommitUndo(editor);
3600 ME_WrapMarkedParagraphs(editor);
3601 ME_UpdateScrollBar(editor);
3602 ME_Repaint(editor);
3604 return TRUE;
3606 case EM_SETOPTIONS:
3608 /* these flags are equivalent to ES_* counterparts, except for
3609 * ECO_AUTOWORDSELECTION that doesn't have an ES_* counterpart,
3610 * but is still stored in editor->styleFlags. */
3611 const DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
3612 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN |
3613 ECO_SELECTIONBAR | ECO_AUTOWORDSELECTION;
3614 DWORD settings = mask & editor->styleFlags;
3615 DWORD oldSettings = settings;
3616 DWORD changedSettings;
3618 switch(wParam)
3620 case ECOOP_SET:
3621 settings = lParam;
3622 break;
3623 case ECOOP_OR:
3624 settings |= lParam;
3625 break;
3626 case ECOOP_AND:
3627 settings &= lParam;
3628 break;
3629 case ECOOP_XOR:
3630 settings ^= lParam;
3632 changedSettings = oldSettings ^ settings;
3634 if (changedSettings) {
3635 editor->styleFlags = (editor->styleFlags & ~mask) | (settings & mask);
3637 if (changedSettings & ECO_SELECTIONBAR)
3639 ITextHost_TxInvalidateRect(editor->texthost, &editor->rcFormat, TRUE);
3640 if (settings & ECO_SELECTIONBAR) {
3641 assert(!editor->selofs);
3642 editor->selofs = SELECTIONBAR_WIDTH;
3643 editor->rcFormat.left += editor->selofs;
3644 } else {
3645 editor->rcFormat.left -= editor->selofs;
3646 editor->selofs = 0;
3648 ME_RewrapRepaint(editor);
3651 if ((changedSettings & settings & ES_NOHIDESEL) && !editor->bHaveFocus)
3652 ME_InvalidateSelection( editor );
3654 if (changedSettings & settings & ECO_VERTICAL)
3655 FIXME("ECO_VERTICAL not implemented yet!\n");
3656 if (changedSettings & settings & ECO_AUTOHSCROLL)
3657 FIXME("ECO_AUTOHSCROLL not implemented yet!\n");
3658 if (changedSettings & settings & ECO_AUTOVSCROLL)
3659 FIXME("ECO_AUTOVSCROLL not implemented yet!\n");
3660 if (changedSettings & settings & ECO_WANTRETURN)
3661 FIXME("ECO_WANTRETURN not implemented yet!\n");
3662 if (changedSettings & settings & ECO_AUTOWORDSELECTION)
3663 FIXME("ECO_AUTOWORDSELECTION not implemented yet!\n");
3666 return settings;
3668 case EM_SETSEL:
3670 return handle_EM_EXSETSEL( editor, wParam, lParam );
3672 case EM_SETSCROLLPOS:
3674 POINT *point = (POINT *)lParam;
3675 ME_ScrollAbs(editor, point->x, point->y);
3676 return 0;
3678 case EM_AUTOURLDETECT:
3680 if (wParam==1 || wParam ==0)
3682 editor->AutoURLDetect_bEnable = (BOOL)wParam;
3683 return 0;
3685 return E_INVALIDARG;
3687 case EM_GETAUTOURLDETECT:
3689 return editor->AutoURLDetect_bEnable;
3691 case EM_EXSETSEL:
3693 CHARRANGE range = *(CHARRANGE *)lParam;
3695 return handle_EM_EXSETSEL( editor, range.cpMin, range.cpMax );
3697 case EM_SHOWSCROLLBAR:
3699 DWORD flags;
3701 switch (wParam)
3703 case SB_HORZ:
3704 flags = WS_HSCROLL;
3705 break;
3706 case SB_VERT:
3707 flags = WS_VSCROLL;
3708 break;
3709 case SB_BOTH:
3710 flags = WS_HSCROLL|WS_VSCROLL;
3711 break;
3712 default:
3713 return 0;
3716 if (lParam) {
3717 editor->styleFlags |= flags;
3718 if (flags & WS_HSCROLL)
3719 ITextHost_TxShowScrollBar(editor->texthost, SB_HORZ,
3720 editor->nTotalWidth > editor->sizeWindow.cx);
3721 if (flags & WS_VSCROLL)
3722 ITextHost_TxShowScrollBar(editor->texthost, SB_VERT,
3723 editor->nTotalLength > editor->sizeWindow.cy);
3724 } else {
3725 editor->styleFlags &= ~flags;
3726 ITextHost_TxShowScrollBar(editor->texthost, wParam, FALSE);
3728 return 0;
3730 case EM_SETTEXTEX:
3732 LPWSTR wszText;
3733 SETTEXTEX *pStruct = (SETTEXTEX *)wParam;
3734 int from, to, len;
3735 ME_Style *style;
3736 BOOL bRtf, bUnicode, bSelection, bUTF8;
3737 int oldModify = editor->nModifyStep;
3738 static const char utf8_bom[] = {0xef, 0xbb, 0xbf};
3740 if (!pStruct) return 0;
3742 /* If we detect ascii rtf at the start of the string,
3743 * we know it isn't unicode. */
3744 bRtf = (lParam && (!strncmp((char *)lParam, "{\\rtf", 5) ||
3745 !strncmp((char *)lParam, "{\\urtf", 6)));
3746 bUnicode = !bRtf && pStruct->codepage == CP_UNICODE;
3747 bUTF8 = (lParam && (!strncmp((char *)lParam, utf8_bom, 3)));
3749 TRACE("EM_SETTEXTEX - %s, flags %d, cp %d\n",
3750 bUnicode ? debugstr_w((LPCWSTR)lParam) : debugstr_a((LPCSTR)lParam),
3751 pStruct->flags, pStruct->codepage);
3753 bSelection = (pStruct->flags & ST_SELECTION) != 0;
3754 if (bSelection) {
3755 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3756 style = ME_GetSelectionInsertStyle(editor);
3757 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to - from, FALSE);
3758 } else {
3759 ME_Cursor start;
3760 ME_SetCursorToStart(editor, &start);
3761 ME_InternalDeleteText(editor, &start, ME_GetTextLength(editor), FALSE);
3762 style = editor->pBuffer->pDefaultStyle;
3765 if (bRtf) {
3766 ME_StreamInRTFString(editor, bSelection, (char *)lParam);
3767 if (bSelection) {
3768 /* FIXME: The length returned doesn't include the rtf control
3769 * characters, only the actual text. */
3770 len = lParam ? strlen((char *)lParam) : 0;
3772 } else {
3773 if (bUTF8 && !bUnicode) {
3774 wszText = ME_ToUnicode(CP_UTF8, (void *)(lParam+3), &len);
3775 ME_InsertTextFromCursor(editor, 0, wszText, len, style);
3776 ME_EndToUnicode(CP_UTF8, wszText);
3777 } else {
3778 wszText = ME_ToUnicode(pStruct->codepage, (void *)lParam, &len);
3779 ME_InsertTextFromCursor(editor, 0, wszText, len, style);
3780 ME_EndToUnicode(pStruct->codepage, wszText);
3784 if (bSelection) {
3785 ME_ReleaseStyle(style);
3786 ME_UpdateSelectionLinkAttribute(editor);
3787 } else {
3788 ME_Cursor cursor;
3789 len = 1;
3790 ME_SetCursorToStart(editor, &cursor);
3791 ME_UpdateLinkAttribute(editor, &cursor, INT_MAX);
3793 ME_CommitUndo(editor);
3794 if (!(pStruct->flags & ST_KEEPUNDO))
3796 editor->nModifyStep = oldModify;
3797 ME_EmptyUndoStack(editor);
3799 ME_UpdateRepaint(editor, FALSE);
3800 return len;
3802 case EM_SETBKGNDCOLOR:
3804 LRESULT lColor;
3805 if (editor->rgbBackColor != -1) {
3806 DeleteObject(editor->hbrBackground);
3807 lColor = editor->rgbBackColor;
3809 else lColor = ITextHost_TxGetSysColor(editor->texthost, COLOR_WINDOW);
3811 if (wParam)
3813 editor->rgbBackColor = -1;
3814 editor->hbrBackground = GetSysColorBrush(COLOR_WINDOW);
3816 else
3818 editor->rgbBackColor = lParam;
3819 editor->hbrBackground = CreateSolidBrush(editor->rgbBackColor);
3821 ITextHost_TxInvalidateRect(editor->texthost, NULL, TRUE);
3822 return lColor;
3824 case EM_GETMODIFY:
3825 return editor->nModifyStep == 0 ? 0 : -1;
3826 case EM_SETMODIFY:
3828 if (wParam)
3829 editor->nModifyStep = 1;
3830 else
3831 editor->nModifyStep = 0;
3833 return 0;
3835 case EM_SETREADONLY:
3837 if (wParam)
3838 editor->styleFlags |= ES_READONLY;
3839 else
3840 editor->styleFlags &= ~ES_READONLY;
3841 return 1;
3843 case EM_SETEVENTMASK:
3845 DWORD nOldMask = editor->nEventMask;
3847 editor->nEventMask = lParam;
3848 return nOldMask;
3850 case EM_GETEVENTMASK:
3851 return editor->nEventMask;
3852 case EM_SETCHARFORMAT:
3854 CHARFORMAT2W buf, *p;
3855 BOOL bRepaint = TRUE;
3856 p = ME_ToCF2W(&buf, (CHARFORMAT2W *)lParam);
3857 if (p == NULL) return 0;
3858 if (wParam & SCF_ALL) {
3859 if (editor->mode & TM_PLAINTEXT) {
3860 ME_SetDefaultCharFormat(editor, p);
3861 } else {
3862 ME_Cursor start;
3863 ME_SetCursorToStart(editor, &start);
3864 ME_SetCharFormat(editor, &start, NULL, p);
3865 editor->nModifyStep = 1;
3867 } else if (wParam & SCF_SELECTION) {
3868 if (editor->mode & TM_PLAINTEXT)
3869 return 0;
3870 if (wParam & SCF_WORD) {
3871 ME_Cursor start;
3872 ME_Cursor end = editor->pCursors[0];
3873 ME_MoveCursorWords(editor, &end, +1);
3874 start = end;
3875 ME_MoveCursorWords(editor, &start, -1);
3876 ME_SetCharFormat(editor, &start, &end, p);
3878 bRepaint = ME_IsSelection(editor);
3879 ME_SetSelectionCharFormat(editor, p);
3880 if (bRepaint) editor->nModifyStep = 1;
3881 } else { /* SCF_DEFAULT */
3882 ME_SetDefaultCharFormat(editor, p);
3884 ME_CommitUndo(editor);
3885 if (bRepaint)
3887 ME_WrapMarkedParagraphs(editor);
3888 ME_UpdateScrollBar(editor);
3889 ME_Repaint(editor);
3891 return 1;
3893 case EM_GETCHARFORMAT:
3895 CHARFORMAT2W tmp, *dst = (CHARFORMAT2W *)lParam;
3896 if (dst->cbSize != sizeof(CHARFORMATA) &&
3897 dst->cbSize != sizeof(CHARFORMATW) &&
3898 dst->cbSize != sizeof(CHARFORMAT2A) &&
3899 dst->cbSize != sizeof(CHARFORMAT2W))
3900 return 0;
3901 tmp.cbSize = sizeof(tmp);
3902 if (!wParam)
3903 ME_GetDefaultCharFormat(editor, &tmp);
3904 else
3905 ME_GetSelectionCharFormat(editor, &tmp);
3906 ME_CopyToCFAny(dst, &tmp);
3907 return tmp.dwMask;
3909 case EM_SETPARAFORMAT:
3911 BOOL result = ME_SetSelectionParaFormat(editor, (PARAFORMAT2 *)lParam);
3912 ME_WrapMarkedParagraphs(editor);
3913 ME_UpdateScrollBar(editor);
3914 ME_Repaint(editor);
3915 ME_CommitUndo(editor);
3916 return result;
3918 case EM_GETPARAFORMAT:
3919 ME_GetSelectionParaFormat(editor, (PARAFORMAT2 *)lParam);
3920 return ((PARAFORMAT2 *)lParam)->dwMask;
3921 case EM_GETFIRSTVISIBLELINE:
3923 ME_DisplayItem *p = editor->pBuffer->pFirst;
3924 int y = editor->vert_si.nPos;
3925 int ypara = 0;
3926 int count = 0;
3927 int ystart, yend;
3928 while(p) {
3929 p = ME_FindItemFwd(p, diStartRowOrParagraphOrEnd);
3930 if (p->type == diTextEnd)
3931 break;
3932 if (p->type == diParagraph) {
3933 ypara = p->member.para.pt.y;
3934 continue;
3936 ystart = ypara + p->member.row.pt.y;
3937 yend = ystart + p->member.row.nHeight;
3938 if (y < yend) {
3939 break;
3941 count++;
3943 return count;
3945 case EM_HIDESELECTION:
3947 editor->bHideSelection = (wParam != 0);
3948 ME_InvalidateSelection(editor);
3949 return 0;
3951 case EM_LINESCROLL:
3953 if (!(editor->styleFlags & ES_MULTILINE))
3954 return FALSE;
3955 ME_ScrollDown( editor, lParam * get_default_line_height( editor ) );
3956 return TRUE;
3958 case WM_CLEAR:
3960 int from, to;
3961 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3962 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to-from, FALSE);
3963 ME_CommitUndo(editor);
3964 ME_UpdateRepaint(editor, TRUE);
3965 return 0;
3967 case EM_REPLACESEL:
3969 int len = 0;
3970 LONG codepage = unicode ? CP_UNICODE : CP_ACP;
3971 LPWSTR wszText = ME_ToUnicode(codepage, (void *)lParam, &len);
3973 TRACE("EM_REPLACESEL - %s\n", debugstr_w(wszText));
3975 ME_ReplaceSel(editor, !!wParam, wszText, len);
3976 ME_EndToUnicode(codepage, wszText);
3977 return len;
3979 case EM_SCROLLCARET:
3980 ME_EnsureVisible(editor, &editor->pCursors[0]);
3981 return 0;
3982 case WM_SETFONT:
3984 LOGFONTW lf;
3985 CHARFORMAT2W fmt;
3986 HDC hDC;
3987 BOOL bRepaint = LOWORD(lParam);
3989 if (!wParam)
3990 wParam = (WPARAM)GetStockObject(SYSTEM_FONT);
3992 if (!GetObjectW((HGDIOBJ)wParam, sizeof(LOGFONTW), &lf))
3993 return 0;
3995 hDC = ITextHost_TxGetDC(editor->texthost);
3996 ME_CharFormatFromLogFont(hDC, &lf, &fmt);
3997 ITextHost_TxReleaseDC(editor->texthost, hDC);
3998 if (editor->mode & TM_RICHTEXT) {
3999 ME_Cursor start;
4000 ME_SetCursorToStart(editor, &start);
4001 ME_SetCharFormat(editor, &start, NULL, &fmt);
4003 ME_SetDefaultCharFormat(editor, &fmt);
4005 ME_CommitUndo(editor);
4006 ME_MarkAllForWrapping(editor);
4007 ME_WrapMarkedParagraphs(editor);
4008 ME_UpdateScrollBar(editor);
4009 if (bRepaint)
4010 ME_Repaint(editor);
4011 return 0;
4013 case WM_SETTEXT:
4015 ME_Cursor cursor;
4016 ME_SetCursorToStart(editor, &cursor);
4017 ME_InternalDeleteText(editor, &cursor, ME_GetTextLength(editor), FALSE);
4018 if (lParam)
4020 TRACE("WM_SETTEXT lParam==%lx\n",lParam);
4021 if (!strncmp((char *)lParam, "{\\rtf", 5) ||
4022 !strncmp((char *)lParam, "{\\urtf", 6))
4024 /* Undocumented: WM_SETTEXT supports RTF text */
4025 ME_StreamInRTFString(editor, 0, (char *)lParam);
4027 else
4028 ME_SetText(editor, (void*)lParam, unicode);
4030 else
4031 TRACE("WM_SETTEXT - NULL\n");
4032 ME_SetCursorToStart(editor, &cursor);
4033 ME_UpdateLinkAttribute(editor, &cursor, INT_MAX);
4034 ME_SetSelection(editor, 0, 0);
4035 editor->nModifyStep = 0;
4036 ME_CommitUndo(editor);
4037 ME_EmptyUndoStack(editor);
4038 ME_UpdateRepaint(editor, FALSE);
4039 return 1;
4041 case EM_CANPASTE:
4042 return paste_special( editor, 0, NULL, TRUE );
4043 case WM_PASTE:
4044 case WM_MBUTTONDOWN:
4045 wParam = 0;
4046 lParam = 0;
4047 /* fall through */
4048 case EM_PASTESPECIAL:
4049 paste_special( editor, wParam, (REPASTESPECIAL *)lParam, FALSE );
4050 return 0;
4051 case WM_CUT:
4052 case WM_COPY:
4054 int nFrom, nTo, nStartCur = ME_GetSelectionOfs(editor, &nFrom, &nTo);
4055 int nChars = nTo - nFrom;
4056 ME_Cursor *selStart = &editor->pCursors[nStartCur];
4058 if (ME_Copy(editor, selStart, nChars) && msg == WM_CUT)
4060 ME_InternalDeleteText(editor, selStart, nChars, FALSE);
4061 ME_CommitUndo(editor);
4062 ME_UpdateRepaint(editor, TRUE);
4064 return 0;
4066 case WM_GETTEXTLENGTH:
4068 GETTEXTLENGTHEX how;
4070 /* CR/LF conversion required in 2.0 mode, verbatim in 1.0 mode */
4071 how.flags = GTL_CLOSE | (editor->bEmulateVersion10 ? 0 : GTL_USECRLF) | GTL_NUMCHARS;
4072 how.codepage = unicode ? CP_UNICODE : CP_ACP;
4073 return ME_GetTextLengthEx(editor, &how);
4075 case EM_GETTEXTLENGTHEX:
4076 return ME_GetTextLengthEx(editor, (GETTEXTLENGTHEX *)wParam);
4077 case WM_GETTEXT:
4079 GETTEXTEX ex;
4080 ex.cb = wParam * (unicode ? sizeof(WCHAR) : sizeof(CHAR));
4081 ex.flags = GT_USECRLF;
4082 ex.codepage = unicode ? CP_UNICODE : CP_ACP;
4083 ex.lpDefaultChar = NULL;
4084 ex.lpUsedDefChar = NULL;
4085 return ME_GetTextEx(editor, &ex, lParam);
4087 case EM_GETTEXTEX:
4088 return ME_GetTextEx(editor, (GETTEXTEX*)wParam, lParam);
4089 case EM_GETSELTEXT:
4091 int nFrom, nTo, nStartCur = ME_GetSelectionOfs(editor, &nFrom, &nTo);
4092 ME_Cursor *from = &editor->pCursors[nStartCur];
4093 return ME_GetTextRange(editor, (WCHAR *)lParam, from,
4094 nTo - nFrom, unicode);
4096 case EM_GETSCROLLPOS:
4098 POINT *point = (POINT *)lParam;
4099 point->x = editor->horz_si.nPos;
4100 point->y = editor->vert_si.nPos;
4101 /* 16-bit scaled value is returned as stored in scrollinfo */
4102 if (editor->horz_si.nMax > 0xffff)
4103 point->x = MulDiv(point->x, 0xffff, editor->horz_si.nMax);
4104 if (editor->vert_si.nMax > 0xffff)
4105 point->y = MulDiv(point->y, 0xffff, editor->vert_si.nMax);
4106 return 1;
4108 case EM_GETTEXTRANGE:
4110 TEXTRANGEW *rng = (TEXTRANGEW *)lParam;
4111 ME_Cursor start;
4112 int nStart = rng->chrg.cpMin;
4113 int nEnd = rng->chrg.cpMax;
4114 int textlength = ME_GetTextLength(editor);
4116 TRACE("EM_GETTEXTRANGE min=%d max=%d unicode=%d textlength=%d\n",
4117 rng->chrg.cpMin, rng->chrg.cpMax, unicode, textlength);
4118 if (nStart < 0) return 0;
4119 if ((nStart == 0 && nEnd == -1) || nEnd > textlength)
4120 nEnd = textlength;
4121 if (nStart >= nEnd) return 0;
4123 ME_CursorFromCharOfs(editor, nStart, &start);
4124 return ME_GetTextRange(editor, rng->lpstrText, &start, nEnd - nStart, unicode);
4126 case EM_GETLINE:
4128 ME_DisplayItem *run;
4129 const unsigned int nMaxChars = *(WORD *) lParam;
4130 unsigned int nCharsLeft = nMaxChars;
4131 char *dest = (char *) lParam;
4132 BOOL wroteNull = FALSE;
4134 TRACE("EM_GETLINE: row=%d, nMaxChars=%d (%s)\n", (int) wParam, nMaxChars,
4135 unicode ? "Unicode" : "Ansi");
4137 run = ME_FindRowWithNumber(editor, wParam);
4138 if (run == NULL)
4139 return 0;
4141 while (nCharsLeft && (run = ME_FindItemFwd(run, diRunOrStartRow))
4142 && run->type == diRun)
4144 WCHAR *str = get_text( &run->member.run, 0 );
4145 unsigned int nCopy;
4147 nCopy = min(nCharsLeft, run->member.run.len);
4149 if (unicode)
4150 memcpy(dest, str, nCopy * sizeof(WCHAR));
4151 else
4152 nCopy = WideCharToMultiByte(CP_ACP, 0, str, nCopy, dest,
4153 nCharsLeft, NULL, NULL);
4154 dest += nCopy * (unicode ? sizeof(WCHAR) : 1);
4155 nCharsLeft -= nCopy;
4158 /* append line termination, space allowing */
4159 if (nCharsLeft > 0)
4161 if (unicode)
4162 *((WCHAR *)dest) = '\0';
4163 else
4164 *dest = '\0';
4165 nCharsLeft--;
4166 wroteNull = TRUE;
4169 TRACE("EM_GETLINE: got %u characters\n", nMaxChars - nCharsLeft);
4170 return nMaxChars - nCharsLeft - (wroteNull ? 1 : 0);
4172 case EM_GETLINECOUNT:
4174 ME_DisplayItem *item = editor->pBuffer->pFirst->next;
4175 int nRows = 0;
4177 ME_DisplayItem *prev_para = NULL, *last_para = NULL;
4179 while (item != editor->pBuffer->pLast)
4181 assert(item->type == diParagraph);
4182 prev_para = ME_FindItemBack(item, diRun);
4183 if (prev_para) {
4184 assert(prev_para->member.run.nFlags & MERF_ENDPARA);
4186 nRows += item->member.para.nRows;
4187 item = item->member.para.next_para;
4189 last_para = ME_FindItemBack(item, diRun);
4190 assert(last_para);
4191 assert(last_para->member.run.nFlags & MERF_ENDPARA);
4192 if (editor->bEmulateVersion10 && prev_para &&
4193 last_para->member.run.nCharOfs == 0 &&
4194 prev_para->member.run.len == 1 &&
4195 *get_text( &prev_para->member.run, 0 ) == '\r')
4197 /* In 1.0 emulation, the last solitary \r at the very end of the text
4198 (if one exists) is NOT a line break.
4199 FIXME: this is an ugly hack. This should have a more regular model. */
4200 nRows--;
4203 TRACE("EM_GETLINECOUNT: nRows==%d\n", nRows);
4204 return max(1, nRows);
4206 case EM_LINEFROMCHAR:
4208 if (wParam == -1)
4209 return ME_RowNumberFromCharOfs(editor, ME_GetCursorOfs(&editor->pCursors[1]));
4210 else
4211 return ME_RowNumberFromCharOfs(editor, wParam);
4213 case EM_EXLINEFROMCHAR:
4215 if (lParam == -1)
4216 return ME_RowNumberFromCharOfs(editor, ME_GetCursorOfs(&editor->pCursors[1]));
4217 else
4218 return ME_RowNumberFromCharOfs(editor, lParam);
4220 case EM_LINEINDEX:
4222 ME_DisplayItem *item, *para;
4223 int nCharOfs;
4225 if (wParam == -1)
4226 item = ME_FindItemBack(editor->pCursors[0].pRun, diStartRow);
4227 else
4228 item = ME_FindRowWithNumber(editor, wParam);
4229 if (!item)
4230 return -1;
4231 para = ME_GetParagraph(item);
4232 item = ME_FindItemFwd(item, diRun);
4233 nCharOfs = para->member.para.nCharOfs + item->member.run.nCharOfs;
4234 TRACE("EM_LINEINDEX: nCharOfs==%d\n", nCharOfs);
4235 return nCharOfs;
4237 case EM_LINELENGTH:
4239 ME_DisplayItem *item, *item_end;
4240 int nChars = 0, nThisLineOfs = 0, nNextLineOfs = 0;
4241 ME_DisplayItem *para, *run;
4243 if (wParam > ME_GetTextLength(editor))
4244 return 0;
4245 if (wParam == -1)
4247 FIXME("EM_LINELENGTH: returning number of unselected characters on lines with selection unsupported.\n");
4248 return 0;
4250 ME_RunOfsFromCharOfs(editor, wParam, &para, &run, NULL);
4251 item = ME_RowStart(run);
4252 nThisLineOfs = ME_CharOfsFromRunOfs(editor, para, ME_FindItemFwd(item, diRun), 0);
4253 item_end = ME_FindItemFwd(item, diStartRowOrParagraphOrEnd);
4254 if (item_end->type == diStartRow) {
4255 nNextLineOfs = ME_CharOfsFromRunOfs(editor, para, ME_FindItemFwd(item_end, diRun), 0);
4256 } else {
4257 ME_DisplayItem *endRun = ME_FindItemBack(item_end, diRun);
4258 assert(endRun && endRun->member.run.nFlags & MERF_ENDPARA);
4259 nNextLineOfs = item_end->member.para.nCharOfs - endRun->member.run.len;
4261 nChars = nNextLineOfs - nThisLineOfs;
4262 TRACE("EM_LINELENGTH(%ld)==%d\n",wParam, nChars);
4263 return nChars;
4265 case EM_EXLIMITTEXT:
4267 if ((int)lParam < 0)
4268 return 0;
4269 if (lParam == 0)
4270 editor->nTextLimit = 65536;
4271 else
4272 editor->nTextLimit = (int) lParam;
4273 return 0;
4275 case EM_LIMITTEXT:
4277 if (wParam == 0)
4278 editor->nTextLimit = 65536;
4279 else
4280 editor->nTextLimit = (int) wParam;
4281 return 0;
4283 case EM_GETLIMITTEXT:
4285 return editor->nTextLimit;
4287 case EM_FINDTEXT:
4289 LRESULT r;
4290 if(!unicode){
4291 FINDTEXTA *ft = (FINDTEXTA *)lParam;
4292 int nChars = MultiByteToWideChar(CP_ACP, 0, ft->lpstrText, -1, NULL, 0);
4293 WCHAR *tmp;
4295 if ((tmp = ALLOC_N_OBJ(WCHAR, nChars)) != NULL)
4296 MultiByteToWideChar(CP_ACP, 0, ft->lpstrText, -1, tmp, nChars);
4297 r = ME_FindText(editor, wParam, &ft->chrg, tmp, NULL);
4298 FREE_OBJ( tmp );
4299 }else{
4300 FINDTEXTW *ft = (FINDTEXTW *)lParam;
4301 r = ME_FindText(editor, wParam, &ft->chrg, ft->lpstrText, NULL);
4303 return r;
4305 case EM_FINDTEXTEX:
4307 LRESULT r;
4308 if(!unicode){
4309 FINDTEXTEXA *ex = (FINDTEXTEXA *)lParam;
4310 int nChars = MultiByteToWideChar(CP_ACP, 0, ex->lpstrText, -1, NULL, 0);
4311 WCHAR *tmp;
4313 if ((tmp = ALLOC_N_OBJ(WCHAR, nChars)) != NULL)
4314 MultiByteToWideChar(CP_ACP, 0, ex->lpstrText, -1, tmp, nChars);
4315 r = ME_FindText(editor, wParam, &ex->chrg, tmp, &ex->chrgText);
4316 FREE_OBJ( tmp );
4317 }else{
4318 FINDTEXTEXW *ex = (FINDTEXTEXW *)lParam;
4319 r = ME_FindText(editor, wParam, &ex->chrg, ex->lpstrText, &ex->chrgText);
4321 return r;
4323 case EM_FINDTEXTW:
4325 FINDTEXTW *ft = (FINDTEXTW *)lParam;
4326 return ME_FindText(editor, wParam, &ft->chrg, ft->lpstrText, NULL);
4328 case EM_FINDTEXTEXW:
4330 FINDTEXTEXW *ex = (FINDTEXTEXW *)lParam;
4331 return ME_FindText(editor, wParam, &ex->chrg, ex->lpstrText, &ex->chrgText);
4333 case EM_GETZOOM:
4334 if (!wParam || !lParam)
4335 return FALSE;
4336 *(int *)wParam = editor->nZoomNumerator;
4337 *(int *)lParam = editor->nZoomDenominator;
4338 return TRUE;
4339 case EM_SETZOOM:
4340 return ME_SetZoom(editor, wParam, lParam);
4341 case EM_CHARFROMPOS:
4343 ME_Cursor cursor;
4344 if (ME_CharFromPos(editor, ((POINTL *)lParam)->x, ((POINTL *)lParam)->y,
4345 &cursor, NULL))
4346 return ME_GetCursorOfs(&cursor);
4347 else
4348 return -1;
4350 case EM_POSFROMCHAR:
4352 ME_DisplayItem *pPara, *pRun;
4353 int nCharOfs, nOffset, nLength;
4354 POINTL pt = {0,0};
4356 nCharOfs = wParam;
4357 /* detect which API version we're dealing with */
4358 if (wParam >= 0x40000)
4359 nCharOfs = lParam;
4360 nLength = ME_GetTextLength(editor);
4361 nCharOfs = min(nCharOfs, nLength);
4362 nCharOfs = max(nCharOfs, 0);
4364 ME_RunOfsFromCharOfs(editor, nCharOfs, &pPara, &pRun, &nOffset);
4365 assert(pRun->type == diRun);
4366 pt.y = pRun->member.run.pt.y;
4367 pt.x = pRun->member.run.pt.x + ME_PointFromChar(editor, &pRun->member.run, nOffset, TRUE);
4368 pt.y += pPara->member.para.pt.y + editor->rcFormat.top;
4369 pt.x += editor->rcFormat.left;
4371 pt.x -= editor->horz_si.nPos;
4372 pt.y -= editor->vert_si.nPos;
4374 if (wParam >= 0x40000) {
4375 *(POINTL *)wParam = pt;
4377 return (wParam >= 0x40000) ? 0 : MAKELONG( pt.x, pt.y );
4379 case WM_CREATE:
4380 return ME_WmCreate(editor, lParam, unicode);
4381 case WM_DESTROY:
4382 ME_DestroyEditor(editor);
4383 return 0;
4384 case WM_SETCURSOR:
4386 POINT cursor_pos;
4387 if (wParam == (WPARAM)editor->hWnd && GetCursorPos(&cursor_pos) &&
4388 ScreenToClient(editor->hWnd, &cursor_pos))
4389 ME_LinkNotify(editor, msg, 0, MAKELPARAM(cursor_pos.x, cursor_pos.y));
4390 return ME_SetCursor(editor);
4392 case WM_LBUTTONDBLCLK:
4393 case WM_LBUTTONDOWN:
4395 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4396 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4397 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4398 return 0;
4399 ITextHost_TxSetFocus(editor->texthost);
4400 ME_LButtonDown(editor, (short)LOWORD(lParam), (short)HIWORD(lParam),
4401 ME_CalculateClickCount(editor, msg, wParam, lParam));
4402 ITextHost_TxSetCapture(editor->texthost, TRUE);
4403 editor->bMouseCaptured = TRUE;
4404 ME_LinkNotify(editor, msg, wParam, lParam);
4405 if (!ME_SetCursor(editor)) goto do_default;
4406 break;
4408 case WM_MOUSEMOVE:
4409 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4410 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4411 return 0;
4412 if (editor->bMouseCaptured)
4413 ME_MouseMove(editor, (short)LOWORD(lParam), (short)HIWORD(lParam));
4414 else
4415 ME_LinkNotify(editor, msg, wParam, lParam);
4416 /* Set cursor if mouse is captured, since WM_SETCURSOR won't be received. */
4417 if (editor->bMouseCaptured)
4418 ME_SetCursor(editor);
4419 break;
4420 case WM_LBUTTONUP:
4421 if (editor->bMouseCaptured) {
4422 ITextHost_TxSetCapture(editor->texthost, FALSE);
4423 editor->bMouseCaptured = FALSE;
4425 if (editor->nSelectionType == stDocument)
4426 editor->nSelectionType = stPosition;
4427 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4428 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4429 return 0;
4430 else
4432 ME_SetCursor(editor);
4433 ME_LinkNotify(editor, msg, wParam, lParam);
4435 break;
4436 case WM_RBUTTONUP:
4437 case WM_RBUTTONDOWN:
4438 case WM_RBUTTONDBLCLK:
4439 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4440 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4441 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4442 return 0;
4443 ME_LinkNotify(editor, msg, wParam, lParam);
4444 goto do_default;
4445 case WM_CONTEXTMENU:
4446 if (!ME_ShowContextMenu(editor, (short)LOWORD(lParam), (short)HIWORD(lParam)))
4447 goto do_default;
4448 break;
4449 case WM_SETFOCUS:
4450 editor->bHaveFocus = TRUE;
4451 ME_ShowCaret(editor);
4452 ME_SendOldNotify(editor, EN_SETFOCUS);
4453 if (!editor->bHideSelection && !(editor->styleFlags & ES_NOHIDESEL))
4454 ME_InvalidateSelection( editor );
4455 return 0;
4456 case WM_KILLFOCUS:
4457 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4458 editor->bHaveFocus = FALSE;
4459 editor->wheel_remain = 0;
4460 ME_HideCaret(editor);
4461 ME_SendOldNotify(editor, EN_KILLFOCUS);
4462 if (!editor->bHideSelection && !(editor->styleFlags & ES_NOHIDESEL))
4463 ME_InvalidateSelection( editor );
4464 return 0;
4465 case WM_COMMAND:
4466 TRACE("editor wnd command = %d\n", LOWORD(wParam));
4467 return 0;
4468 case WM_KEYUP:
4469 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4470 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4471 return 0;
4472 goto do_default;
4473 case WM_KEYDOWN:
4474 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4475 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4476 return 0;
4477 if (ME_KeyDown(editor, LOWORD(wParam)))
4478 return 0;
4479 goto do_default;
4480 case WM_CHAR:
4481 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4482 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4483 return 0;
4484 return ME_Char(editor, wParam, lParam, unicode);
4485 case WM_UNICHAR:
4486 if (unicode)
4488 if(wParam == UNICODE_NOCHAR) return TRUE;
4489 if(wParam <= 0x000fffff)
4491 if(wParam > 0xffff) /* convert to surrogates */
4493 wParam -= 0x10000;
4494 ME_Char(editor, (wParam >> 10) + 0xd800, 0, TRUE);
4495 ME_Char(editor, (wParam & 0x03ff) + 0xdc00, 0, TRUE);
4496 } else {
4497 ME_Char(editor, wParam, 0, TRUE);
4500 return 0;
4502 break;
4503 case EM_STOPGROUPTYPING:
4504 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4505 return 0;
4506 case WM_HSCROLL:
4508 const int scrollUnit = 7;
4510 switch(LOWORD(wParam))
4512 case SB_LEFT:
4513 ME_ScrollAbs(editor, 0, 0);
4514 break;
4515 case SB_RIGHT:
4516 ME_ScrollAbs(editor,
4517 editor->horz_si.nMax - (int)editor->horz_si.nPage,
4518 editor->vert_si.nMax - (int)editor->vert_si.nPage);
4519 break;
4520 case SB_LINELEFT:
4521 ME_ScrollLeft(editor, scrollUnit);
4522 break;
4523 case SB_LINERIGHT:
4524 ME_ScrollRight(editor, scrollUnit);
4525 break;
4526 case SB_PAGELEFT:
4527 ME_ScrollLeft(editor, editor->sizeWindow.cx);
4528 break;
4529 case SB_PAGERIGHT:
4530 ME_ScrollRight(editor, editor->sizeWindow.cx);
4531 break;
4532 case SB_THUMBTRACK:
4533 case SB_THUMBPOSITION:
4535 int pos = HIWORD(wParam);
4536 if (editor->horz_si.nMax > 0xffff)
4537 pos = MulDiv(pos, editor->horz_si.nMax, 0xffff);
4538 ME_HScrollAbs(editor, pos);
4539 break;
4542 break;
4544 case EM_SCROLL: /* fall through */
4545 case WM_VSCROLL:
4547 int origNPos;
4548 int lineHeight = get_default_line_height( editor );
4550 origNPos = editor->vert_si.nPos;
4552 switch(LOWORD(wParam))
4554 case SB_TOP:
4555 ME_ScrollAbs(editor, 0, 0);
4556 break;
4557 case SB_BOTTOM:
4558 ME_ScrollAbs(editor,
4559 editor->horz_si.nMax - (int)editor->horz_si.nPage,
4560 editor->vert_si.nMax - (int)editor->vert_si.nPage);
4561 break;
4562 case SB_LINEUP:
4563 ME_ScrollUp(editor,lineHeight);
4564 break;
4565 case SB_LINEDOWN:
4566 ME_ScrollDown(editor,lineHeight);
4567 break;
4568 case SB_PAGEUP:
4569 ME_ScrollUp(editor,editor->sizeWindow.cy);
4570 break;
4571 case SB_PAGEDOWN:
4572 ME_ScrollDown(editor,editor->sizeWindow.cy);
4573 break;
4574 case SB_THUMBTRACK:
4575 case SB_THUMBPOSITION:
4577 int pos = HIWORD(wParam);
4578 if (editor->vert_si.nMax > 0xffff)
4579 pos = MulDiv(pos, editor->vert_si.nMax, 0xffff);
4580 ME_VScrollAbs(editor, pos);
4581 break;
4584 if (msg == EM_SCROLL)
4585 return 0x00010000 | (((editor->vert_si.nPos - origNPos)/lineHeight) & 0xffff);
4586 break;
4588 case WM_MOUSEWHEEL:
4590 int delta;
4591 BOOL ctrl_is_down;
4593 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4594 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4595 return 0;
4597 ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
4599 delta = GET_WHEEL_DELTA_WPARAM(wParam);
4601 /* if scrolling changes direction, ignore left overs */
4602 if ((delta < 0 && editor->wheel_remain < 0) ||
4603 (delta > 0 && editor->wheel_remain > 0))
4604 editor->wheel_remain += delta;
4605 else
4606 editor->wheel_remain = delta;
4608 if (editor->wheel_remain)
4610 if (ctrl_is_down) {
4611 int numerator;
4612 if (!editor->nZoomNumerator || !editor->nZoomDenominator)
4614 numerator = 100;
4615 } else {
4616 numerator = editor->nZoomNumerator * 100 / editor->nZoomDenominator;
4618 numerator += calc_wheel_change( &editor->wheel_remain, 10 );
4619 if (numerator >= 10 && numerator <= 500)
4620 ME_SetZoom(editor, numerator, 100);
4621 } else {
4622 UINT max_lines = 3;
4623 int lines = 0;
4625 SystemParametersInfoW( SPI_GETWHEELSCROLLLINES, 0, &max_lines, 0 );
4626 if (max_lines)
4627 lines = calc_wheel_change( &editor->wheel_remain, (int)max_lines );
4628 if (lines)
4629 ME_ScrollDown( editor, -lines * get_default_line_height( editor ) );
4632 break;
4634 case EM_GETRECT:
4636 *((RECT *)lParam) = editor->rcFormat;
4637 if (editor->bDefaultFormatRect)
4638 ((RECT *)lParam)->left -= editor->selofs;
4639 return 0;
4641 case EM_SETRECT:
4642 case EM_SETRECTNP:
4644 if (lParam)
4646 int border = 0;
4647 RECT clientRect;
4648 RECT *rc = (RECT *)lParam;
4650 border = editor->exStyleFlags & WS_EX_CLIENTEDGE ? 1 : 0;
4651 ITextHost_TxGetClientRect(editor->texthost, &clientRect);
4652 if (wParam == 0)
4654 editor->rcFormat.top = max(0, rc->top - border);
4655 editor->rcFormat.left = max(0, rc->left - border);
4656 editor->rcFormat.bottom = min(clientRect.bottom, rc->bottom);
4657 editor->rcFormat.right = min(clientRect.right, rc->right + border);
4658 } else if (wParam == 1) {
4659 /* MSDN incorrectly says a wParam value of 1 causes the
4660 * lParam rect to be used as a relative offset,
4661 * however, the tests show it just prevents min/max bound
4662 * checking. */
4663 editor->rcFormat.top = rc->top - border;
4664 editor->rcFormat.left = rc->left - border;
4665 editor->rcFormat.bottom = rc->bottom;
4666 editor->rcFormat.right = rc->right + border;
4667 } else {
4668 return 0;
4670 editor->bDefaultFormatRect = FALSE;
4672 else
4674 ME_SetDefaultFormatRect(editor);
4675 editor->bDefaultFormatRect = TRUE;
4677 ME_MarkAllForWrapping(editor);
4678 ME_WrapMarkedParagraphs(editor);
4679 ME_UpdateScrollBar(editor);
4680 if (msg != EM_SETRECTNP)
4681 ME_Repaint(editor);
4682 return 0;
4684 case EM_REQUESTRESIZE:
4685 ME_SendRequestResize(editor, TRUE);
4686 return 0;
4687 case WM_SETREDRAW:
4688 goto do_default;
4689 case WM_WINDOWPOSCHANGED:
4691 RECT clientRect;
4692 WINDOWPOS *winpos = (WINDOWPOS *)lParam;
4694 if (winpos->flags & SWP_NOCLIENTSIZE) goto do_default;
4695 ITextHost_TxGetClientRect(editor->texthost, &clientRect);
4696 if (editor->bDefaultFormatRect) {
4697 ME_SetDefaultFormatRect(editor);
4698 } else {
4699 editor->rcFormat.right += clientRect.right - editor->prevClientRect.right;
4700 editor->rcFormat.bottom += clientRect.bottom - editor->prevClientRect.bottom;
4702 editor->prevClientRect = clientRect;
4703 ME_RewrapRepaint(editor);
4704 goto do_default;
4706 /* IME messages to make richedit controls IME aware */
4707 case WM_IME_SETCONTEXT:
4708 case WM_IME_CONTROL:
4709 case WM_IME_SELECT:
4710 case WM_IME_COMPOSITIONFULL:
4711 return 0;
4712 case WM_IME_STARTCOMPOSITION:
4714 editor->imeStartIndex=ME_GetCursorOfs(&editor->pCursors[0]);
4715 ME_DeleteSelection(editor);
4716 ME_CommitUndo(editor);
4717 ME_UpdateRepaint(editor, FALSE);
4718 return 0;
4720 case WM_IME_COMPOSITION:
4722 HIMC hIMC;
4724 ME_Style *style = ME_GetInsertStyle(editor, 0);
4725 hIMC = ITextHost_TxImmGetContext(editor->texthost);
4726 ME_DeleteSelection(editor);
4727 ME_SaveTempStyle(editor, style);
4728 if (lParam & (GCS_RESULTSTR|GCS_COMPSTR))
4730 LPWSTR lpCompStr = NULL;
4731 DWORD dwBufLen;
4732 DWORD dwIndex = lParam & GCS_RESULTSTR;
4733 if (!dwIndex)
4734 dwIndex = GCS_COMPSTR;
4736 dwBufLen = ImmGetCompositionStringW(hIMC, dwIndex, NULL, 0);
4737 lpCompStr = HeapAlloc(GetProcessHeap(),0,dwBufLen + sizeof(WCHAR));
4738 ImmGetCompositionStringW(hIMC, dwIndex, lpCompStr, dwBufLen);
4739 lpCompStr[dwBufLen/sizeof(WCHAR)] = 0;
4740 ME_InsertTextFromCursor(editor,0,lpCompStr,dwBufLen/sizeof(WCHAR),style);
4741 HeapFree(GetProcessHeap(), 0, lpCompStr);
4743 if (dwIndex == GCS_COMPSTR)
4744 ME_SetSelection(editor,editor->imeStartIndex,
4745 editor->imeStartIndex + dwBufLen/sizeof(WCHAR));
4747 ME_ReleaseStyle(style);
4748 ME_CommitUndo(editor);
4749 ME_UpdateRepaint(editor, FALSE);
4750 return 0;
4752 case WM_IME_ENDCOMPOSITION:
4754 ME_DeleteSelection(editor);
4755 editor->imeStartIndex=-1;
4756 return 0;
4758 case EM_GETOLEINTERFACE:
4760 if (!editor->reOle)
4761 if (!CreateIRichEditOle(NULL, editor, (LPVOID *)&editor->reOle))
4762 return 0;
4763 *(LPVOID *)lParam = editor->reOle;
4764 IRichEditOle_AddRef(editor->reOle);
4765 return 1;
4767 case EM_GETPASSWORDCHAR:
4769 return editor->cPasswordMask;
4771 case EM_SETOLECALLBACK:
4772 if(editor->lpOleCallback)
4773 IRichEditOleCallback_Release(editor->lpOleCallback);
4774 editor->lpOleCallback = (IRichEditOleCallback*)lParam;
4775 if(editor->lpOleCallback)
4776 IRichEditOleCallback_AddRef(editor->lpOleCallback);
4777 return TRUE;
4778 case EM_GETWORDBREAKPROC:
4779 return (LRESULT)editor->pfnWordBreak;
4780 case EM_SETWORDBREAKPROC:
4782 EDITWORDBREAKPROCW pfnOld = editor->pfnWordBreak;
4784 editor->pfnWordBreak = (EDITWORDBREAKPROCW)lParam;
4785 return (LRESULT)pfnOld;
4787 case EM_GETTEXTMODE:
4788 return editor->mode;
4789 case EM_SETTEXTMODE:
4791 int mask = 0;
4792 int changes = 0;
4794 if (ME_GetTextLength(editor) ||
4795 !list_empty( &editor->undo_stack ) || !list_empty( &editor->redo_stack ))
4796 return E_UNEXPECTED;
4798 /* Check for mutually exclusive flags in adjacent bits of wParam */
4799 if ((wParam & (TM_RICHTEXT | TM_MULTILEVELUNDO | TM_MULTICODEPAGE)) &
4800 (wParam & (TM_PLAINTEXT | TM_SINGLELEVELUNDO | TM_SINGLECODEPAGE)) << 1)
4801 return E_INVALIDARG;
4803 if (wParam & (TM_RICHTEXT | TM_PLAINTEXT))
4805 mask |= TM_RICHTEXT | TM_PLAINTEXT;
4806 changes |= wParam & (TM_RICHTEXT | TM_PLAINTEXT);
4807 if (wParam & TM_PLAINTEXT) {
4808 /* Clear selection since it should be possible to select the
4809 * end of text run for rich text */
4810 ME_InvalidateSelection(editor);
4811 ME_SetCursorToStart(editor, &editor->pCursors[0]);
4812 editor->pCursors[1] = editor->pCursors[0];
4813 /* plain text can only have the default style. */
4814 ME_ClearTempStyle(editor);
4815 ME_AddRefStyle(editor->pBuffer->pDefaultStyle);
4816 ME_ReleaseStyle(editor->pCursors[0].pRun->member.run.style);
4817 editor->pCursors[0].pRun->member.run.style = editor->pBuffer->pDefaultStyle;
4820 /* FIXME: Currently no support for undo level and code page options */
4821 editor->mode = (editor->mode & ~mask) | changes;
4822 return 0;
4824 case EM_SETPASSWORDCHAR:
4826 editor->cPasswordMask = wParam;
4827 ME_RewrapRepaint(editor);
4828 return 0;
4830 case EM_SETTARGETDEVICE:
4831 if (wParam == 0)
4833 BOOL new = (lParam == 0 && (editor->styleFlags & ES_MULTILINE));
4834 if (editor->nAvailWidth || editor->bWordWrap != new)
4836 editor->bWordWrap = new;
4837 editor->nAvailWidth = 0; /* wrap to client area */
4838 ME_RewrapRepaint(editor);
4840 } else {
4841 int width = max(0, lParam);
4842 if ((editor->styleFlags & ES_MULTILINE) &&
4843 (!editor->bWordWrap || editor->nAvailWidth != width))
4845 editor->nAvailWidth = width;
4846 editor->bWordWrap = TRUE;
4847 ME_RewrapRepaint(editor);
4849 FIXME("EM_SETTARGETDEVICE doesn't use non-NULL target devices\n");
4851 return TRUE;
4852 default:
4853 do_default:
4854 *phresult = S_FALSE;
4855 break;
4857 return 0L;
4860 static BOOL create_windowed_editor(HWND hwnd, CREATESTRUCTW *create, BOOL emulate_10)
4862 ITextHost *host = ME_CreateTextHost( hwnd, create, emulate_10 );
4863 ME_TextEditor *editor;
4865 if (!host) return FALSE;
4867 editor = ME_MakeEditor( host, emulate_10 );
4868 if (!editor)
4870 ITextHost_Release( host );
4871 return FALSE;
4874 editor->exStyleFlags = GetWindowLongW( hwnd, GWL_EXSTYLE );
4875 editor->styleFlags |= GetWindowLongW( hwnd, GWL_STYLE ) & ES_WANTRETURN;
4876 editor->hWnd = hwnd; /* FIXME: Remove editor's dependence on hWnd */
4877 editor->hwndParent = create->hwndParent;
4879 SetWindowLongPtrW( hwnd, 0, (LONG_PTR)editor );
4881 return TRUE;
4884 static LRESULT RichEditWndProc_common(HWND hWnd, UINT msg, WPARAM wParam,
4885 LPARAM lParam, BOOL unicode)
4887 ME_TextEditor *editor;
4888 HRESULT hresult;
4889 LRESULT lresult = 0;
4891 TRACE("enter hwnd %p msg %04x (%s) %lx %lx, unicode %d\n",
4892 hWnd, msg, get_msg_name(msg), wParam, lParam, unicode);
4894 editor = (ME_TextEditor *)GetWindowLongPtrW(hWnd, 0);
4895 if (!editor)
4897 if (msg == WM_NCCREATE)
4899 CREATESTRUCTW *pcs = (CREATESTRUCTW *)lParam;
4901 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd, pcs->style);
4902 return create_windowed_editor( hWnd, pcs, FALSE );
4904 else
4906 return DefWindowProcW(hWnd, msg, wParam, lParam);
4910 switch (msg)
4912 case WM_PAINT:
4914 HDC hDC;
4915 RECT rc;
4916 PAINTSTRUCT ps;
4918 hDC = BeginPaint(editor->hWnd, &ps);
4919 if (!editor->bEmulateVersion10 || (editor->nEventMask & ENM_UPDATE))
4920 ME_SendOldNotify(editor, EN_UPDATE);
4921 /* Erase area outside of the formatting rectangle */
4922 if (ps.rcPaint.top < editor->rcFormat.top)
4924 rc = ps.rcPaint;
4925 rc.bottom = editor->rcFormat.top;
4926 FillRect(hDC, &rc, editor->hbrBackground);
4927 ps.rcPaint.top = editor->rcFormat.top;
4929 if (ps.rcPaint.bottom > editor->rcFormat.bottom) {
4930 rc = ps.rcPaint;
4931 rc.top = editor->rcFormat.bottom;
4932 FillRect(hDC, &rc, editor->hbrBackground);
4933 ps.rcPaint.bottom = editor->rcFormat.bottom;
4935 if (ps.rcPaint.left < editor->rcFormat.left) {
4936 rc = ps.rcPaint;
4937 rc.right = editor->rcFormat.left;
4938 FillRect(hDC, &rc, editor->hbrBackground);
4939 ps.rcPaint.left = editor->rcFormat.left;
4941 if (ps.rcPaint.right > editor->rcFormat.right) {
4942 rc = ps.rcPaint;
4943 rc.left = editor->rcFormat.right;
4944 FillRect(hDC, &rc, editor->hbrBackground);
4945 ps.rcPaint.right = editor->rcFormat.right;
4948 ME_PaintContent(editor, hDC, &ps.rcPaint);
4949 EndPaint(editor->hWnd, &ps);
4950 return 0;
4952 case WM_ERASEBKGND:
4954 HDC hDC = (HDC)wParam;
4955 RECT rc;
4957 if (GetUpdateRect(editor->hWnd, &rc, TRUE))
4958 FillRect(hDC, &rc, editor->hbrBackground);
4959 return 1;
4961 case EM_SETOPTIONS:
4963 DWORD dwStyle;
4964 const DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
4965 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN |
4966 ECO_SELECTIONBAR;
4967 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
4968 dwStyle = GetWindowLongW(hWnd, GWL_STYLE);
4969 dwStyle = (dwStyle & ~mask) | (lresult & mask);
4970 SetWindowLongW(hWnd, GWL_STYLE, dwStyle);
4971 return lresult;
4973 case EM_SETREADONLY:
4975 DWORD dwStyle;
4976 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
4977 dwStyle = GetWindowLongW(hWnd, GWL_STYLE);
4978 dwStyle &= ~ES_READONLY;
4979 if (wParam)
4980 dwStyle |= ES_READONLY;
4981 SetWindowLongW(hWnd, GWL_STYLE, dwStyle);
4982 return lresult;
4984 default:
4985 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
4988 if (hresult == S_FALSE)
4989 lresult = DefWindowProcW(hWnd, msg, wParam, lParam);
4991 TRACE("exit hwnd %p msg %04x (%s) %lx %lx, unicode %d -> %lu\n",
4992 hWnd, msg, get_msg_name(msg), wParam, lParam, unicode, lresult);
4994 return lresult;
4997 static LRESULT WINAPI RichEditWndProcW(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
4999 BOOL unicode = TRUE;
5001 /* Under Win9x RichEdit20W returns ANSI strings, see the tests. */
5002 if (msg == WM_GETTEXT && (GetVersion() & 0x80000000))
5003 unicode = FALSE;
5005 return RichEditWndProc_common(hWnd, msg, wParam, lParam, unicode);
5008 static LRESULT WINAPI RichEditWndProcA(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5010 return RichEditWndProc_common(hWnd, msg, wParam, lParam, FALSE);
5013 /******************************************************************
5014 * RichEditANSIWndProc (RICHED20.10)
5016 LRESULT WINAPI RichEditANSIWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5018 return RichEditWndProcA(hWnd, msg, wParam, lParam);
5021 /******************************************************************
5022 * RichEdit10ANSIWndProc (RICHED20.9)
5024 LRESULT WINAPI RichEdit10ANSIWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5026 if (msg == WM_NCCREATE && !GetWindowLongPtrW(hWnd, 0))
5028 CREATESTRUCTW *pcs = (CREATESTRUCTW *)lParam;
5030 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd, pcs->style);
5031 return create_windowed_editor( hWnd, pcs, TRUE );
5033 return RichEditANSIWndProc(hWnd, msg, wParam, lParam);
5036 void ME_SendOldNotify(ME_TextEditor *editor, int nCode)
5038 ITextHost_TxNotify(editor->texthost, nCode, NULL);
5041 /* Fill buffer with srcChars unicode characters from the start cursor.
5043 * buffer: destination buffer
5044 * buflen: length of buffer in characters excluding the NULL terminator.
5045 * start: start of editor text to copy into buffer.
5046 * srcChars: Number of characters to use from the editor text.
5047 * bCRLF: if true, replaces all end of lines with \r\n pairs.
5049 * returns the number of characters written excluding the NULL terminator.
5051 * The written text is always NULL terminated.
5053 int ME_GetTextW(ME_TextEditor *editor, WCHAR *buffer, int buflen,
5054 const ME_Cursor *start, int srcChars, BOOL bCRLF,
5055 BOOL bEOP)
5057 ME_DisplayItem *pRun, *pNextRun;
5058 const WCHAR *pStart = buffer;
5059 const WCHAR cr_lf[] = {'\r', '\n', 0};
5060 const WCHAR *str;
5061 int nLen;
5063 /* bCRLF flag is only honored in 2.0 and up. 1.0 must always return text verbatim */
5064 if (editor->bEmulateVersion10) bCRLF = FALSE;
5066 pRun = start->pRun;
5067 assert(pRun);
5068 pNextRun = ME_FindItemFwd(pRun, diRun);
5070 nLen = pRun->member.run.len - start->nOffset;
5071 str = get_text( &pRun->member.run, start->nOffset );
5073 while (srcChars && buflen && pNextRun)
5075 int nFlags = pRun->member.run.nFlags;
5077 if (bCRLF && nFlags & MERF_ENDPARA && ~nFlags & MERF_ENDCELL)
5079 if (buflen == 1) break;
5080 /* FIXME: native fails to reduce srcChars here for WM_GETTEXT or
5081 * EM_GETTEXTEX, however, this is done for copying text which
5082 * also uses this function. */
5083 srcChars -= min(nLen, srcChars);
5084 nLen = 2;
5085 str = cr_lf;
5086 } else {
5087 nLen = min(nLen, srcChars);
5088 srcChars -= nLen;
5091 nLen = min(nLen, buflen);
5092 buflen -= nLen;
5094 CopyMemory(buffer, str, sizeof(WCHAR) * nLen);
5096 buffer += nLen;
5098 pRun = pNextRun;
5099 pNextRun = ME_FindItemFwd(pRun, diRun);
5101 nLen = pRun->member.run.len;
5102 str = get_text( &pRun->member.run, 0 );
5104 /* append '\r' to the last paragraph. */
5105 if (pRun->next->type == diTextEnd && bEOP)
5107 *buffer = '\r';
5108 buffer ++;
5110 *buffer = 0;
5111 return buffer - pStart;
5114 static BOOL ME_RegisterEditorClass(HINSTANCE hInstance)
5116 WNDCLASSW wcW;
5117 WNDCLASSA wcA;
5119 wcW.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS;
5120 wcW.lpfnWndProc = RichEditWndProcW;
5121 wcW.cbClsExtra = 0;
5122 wcW.cbWndExtra = sizeof(ME_TextEditor *);
5123 wcW.hInstance = NULL; /* hInstance would register DLL-local class */
5124 wcW.hIcon = NULL;
5125 wcW.hCursor = LoadCursorW(NULL, (LPWSTR)IDC_IBEAM);
5126 wcW.hbrBackground = GetStockObject(NULL_BRUSH);
5127 wcW.lpszMenuName = NULL;
5129 if (is_version_nt())
5131 wcW.lpszClassName = RICHEDIT_CLASS20W;
5132 if (!RegisterClassW(&wcW)) return FALSE;
5133 wcW.lpszClassName = MSFTEDIT_CLASS;
5134 if (!RegisterClassW(&wcW)) return FALSE;
5136 else
5138 /* WNDCLASSA/W have the same layout */
5139 wcW.lpszClassName = (LPCWSTR)"RichEdit20W";
5140 if (!RegisterClassA((WNDCLASSA *)&wcW)) return FALSE;
5141 wcW.lpszClassName = (LPCWSTR)"RichEdit50W";
5142 if (!RegisterClassA((WNDCLASSA *)&wcW)) return FALSE;
5145 wcA.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS;
5146 wcA.lpfnWndProc = RichEditWndProcA;
5147 wcA.cbClsExtra = 0;
5148 wcA.cbWndExtra = sizeof(ME_TextEditor *);
5149 wcA.hInstance = NULL; /* hInstance would register DLL-local class */
5150 wcA.hIcon = NULL;
5151 wcA.hCursor = LoadCursorW(NULL, (LPWSTR)IDC_IBEAM);
5152 wcA.hbrBackground = GetStockObject(NULL_BRUSH);
5153 wcA.lpszMenuName = NULL;
5154 wcA.lpszClassName = RICHEDIT_CLASS20A;
5155 if (!RegisterClassA(&wcA)) return FALSE;
5156 wcA.lpszClassName = "RichEdit50A";
5157 if (!RegisterClassA(&wcA)) return FALSE;
5159 return TRUE;
5162 static LRESULT WINAPI REComboWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
5163 /* FIXME: Not implemented */
5164 TRACE("hWnd %p msg %04x (%s) %08lx %08lx\n",
5165 hWnd, msg, get_msg_name(msg), wParam, lParam);
5166 return DefWindowProcW(hWnd, msg, wParam, lParam);
5169 static LRESULT WINAPI REListWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
5170 /* FIXME: Not implemented */
5171 TRACE("hWnd %p msg %04x (%s) %08lx %08lx\n",
5172 hWnd, msg, get_msg_name(msg), wParam, lParam);
5173 return DefWindowProcW(hWnd, msg, wParam, lParam);
5176 /******************************************************************
5177 * REExtendedRegisterClass (RICHED20.8)
5179 * FIXME undocumented
5180 * Need to check for errors and implement controls and callbacks
5182 LRESULT WINAPI REExtendedRegisterClass(void)
5184 WNDCLASSW wcW;
5185 UINT result;
5187 FIXME("semi stub\n");
5189 wcW.cbClsExtra = 0;
5190 wcW.cbWndExtra = 4;
5191 wcW.hInstance = NULL;
5192 wcW.hIcon = NULL;
5193 wcW.hCursor = NULL;
5194 wcW.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
5195 wcW.lpszMenuName = NULL;
5197 if (!ME_ListBoxRegistered)
5199 wcW.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS;
5200 wcW.lpfnWndProc = REListWndProc;
5201 wcW.lpszClassName = REListBox20W;
5202 if (RegisterClassW(&wcW)) ME_ListBoxRegistered = TRUE;
5205 if (!ME_ComboBoxRegistered)
5207 wcW.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS | CS_VREDRAW | CS_HREDRAW;
5208 wcW.lpfnWndProc = REComboWndProc;
5209 wcW.lpszClassName = REComboBox20W;
5210 if (RegisterClassW(&wcW)) ME_ComboBoxRegistered = TRUE;
5213 result = 0;
5214 if (ME_ListBoxRegistered)
5215 result += 1;
5216 if (ME_ComboBoxRegistered)
5217 result += 2;
5219 return result;
5222 static int wchar_comp( const void *key, const void *elem )
5224 return *(const WCHAR *)key - *(const WCHAR *)elem;
5227 /* neutral characters end the url if the next non-neutral character is a space character,
5228 otherwise they are included in the url. */
5229 static BOOL isurlneutral( WCHAR c )
5231 /* NB this list is sorted */
5232 static const WCHAR neutral_chars[] = {'!','\"','\'','(',')',',','-','.',':',';','<','>','?','[',']','{','}'};
5234 /* Some shortcuts */
5235 if (isalnum( c )) return FALSE;
5236 if (c > neutral_chars[sizeof(neutral_chars) / sizeof(neutral_chars[0]) - 1]) return FALSE;
5238 return !!bsearch( &c, neutral_chars, sizeof(neutral_chars) / sizeof(neutral_chars[0]),
5239 sizeof(c), wchar_comp );
5243 * This proc takes a selection, and scans it forward in order to select the span
5244 * of a possible URL candidate. A possible URL candidate must start with isalnum
5245 * or one of the following special characters: *|/\+%#@ and must consist entirely
5246 * of the characters allowed to start the URL, plus : (colon) which may occur
5247 * at most once, and not at either end.
5249 static BOOL ME_FindNextURLCandidate(ME_TextEditor *editor,
5250 const ME_Cursor *start,
5251 int nChars,
5252 ME_Cursor *candidate_min,
5253 ME_Cursor *candidate_max)
5255 ME_Cursor cursor = *start, neutral_end, space_end;
5256 BOOL candidateStarted = FALSE, quoted = FALSE;
5257 WCHAR c;
5259 while (nChars > 0)
5261 WCHAR *str = get_text( &cursor.pRun->member.run, 0 );
5262 int run_len = cursor.pRun->member.run.len;
5264 nChars -= run_len - cursor.nOffset;
5266 /* Find start of candidate */
5267 if (!candidateStarted)
5269 while (cursor.nOffset < run_len)
5271 c = str[cursor.nOffset];
5272 if (!isspaceW( c ) && !isurlneutral( c ))
5274 *candidate_min = cursor;
5275 candidateStarted = TRUE;
5276 neutral_end.pPara = NULL;
5277 space_end.pPara = NULL;
5278 cursor.nOffset++;
5279 break;
5281 quoted = (c == '<');
5282 cursor.nOffset++;
5286 /* Find end of candidate */
5287 if (candidateStarted)
5289 while (cursor.nOffset < run_len)
5291 c = str[cursor.nOffset];
5292 if (isspaceW( c ))
5294 if (quoted && c != '\r')
5296 if (!space_end.pPara)
5298 if (neutral_end.pPara)
5299 space_end = neutral_end;
5300 else
5301 space_end = cursor;
5304 else
5305 goto done;
5307 else if (isurlneutral( c ))
5309 if (quoted && c == '>')
5311 neutral_end.pPara = NULL;
5312 space_end.pPara = NULL;
5313 goto done;
5315 if (!neutral_end.pPara)
5316 neutral_end = cursor;
5318 else
5319 neutral_end.pPara = NULL;
5321 cursor.nOffset++;
5325 cursor.nOffset = 0;
5326 if (!ME_NextRun(&cursor.pPara, &cursor.pRun, TRUE))
5327 goto done;
5330 done:
5331 if (candidateStarted)
5333 if (space_end.pPara)
5334 *candidate_max = space_end;
5335 else if (neutral_end.pPara)
5336 *candidate_max = neutral_end;
5337 else
5338 *candidate_max = cursor;
5339 return TRUE;
5341 *candidate_max = *candidate_min = cursor;
5342 return FALSE;
5346 * This proc evaluates the selection and returns TRUE if it can be considered an URL
5348 static BOOL ME_IsCandidateAnURL(ME_TextEditor *editor, const ME_Cursor *start, int nChars)
5350 #define MAX_PREFIX_LEN 9
5351 struct prefix_s {
5352 const WCHAR text[MAX_PREFIX_LEN];
5353 int length;
5354 }prefixes[] = {
5355 {{'p','r','o','s','p','e','r','o',':'}, 9},
5356 {{'t','e','l','n','e','t',':'}, 7},
5357 {{'g','o','p','h','e','r',':'}, 7},
5358 {{'m','a','i','l','t','o',':'}, 7},
5359 {{'h','t','t','p','s',':'}, 6},
5360 {{'f','i','l','e',':'}, 5},
5361 {{'n','e','w','s',':'}, 5},
5362 {{'w','a','i','s',':'}, 5},
5363 {{'n','n','t','p',':'}, 5},
5364 {{'h','t','t','p',':'}, 5},
5365 {{'w','w','w','.'}, 4},
5366 {{'f','t','p',':'}, 4},
5368 WCHAR bufferW[MAX_PREFIX_LEN + 1];
5369 unsigned int i;
5371 ME_GetTextW(editor, bufferW, MAX_PREFIX_LEN, start, nChars, FALSE, FALSE);
5372 for (i = 0; i < sizeof(prefixes) / sizeof(*prefixes); i++)
5374 if (nChars < prefixes[i].length) continue;
5375 if (!memcmp(prefixes[i].text, bufferW, prefixes[i].length * sizeof(WCHAR)))
5376 return TRUE;
5378 return FALSE;
5379 #undef MAX_PREFIX_LEN
5383 * This proc walks through the indicated selection and evaluates whether each
5384 * section identified by ME_FindNextURLCandidate and in-between sections have
5385 * their proper CFE_LINK attributes set or unset. If the CFE_LINK attribute is
5386 * not what it is supposed to be, this proc sets or unsets it as appropriate.
5388 * Since this function can cause runs to be split, do not depend on the value
5389 * of the start cursor at the end of the function.
5391 * nChars may be set to INT_MAX to update to the end of the text.
5393 * Returns TRUE if at least one section was modified.
5395 static BOOL ME_UpdateLinkAttribute(ME_TextEditor *editor, ME_Cursor *start, int nChars)
5397 BOOL modified = FALSE;
5398 ME_Cursor startCur = *start;
5400 if (!editor->AutoURLDetect_bEnable) return FALSE;
5404 CHARFORMAT2W link;
5405 ME_Cursor candidateStart, candidateEnd;
5407 if (ME_FindNextURLCandidate(editor, &startCur, nChars,
5408 &candidateStart, &candidateEnd))
5410 /* Section before candidate is not an URL */
5411 int cMin = ME_GetCursorOfs(&candidateStart);
5412 int cMax = ME_GetCursorOfs(&candidateEnd);
5414 if (!ME_IsCandidateAnURL(editor, &candidateStart, cMax - cMin))
5415 candidateStart = candidateEnd;
5416 nChars -= cMax - ME_GetCursorOfs(&startCur);
5418 else
5420 /* No more candidates until end of selection */
5421 nChars = 0;
5424 if (startCur.pRun != candidateStart.pRun ||
5425 startCur.nOffset != candidateStart.nOffset)
5427 /* CFE_LINK effect should be consistently unset */
5428 link.cbSize = sizeof(link);
5429 ME_GetCharFormat(editor, &startCur, &candidateStart, &link);
5430 if (!(link.dwMask & CFM_LINK) || (link.dwEffects & CFE_LINK))
5432 /* CFE_LINK must be unset from this range */
5433 memset(&link, 0, sizeof(CHARFORMAT2W));
5434 link.cbSize = sizeof(link);
5435 link.dwMask = CFM_LINK;
5436 link.dwEffects = 0;
5437 ME_SetCharFormat(editor, &startCur, &candidateStart, &link);
5438 /* Update candidateEnd since setting character formats may split
5439 * runs, which can cause a cursor to be at an invalid offset within
5440 * a split run. */
5441 while (candidateEnd.nOffset >= candidateEnd.pRun->member.run.len)
5443 candidateEnd.nOffset -= candidateEnd.pRun->member.run.len;
5444 candidateEnd.pRun = ME_FindItemFwd(candidateEnd.pRun, diRun);
5446 modified = TRUE;
5449 if (candidateStart.pRun != candidateEnd.pRun ||
5450 candidateStart.nOffset != candidateEnd.nOffset)
5452 /* CFE_LINK effect should be consistently set */
5453 link.cbSize = sizeof(link);
5454 ME_GetCharFormat(editor, &candidateStart, &candidateEnd, &link);
5455 if (!(link.dwMask & CFM_LINK) || !(link.dwEffects & CFE_LINK))
5457 /* CFE_LINK must be set on this range */
5458 memset(&link, 0, sizeof(CHARFORMAT2W));
5459 link.cbSize = sizeof(link);
5460 link.dwMask = CFM_LINK;
5461 link.dwEffects = CFE_LINK;
5462 ME_SetCharFormat(editor, &candidateStart, &candidateEnd, &link);
5463 modified = TRUE;
5466 startCur = candidateEnd;
5467 } while (nChars > 0);
5468 return modified;