comctl32/tests: Make test_combo_WS_VSCROLL() static.
[wine.git] / dlls / riched20 / editor.c
blobdaafe80055c8f0f42ee6a8a17b88ca0402013bf0
1 /*
2 * RichEdit - functions dealing with editor object
4 * Copyright 2004 by Krzysztof Foltman
5 * Copyright 2005 by Cihan Altinay
6 * Copyright 2005 by Phil Krylov
7 * Copyright 2008 Eric Pouech
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24 /*
25 API implementation status:
27 Messages (ANSI versions not done yet)
28 + EM_AUTOURLDETECT 2.0
29 + EM_CANPASTE
30 + EM_CANREDO 2.0
31 + EM_CANUNDO
32 + EM_CHARFROMPOS
33 - EM_DISPLAYBAND
34 + EM_EMPTYUNDOBUFFER
35 + EM_EXGETSEL
36 + EM_EXLIMITTEXT
37 + EM_EXLINEFROMCHAR
38 + EM_EXSETSEL
39 + EM_FINDTEXT (only FR_DOWN flag implemented)
40 + EM_FINDTEXTEX (only FR_DOWN flag implemented)
41 - EM_FINDWORDBREAK
42 - EM_FMTLINES
43 - EM_FORMATRANGE
44 + EM_GETAUTOURLDETECT 2.0
45 - EM_GETBIDIOPTIONS 3.0
46 - EM_GETCHARFORMAT (partly done)
47 - EM_GETEDITSTYLE
48 + EM_GETEVENTMASK
49 + EM_GETFIRSTVISIBLELINE (can be optimized if needed)
50 - EM_GETIMECOLOR 1.0asian
51 - EM_GETIMECOMPMODE 2.0
52 - EM_GETIMEOPTIONS 1.0asian
53 - EM_GETIMESTATUS
54 - EM_GETLANGOPTIONS 2.0
55 + EM_GETLIMITTEXT
56 + EM_GETLINE
57 + EM_GETLINECOUNT returns number of rows, not of paragraphs
58 + EM_GETMODIFY
59 + EM_GETOLEINTERFACE
60 + EM_GETOPTIONS
61 + EM_GETPARAFORMAT
62 + EM_GETPASSWORDCHAR 2.0
63 - EM_GETPUNCTUATION 1.0asian
64 + EM_GETRECT
65 - EM_GETREDONAME 2.0
66 + EM_GETSEL
67 + EM_GETSELTEXT (ANSI&Unicode)
68 + EM_GETSCROLLPOS 3.0
69 ! - EM_GETTHUMB
70 + EM_GETTEXTEX 2.0
71 + EM_GETTEXTLENGTHEX (GTL_PRECISE unimplemented)
72 + EM_GETTEXTMODE 2.0
73 ? + EM_GETTEXTRANGE (ANSI&Unicode)
74 - EM_GETTYPOGRAPHYOPTIONS 3.0
75 - EM_GETUNDONAME
76 + EM_GETWORDBREAKPROC
77 - EM_GETWORDBREAKPROCEX
78 - EM_GETWORDWRAPMODE 1.0asian
79 + EM_GETZOOM 3.0
80 + EM_HIDESELECTION
81 + EM_LIMITTEXT (Also called EM_SETLIMITTEXT)
82 + EM_LINEFROMCHAR
83 + EM_LINEINDEX
84 + EM_LINELENGTH
85 + EM_LINESCROLL
86 - EM_PASTESPECIAL
87 + EM_POSFROMCHAR
88 + EM_REDO 2.0
89 + EM_REQUESTRESIZE
90 + EM_REPLACESEL (proper style?) ANSI&Unicode
91 + EM_SCROLL
92 + EM_SCROLLCARET
93 - EM_SELECTIONTYPE
94 - EM_SETBIDIOPTIONS 3.0
95 + EM_SETBKGNDCOLOR
96 + EM_SETCHARFORMAT (partly done, no ANSI)
97 - EM_SETEDITSTYLE
98 + EM_SETEVENTMASK (few notifications supported)
99 + EM_SETFONTSIZE
100 - EM_SETIMECOLOR 1.0asian
101 - EM_SETIMEOPTIONS 1.0asian
102 - EM_SETIMESTATUS
103 - EM_SETLANGOPTIONS 2.0
104 - EM_SETLIMITTEXT
105 - EM_SETMARGINS
106 + EM_SETMODIFY (not sure if implementation is correct)
107 - EM_SETOLECALLBACK
108 + EM_SETOPTIONS (partially implemented)
109 - EM_SETPALETTE 2.0
110 + EM_SETPARAFORMAT
111 + EM_SETPASSWORDCHAR 2.0
112 - EM_SETPUNCTUATION 1.0asian
113 + EM_SETREADONLY no beep on modification attempt
114 + EM_SETRECT
115 + EM_SETRECTNP (EM_SETRECT without repainting)
116 + EM_SETSEL
117 + EM_SETSCROLLPOS 3.0
118 - EM_SETTABSTOPS 3.0
119 - EM_SETTARGETDEVICE (partial)
120 + EM_SETTEXTEX 3.0 (proper style?)
121 - EM_SETTEXTMODE 2.0
122 - EM_SETTYPOGRAPHYOPTIONS 3.0
123 + EM_SETUNDOLIMIT 2.0
124 + EM_SETWORDBREAKPROC (used only for word movement at the moment)
125 - EM_SETWORDBREAKPROCEX
126 - EM_SETWORDWRAPMODE 1.0asian
127 + EM_SETZOOM 3.0
128 + EM_SHOWSCROLLBAR 2.0
129 + EM_STOPGROUPTYPING 2.0
130 + EM_STREAMIN
131 + EM_STREAMOUT
132 + EM_UNDO
133 + WM_CHAR
134 + WM_CLEAR
135 + WM_COPY
136 + WM_CUT
137 + WM_GETDLGCODE (the current implementation is incomplete)
138 + WM_GETTEXT (ANSI&Unicode)
139 + WM_GETTEXTLENGTH (ANSI version sucks)
140 + WM_HSCROLL
141 + WM_PASTE
142 + WM_SETFONT
143 + WM_SETTEXT (resets undo stack !) (proper style?) ANSI&Unicode
144 + WM_STYLECHANGING (seems to do nothing)
145 + WM_STYLECHANGED (seems to do nothing)
146 + WM_UNICHAR
147 + WM_VSCROLL
149 Notifications
151 * EN_CHANGE (sent from the wrong place)
152 - EN_CORRECTTEXT
153 - EN_DROPFILES
154 - EN_ERRSPACE
155 - EN_HSCROLL
156 - EN_IMECHANGE
157 + EN_KILLFOCUS
158 - EN_LINK
159 - EN_MAXTEXT
160 - EN_MSGFILTER
161 - EN_OLEOPFAILED
162 - EN_PROTECTED
163 + EN_REQUESTRESIZE
164 - EN_SAVECLIPBOARD
165 + EN_SELCHANGE
166 + EN_SETFOCUS
167 - EN_STOPNOUNDO
168 * EN_UPDATE (sent from the wrong place)
169 - EN_VSCROLL
171 Styles
173 - ES_AUTOHSCROLL
174 - ES_AUTOVSCROLL
175 + ES_CENTER
176 + ES_DISABLENOSCROLL (scrollbar is always visible)
177 - ES_EX_NOCALLOLEINIT
178 + ES_LEFT
179 + ES_MULTILINE
180 - ES_NOIME
181 - ES_READONLY (I'm not sure if beeping is the proper behaviour)
182 + ES_RIGHT
183 - ES_SAVESEL
184 - ES_SELFIME
185 - ES_SUNKEN
186 - ES_VERTICAL
187 - ES_WANTRETURN (don't know how to do WM_GETDLGCODE part)
188 - WS_SETFONT
189 + WS_HSCROLL
190 + WS_VSCROLL
194 * RICHED20 TODO (incomplete):
196 * - messages/styles/notifications listed above
197 * - add remaining CHARFORMAT/PARAFORMAT fields
198 * - right/center align should strip spaces from the beginning
199 * - pictures/OLE objects (not just smiling faces that lack API support ;-) )
200 * - COM interface (looks like a major pain in the TODO list)
201 * - calculate heights of pictures (half-done)
202 * - hysteresis during wrapping (related to scrollbars appearing/disappearing)
203 * - find/replace
204 * - how to implement EM_FORMATRANGE and EM_DISPLAYBAND ? (Mission Impossible)
205 * - italic caret with italic fonts
206 * - IME
207 * - most notifications aren't sent at all (the most important ones are)
208 * - when should EN_SELCHANGE be sent after text change ? (before/after EN_UPDATE?)
209 * - WM_SETTEXT may use wrong style (but I'm 80% sure it's OK)
210 * - EM_GETCHARFORMAT with SCF_SELECTION may not behave 100% like in original (but very close)
211 * - full justification
212 * - hyphenation
213 * - tables
214 * - ListBox & ComboBox not implemented
216 * Bugs that are probably fixed, but not so easy to verify:
217 * - EN_UPDATE/EN_CHANGE are handled very incorrectly (should be OK now)
218 * - undo for ME_JoinParagraphs doesn't store paragraph format ? (it does)
219 * - check/fix artificial EOL logic (bCursorAtEnd, hardly logical)
220 * - caret shouldn't be displayed when selection isn't empty
221 * - check refcounting in style management functions (looks perfect now, but no bugs is suspicious)
222 * - undo for setting default format (done, might be buggy)
223 * - styles might be not released properly (looks like they work like charm, but who knows?
227 #define NONAMELESSUNION
229 #include "editor.h"
230 #include "commdlg.h"
231 #include "winreg.h"
232 #define NO_SHLWAPI_STREAM
233 #include "shlwapi.h"
234 #include "rtf.h"
235 #include "imm.h"
236 #include "res.h"
238 #define STACK_SIZE_DEFAULT 100
239 #define STACK_SIZE_MAX 1000
241 #define TEXT_LIMIT_DEFAULT 32767
243 WINE_DEFAULT_DEBUG_CHANNEL(richedit);
245 static BOOL ME_RegisterEditorClass(HINSTANCE);
246 static BOOL ME_UpdateLinkAttribute(ME_TextEditor *editor, ME_Cursor *start, int nChars);
248 static const WCHAR REListBox20W[] = {'R','E','L','i','s','t','B','o','x','2','0','W', 0};
249 static const WCHAR REComboBox20W[] = {'R','E','C','o','m','b','o','B','o','x','2','0','W', 0};
250 static HCURSOR hLeft;
252 BOOL me_debug = FALSE;
253 HANDLE me_heap = NULL;
255 static BOOL ME_ListBoxRegistered = FALSE;
256 static BOOL ME_ComboBoxRegistered = FALSE;
258 static inline BOOL is_version_nt(void)
260 return !(GetVersion() & 0x80000000);
263 static ME_TextBuffer *ME_MakeText(void) {
265 ME_TextBuffer *buf = ALLOC_OBJ(ME_TextBuffer);
267 ME_DisplayItem *p1 = ME_MakeDI(diTextStart);
268 ME_DisplayItem *p2 = ME_MakeDI(diTextEnd);
270 p1->prev = NULL;
271 p1->next = p2;
272 p2->prev = p1;
273 p2->next = NULL;
274 p1->member.para.next_para = p2;
275 p2->member.para.prev_para = p1;
276 p2->member.para.nCharOfs = 0;
278 buf->pFirst = p1;
279 buf->pLast = p2;
280 buf->pCharStyle = NULL;
282 return buf;
286 static LRESULT ME_StreamInText(ME_TextEditor *editor, DWORD dwFormat, ME_InStream *stream, ME_Style *style)
288 WCHAR *pText;
289 LRESULT total_bytes_read = 0;
290 BOOL is_read = FALSE;
291 DWORD cp = CP_ACP, copy = 0;
292 char conv_buf[4 + STREAMIN_BUFFER_SIZE]; /* up to 4 additional UTF-8 bytes */
294 static const char bom_utf8[] = {0xEF, 0xBB, 0xBF};
296 TRACE("%08x %p\n", dwFormat, stream);
298 do {
299 LONG nWideChars = 0;
300 WCHAR wszText[STREAMIN_BUFFER_SIZE+1];
302 if (!stream->dwSize)
304 ME_StreamInFill(stream);
305 if (stream->editstream->dwError)
306 break;
307 if (!stream->dwSize)
308 break;
309 total_bytes_read += stream->dwSize;
312 if (!(dwFormat & SF_UNICODE))
314 char * buf = stream->buffer;
315 DWORD size = stream->dwSize, end;
317 if (!is_read)
319 is_read = TRUE;
320 if (stream->dwSize >= 3 && !memcmp(stream->buffer, bom_utf8, 3))
322 cp = CP_UTF8;
323 buf += 3;
324 size -= 3;
328 if (cp == CP_UTF8)
330 if (copy)
332 memcpy(conv_buf + copy, buf, size);
333 buf = conv_buf;
334 size += copy;
336 end = size;
337 while ((buf[end-1] & 0xC0) == 0x80)
339 --end;
340 --total_bytes_read; /* strange, but seems to match windows */
342 if (buf[end-1] & 0x80)
344 DWORD need = 0;
345 if ((buf[end-1] & 0xE0) == 0xC0)
346 need = 1;
347 if ((buf[end-1] & 0xF0) == 0xE0)
348 need = 2;
349 if ((buf[end-1] & 0xF8) == 0xF0)
350 need = 3;
352 if (size - end >= need)
354 /* we have enough bytes for this sequence */
355 end = size;
357 else
359 /* need more bytes, so don't transcode this sequence */
360 --end;
364 else
365 end = size;
367 nWideChars = MultiByteToWideChar(cp, 0, buf, end, wszText, STREAMIN_BUFFER_SIZE);
368 pText = wszText;
370 if (cp == CP_UTF8)
372 if (end != size)
374 memcpy(conv_buf, buf + end, size - end);
375 copy = size - end;
379 else
381 nWideChars = stream->dwSize >> 1;
382 pText = (WCHAR *)stream->buffer;
385 ME_InsertTextFromCursor(editor, 0, pText, nWideChars, style);
386 if (stream->dwSize == 0)
387 break;
388 stream->dwSize = 0;
389 } while(1);
390 return total_bytes_read;
393 static void ME_ApplyBorderProperties(RTF_Info *info,
394 ME_BorderRect *borderRect,
395 RTFBorder *borderDef)
397 int i, colorNum;
398 ME_Border *pBorders[] = {&borderRect->top,
399 &borderRect->left,
400 &borderRect->bottom,
401 &borderRect->right};
402 for (i = 0; i < 4; i++)
404 RTFColor *colorDef = info->colorList;
405 pBorders[i]->width = borderDef[i].width;
406 colorNum = borderDef[i].color;
407 while (colorDef && colorDef->rtfCNum != colorNum)
408 colorDef = colorDef->rtfNextColor;
409 if (colorDef)
410 pBorders[i]->colorRef = RGB(
411 colorDef->rtfCRed >= 0 ? colorDef->rtfCRed : 0,
412 colorDef->rtfCGreen >= 0 ? colorDef->rtfCGreen : 0,
413 colorDef->rtfCBlue >= 0 ? colorDef->rtfCBlue : 0);
414 else
415 pBorders[i]->colorRef = RGB(0, 0, 0);
419 void ME_RTFCharAttrHook(RTF_Info *info)
421 CHARFORMAT2W fmt;
422 fmt.cbSize = sizeof(fmt);
423 fmt.dwMask = 0;
424 fmt.dwEffects = 0;
426 switch(info->rtfMinor)
428 case rtfPlain:
429 /* FIXME add more flags once they're implemented */
430 fmt.dwMask = CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE | CFM_UNDERLINETYPE | CFM_STRIKEOUT |
431 CFM_COLOR | CFM_BACKCOLOR | CFM_SIZE | CFM_WEIGHT;
432 fmt.dwEffects = CFE_AUTOCOLOR | CFE_AUTOBACKCOLOR;
433 fmt.yHeight = 12*20; /* 12pt */
434 fmt.wWeight = FW_NORMAL;
435 fmt.bUnderlineType = CFU_UNDERLINE;
436 break;
437 case rtfBold:
438 fmt.dwMask = CFM_BOLD | CFM_WEIGHT;
439 fmt.dwEffects = info->rtfParam ? CFE_BOLD : 0;
440 fmt.wWeight = info->rtfParam ? FW_BOLD : FW_NORMAL;
441 break;
442 case rtfItalic:
443 fmt.dwMask = CFM_ITALIC;
444 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
445 break;
446 case rtfUnderline:
447 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
448 fmt.bUnderlineType = CFU_UNDERLINE;
449 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
450 break;
451 case rtfDotUnderline:
452 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
453 fmt.bUnderlineType = CFU_UNDERLINEDOTTED;
454 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
455 break;
456 case rtfDbUnderline:
457 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
458 fmt.bUnderlineType = CFU_UNDERLINEDOUBLE;
459 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
460 break;
461 case rtfWordUnderline:
462 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
463 fmt.bUnderlineType = CFU_UNDERLINEWORD;
464 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
465 break;
466 case rtfNoUnderline:
467 fmt.dwMask = CFM_UNDERLINE;
468 fmt.dwEffects = 0;
469 break;
470 case rtfStrikeThru:
471 fmt.dwMask = CFM_STRIKEOUT;
472 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
473 break;
474 case rtfSubScript:
475 case rtfSuperScript:
476 case rtfSubScrShrink:
477 case rtfSuperScrShrink:
478 case rtfNoSuperSub:
479 fmt.dwMask = CFM_SUBSCRIPT|CFM_SUPERSCRIPT;
480 if (info->rtfMinor == rtfSubScrShrink) fmt.dwEffects = CFE_SUBSCRIPT;
481 if (info->rtfMinor == rtfSuperScrShrink) fmt.dwEffects = CFE_SUPERSCRIPT;
482 if (info->rtfMinor == rtfNoSuperSub) fmt.dwEffects = 0;
483 break;
484 case rtfInvisible:
485 fmt.dwMask = CFM_HIDDEN;
486 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
487 break;
488 case rtfBackColor:
489 fmt.dwMask = CFM_BACKCOLOR;
490 fmt.dwEffects = 0;
491 if (info->rtfParam == 0)
492 fmt.dwEffects = CFE_AUTOBACKCOLOR;
493 else if (info->rtfParam != rtfNoParam)
495 RTFColor *c = RTFGetColor(info, info->rtfParam);
496 if (c && c->rtfCBlue >= 0)
497 fmt.crBackColor = (c->rtfCBlue<<16)|(c->rtfCGreen<<8)|(c->rtfCRed);
498 else
499 fmt.dwEffects = CFE_AUTOBACKCOLOR;
501 break;
502 case rtfForeColor:
503 fmt.dwMask = CFM_COLOR;
504 fmt.dwEffects = 0;
505 if (info->rtfParam == 0)
506 fmt.dwEffects = CFE_AUTOCOLOR;
507 else if (info->rtfParam != rtfNoParam)
509 RTFColor *c = RTFGetColor(info, info->rtfParam);
510 if (c && c->rtfCBlue >= 0)
511 fmt.crTextColor = (c->rtfCBlue<<16)|(c->rtfCGreen<<8)|(c->rtfCRed);
512 else {
513 fmt.dwEffects = CFE_AUTOCOLOR;
516 break;
517 case rtfFontNum:
518 if (info->rtfParam != rtfNoParam)
520 RTFFont *f = RTFGetFont(info, info->rtfParam);
521 if (f)
523 MultiByteToWideChar(CP_ACP, 0, f->rtfFName, -1, fmt.szFaceName, sizeof(fmt.szFaceName)/sizeof(WCHAR));
524 fmt.szFaceName[sizeof(fmt.szFaceName)/sizeof(WCHAR)-1] = '\0';
525 fmt.bCharSet = f->rtfFCharSet;
526 fmt.dwMask = CFM_FACE | CFM_CHARSET;
527 fmt.bPitchAndFamily = f->rtfFPitch | (f->rtfFFamily << 4);
530 break;
531 case rtfFontSize:
532 fmt.dwMask = CFM_SIZE;
533 if (info->rtfParam != rtfNoParam)
534 fmt.yHeight = info->rtfParam*10;
535 break;
537 if (fmt.dwMask) {
538 ME_Style *style2;
539 RTFFlushOutputBuffer(info);
540 /* FIXME too slow ? how come ? */
541 style2 = ME_ApplyStyle(info->editor, info->style, &fmt);
542 ME_ReleaseStyle(info->style);
543 info->style = style2;
544 info->styleChanged = TRUE;
548 /* FIXME this function doesn't get any information about context of the RTF tag, which is very bad,
549 the same tags mean different things in different contexts */
550 void ME_RTFParAttrHook(RTF_Info *info)
552 switch(info->rtfMinor)
554 case rtfParDef: /* restores default paragraph attributes */
555 if (!info->editor->bEmulateVersion10) /* v4.1 */
556 info->borderType = RTFBorderParaLeft;
557 else /* v1.0 - 3.0 */
558 info->borderType = RTFBorderParaTop;
559 info->fmt.dwMask = PFM_ALIGNMENT | PFM_BORDER | PFM_LINESPACING | PFM_TABSTOPS |
560 PFM_OFFSET | PFM_RIGHTINDENT | PFM_SPACEAFTER | PFM_SPACEBEFORE |
561 PFM_STARTINDENT | PFM_RTLPARA | PFM_NUMBERING | PFM_NUMBERINGSTART |
562 PFM_NUMBERINGSTYLE | PFM_NUMBERINGTAB;
563 /* TODO: shading */
564 info->fmt.wAlignment = PFA_LEFT;
565 info->fmt.cTabCount = 0;
566 info->fmt.dxOffset = info->fmt.dxStartIndent = info->fmt.dxRightIndent = 0;
567 info->fmt.wBorderWidth = info->fmt.wBorders = 0;
568 info->fmt.wBorderSpace = 0;
569 info->fmt.bLineSpacingRule = 0;
570 info->fmt.dySpaceBefore = info->fmt.dySpaceAfter = 0;
571 info->fmt.dyLineSpacing = 0;
572 info->fmt.wEffects &= ~PFE_RTLPARA;
573 info->fmt.wNumbering = 0;
574 info->fmt.wNumberingStart = 0;
575 info->fmt.wNumberingStyle = 0;
576 info->fmt.wNumberingTab = 0;
578 if (!info->editor->bEmulateVersion10) /* v4.1 */
580 if (info->tableDef && info->tableDef->tableRowStart &&
581 info->tableDef->tableRowStart->member.para.nFlags & MEPF_ROWEND)
583 ME_Cursor cursor;
584 ME_DisplayItem *para;
585 /* We are just after a table row. */
586 RTFFlushOutputBuffer(info);
587 cursor = info->editor->pCursors[0];
588 para = cursor.pPara;
589 if (para == info->tableDef->tableRowStart->member.para.next_para
590 && !cursor.nOffset && !cursor.pRun->member.run.nCharOfs)
592 /* Since the table row end, no text has been inserted, and the \intbl
593 * control word has not be used. We can confirm that we are not in a
594 * table anymore.
596 info->tableDef->tableRowStart = NULL;
597 info->canInheritInTbl = FALSE;
600 } else { /* v1.0 - v3.0 */
601 info->fmt.dwMask |= PFM_TABLE;
602 info->fmt.wEffects &= ~PFE_TABLE;
604 break;
605 case rtfNestLevel:
606 if (!info->editor->bEmulateVersion10) /* v4.1 */
608 while (info->rtfParam > info->nestingLevel) {
609 RTFTable *tableDef = ALLOC_OBJ(RTFTable);
610 ZeroMemory(tableDef, sizeof(RTFTable));
611 tableDef->parent = info->tableDef;
612 info->tableDef = tableDef;
614 RTFFlushOutputBuffer(info);
615 if (tableDef->tableRowStart &&
616 tableDef->tableRowStart->member.para.nFlags & MEPF_ROWEND)
618 ME_DisplayItem *para = tableDef->tableRowStart;
619 para = para->member.para.next_para;
620 para = ME_InsertTableRowStartAtParagraph(info->editor, para);
621 tableDef->tableRowStart = para;
622 } else {
623 ME_Cursor cursor;
624 WCHAR endl = '\r';
625 cursor = info->editor->pCursors[0];
626 if (cursor.nOffset || cursor.pRun->member.run.nCharOfs)
627 ME_InsertTextFromCursor(info->editor, 0, &endl, 1, info->style);
628 tableDef->tableRowStart = ME_InsertTableRowStartFromCursor(info->editor);
631 info->nestingLevel++;
633 info->canInheritInTbl = FALSE;
635 break;
636 case rtfInTable:
638 if (!info->editor->bEmulateVersion10) /* v4.1 */
640 if (info->nestingLevel < 1)
642 RTFTable *tableDef;
643 if (!info->tableDef)
645 info->tableDef = ALLOC_OBJ(RTFTable);
646 ZeroMemory(info->tableDef, sizeof(RTFTable));
648 tableDef = info->tableDef;
649 RTFFlushOutputBuffer(info);
650 if (tableDef->tableRowStart &&
651 tableDef->tableRowStart->member.para.nFlags & MEPF_ROWEND)
653 ME_DisplayItem *para = tableDef->tableRowStart;
654 para = para->member.para.next_para;
655 para = ME_InsertTableRowStartAtParagraph(info->editor, para);
656 tableDef->tableRowStart = para;
657 } else {
658 ME_Cursor cursor;
659 WCHAR endl = '\r';
660 cursor = info->editor->pCursors[0];
661 if (cursor.nOffset || cursor.pRun->member.run.nCharOfs)
662 ME_InsertTextFromCursor(info->editor, 0, &endl, 1, info->style);
663 tableDef->tableRowStart = ME_InsertTableRowStartFromCursor(info->editor);
665 info->nestingLevel = 1;
666 info->canInheritInTbl = TRUE;
668 return;
669 } else { /* v1.0 - v3.0 */
670 info->fmt.dwMask |= PFM_TABLE;
671 info->fmt.wEffects |= PFE_TABLE;
673 break;
675 case rtfFirstIndent:
676 case rtfLeftIndent:
677 if ((info->fmt.dwMask & (PFM_STARTINDENT | PFM_OFFSET)) != (PFM_STARTINDENT | PFM_OFFSET))
679 PARAFORMAT2 fmt;
680 fmt.cbSize = sizeof(fmt);
681 ME_GetSelectionParaFormat(info->editor, &fmt);
682 info->fmt.dwMask |= PFM_STARTINDENT | PFM_OFFSET;
683 info->fmt.dxStartIndent = fmt.dxStartIndent;
684 info->fmt.dxOffset = fmt.dxOffset;
686 if (info->rtfMinor == rtfFirstIndent)
688 info->fmt.dxStartIndent += info->fmt.dxOffset + info->rtfParam;
689 info->fmt.dxOffset = -info->rtfParam;
691 else
692 info->fmt.dxStartIndent = info->rtfParam - info->fmt.dxOffset;
693 break;
694 case rtfRightIndent:
695 info->fmt.dwMask |= PFM_RIGHTINDENT;
696 info->fmt.dxRightIndent = info->rtfParam;
697 break;
698 case rtfQuadLeft:
699 case rtfQuadJust:
700 info->fmt.dwMask |= PFM_ALIGNMENT;
701 info->fmt.wAlignment = PFA_LEFT;
702 break;
703 case rtfQuadRight:
704 info->fmt.dwMask |= PFM_ALIGNMENT;
705 info->fmt.wAlignment = PFA_RIGHT;
706 break;
707 case rtfQuadCenter:
708 info->fmt.dwMask |= PFM_ALIGNMENT;
709 info->fmt.wAlignment = PFA_CENTER;
710 break;
711 case rtfTabPos:
712 if (!(info->fmt.dwMask & PFM_TABSTOPS))
714 PARAFORMAT2 fmt;
715 fmt.cbSize = sizeof(fmt);
716 ME_GetSelectionParaFormat(info->editor, &fmt);
717 memcpy(info->fmt.rgxTabs, fmt.rgxTabs,
718 fmt.cTabCount * sizeof(fmt.rgxTabs[0]));
719 info->fmt.cTabCount = fmt.cTabCount;
720 info->fmt.dwMask |= PFM_TABSTOPS;
722 if (info->fmt.cTabCount < MAX_TAB_STOPS && info->rtfParam < 0x1000000)
723 info->fmt.rgxTabs[info->fmt.cTabCount++] = info->rtfParam;
724 break;
725 case rtfKeep:
726 info->fmt.dwMask |= PFM_KEEP;
727 info->fmt.wEffects |= PFE_KEEP;
728 break;
729 case rtfNoWidowControl:
730 info->fmt.dwMask |= PFM_NOWIDOWCONTROL;
731 info->fmt.wEffects |= PFE_NOWIDOWCONTROL;
732 break;
733 case rtfKeepNext:
734 info->fmt.dwMask |= PFM_KEEPNEXT;
735 info->fmt.wEffects |= PFE_KEEPNEXT;
736 break;
737 case rtfSpaceAfter:
738 info->fmt.dwMask |= PFM_SPACEAFTER;
739 info->fmt.dySpaceAfter = info->rtfParam;
740 break;
741 case rtfSpaceBefore:
742 info->fmt.dwMask |= PFM_SPACEBEFORE;
743 info->fmt.dySpaceBefore = info->rtfParam;
744 break;
745 case rtfSpaceBetween:
746 info->fmt.dwMask |= PFM_LINESPACING;
747 if ((int)info->rtfParam > 0)
749 info->fmt.dyLineSpacing = info->rtfParam;
750 info->fmt.bLineSpacingRule = 3;
752 else
754 info->fmt.dyLineSpacing = info->rtfParam;
755 info->fmt.bLineSpacingRule = 4;
757 break;
758 case rtfSpaceMultiply:
759 info->fmt.dwMask |= PFM_LINESPACING;
760 info->fmt.dyLineSpacing = info->rtfParam * 20;
761 info->fmt.bLineSpacingRule = 5;
762 break;
763 case rtfParBullet:
764 info->fmt.dwMask |= PFM_NUMBERING;
765 info->fmt.wNumbering = PFN_BULLET;
766 break;
767 case rtfParSimple:
768 info->fmt.dwMask |= PFM_NUMBERING;
769 info->fmt.wNumbering = 2; /* FIXME: MSDN says it's not used ?? */
770 break;
771 case rtfBorderLeft:
772 info->borderType = RTFBorderParaLeft;
773 info->fmt.wBorders |= 1;
774 info->fmt.dwMask |= PFM_BORDER;
775 break;
776 case rtfBorderRight:
777 info->borderType = RTFBorderParaRight;
778 info->fmt.wBorders |= 2;
779 info->fmt.dwMask |= PFM_BORDER;
780 break;
781 case rtfBorderTop:
782 info->borderType = RTFBorderParaTop;
783 info->fmt.wBorders |= 4;
784 info->fmt.dwMask |= PFM_BORDER;
785 break;
786 case rtfBorderBottom:
787 info->borderType = RTFBorderParaBottom;
788 info->fmt.wBorders |= 8;
789 info->fmt.dwMask |= PFM_BORDER;
790 break;
791 case rtfBorderSingle:
792 info->fmt.wBorders &= ~0x700;
793 info->fmt.wBorders |= 1 << 8;
794 info->fmt.dwMask |= PFM_BORDER;
795 break;
796 case rtfBorderThick:
797 info->fmt.wBorders &= ~0x700;
798 info->fmt.wBorders |= 2 << 8;
799 info->fmt.dwMask |= PFM_BORDER;
800 break;
801 case rtfBorderShadow:
802 info->fmt.wBorders &= ~0x700;
803 info->fmt.wBorders |= 10 << 8;
804 info->fmt.dwMask |= PFM_BORDER;
805 break;
806 case rtfBorderDouble:
807 info->fmt.wBorders &= ~0x700;
808 info->fmt.wBorders |= 7 << 8;
809 info->fmt.dwMask |= PFM_BORDER;
810 break;
811 case rtfBorderDot:
812 info->fmt.wBorders &= ~0x700;
813 info->fmt.wBorders |= 11 << 8;
814 info->fmt.dwMask |= PFM_BORDER;
815 break;
816 case rtfBorderWidth:
818 int borderSide = info->borderType & RTFBorderSideMask;
819 RTFTable *tableDef = info->tableDef;
820 if ((info->borderType & RTFBorderTypeMask) == RTFBorderTypeCell)
822 RTFBorder *border;
823 if (!tableDef || tableDef->numCellsDefined >= MAX_TABLE_CELLS)
824 break;
825 border = &tableDef->cells[tableDef->numCellsDefined].border[borderSide];
826 border->width = info->rtfParam;
827 break;
829 info->fmt.wBorderWidth = info->rtfParam;
830 info->fmt.dwMask |= PFM_BORDER;
831 break;
833 case rtfBorderSpace:
834 info->fmt.wBorderSpace = info->rtfParam;
835 info->fmt.dwMask |= PFM_BORDER;
836 break;
837 case rtfBorderColor:
839 RTFTable *tableDef = info->tableDef;
840 int borderSide = info->borderType & RTFBorderSideMask;
841 int borderType = info->borderType & RTFBorderTypeMask;
842 switch(borderType)
844 case RTFBorderTypePara:
845 if (!info->editor->bEmulateVersion10) /* v4.1 */
846 break;
847 /* v1.0 - 3.0 treat paragraph and row borders the same. */
848 case RTFBorderTypeRow:
849 if (tableDef) {
850 tableDef->border[borderSide].color = info->rtfParam;
852 break;
853 case RTFBorderTypeCell:
854 if (tableDef && tableDef->numCellsDefined < MAX_TABLE_CELLS) {
855 tableDef->cells[tableDef->numCellsDefined].border[borderSide].color = info->rtfParam;
857 break;
859 break;
861 case rtfRTLPar:
862 info->fmt.dwMask |= PFM_RTLPARA;
863 info->fmt.wEffects |= PFE_RTLPARA;
864 break;
865 case rtfLTRPar:
866 info->fmt.dwMask |= PFM_RTLPARA;
867 info->fmt.wEffects &= ~PFE_RTLPARA;
868 break;
872 void ME_RTFTblAttrHook(RTF_Info *info)
874 switch (info->rtfMinor)
876 case rtfRowDef:
878 if (!info->editor->bEmulateVersion10) /* v4.1 */
879 info->borderType = 0; /* Not sure */
880 else /* v1.0 - 3.0 */
881 info->borderType = RTFBorderRowTop;
882 if (!info->tableDef) {
883 info->tableDef = ME_MakeTableDef(info->editor);
884 } else {
885 ME_InitTableDef(info->editor, info->tableDef);
887 break;
889 case rtfCellPos:
891 int cellNum;
892 if (!info->tableDef)
894 info->tableDef = ME_MakeTableDef(info->editor);
896 cellNum = info->tableDef->numCellsDefined;
897 if (cellNum >= MAX_TABLE_CELLS)
898 break;
899 info->tableDef->cells[cellNum].rightBoundary = info->rtfParam;
900 if (cellNum < MAX_TAB_STOPS) {
901 /* Tab stops were used to store cell positions before v4.1 but v4.1
902 * still seems to set the tabstops without using them. */
903 ME_DisplayItem *para = info->editor->pCursors[0].pPara;
904 PARAFORMAT2 *pFmt = &para->member.para.fmt;
905 pFmt->rgxTabs[cellNum] &= ~0x00FFFFFF;
906 pFmt->rgxTabs[cellNum] |= 0x00FFFFFF & info->rtfParam;
908 info->tableDef->numCellsDefined++;
909 break;
911 case rtfRowBordTop:
912 info->borderType = RTFBorderRowTop;
913 break;
914 case rtfRowBordLeft:
915 info->borderType = RTFBorderRowLeft;
916 break;
917 case rtfRowBordBottom:
918 info->borderType = RTFBorderRowBottom;
919 break;
920 case rtfRowBordRight:
921 info->borderType = RTFBorderRowRight;
922 break;
923 case rtfCellBordTop:
924 info->borderType = RTFBorderCellTop;
925 break;
926 case rtfCellBordLeft:
927 info->borderType = RTFBorderCellLeft;
928 break;
929 case rtfCellBordBottom:
930 info->borderType = RTFBorderCellBottom;
931 break;
932 case rtfCellBordRight:
933 info->borderType = RTFBorderCellRight;
934 break;
935 case rtfRowGapH:
936 if (info->tableDef)
937 info->tableDef->gapH = info->rtfParam;
938 break;
939 case rtfRowLeftEdge:
940 if (info->tableDef)
941 info->tableDef->leftEdge = info->rtfParam;
942 break;
946 void ME_RTFSpecialCharHook(RTF_Info *info)
948 RTFTable *tableDef = info->tableDef;
949 switch (info->rtfMinor)
951 case rtfNestCell:
952 if (info->editor->bEmulateVersion10) /* v1.0 - v3.0 */
953 break;
954 /* else fall through since v4.1 treats rtfNestCell and rtfCell the same */
955 case rtfCell:
956 if (!tableDef)
957 break;
958 RTFFlushOutputBuffer(info);
959 if (!info->editor->bEmulateVersion10) { /* v4.1 */
960 if (tableDef->tableRowStart)
962 if (!info->nestingLevel &&
963 tableDef->tableRowStart->member.para.nFlags & MEPF_ROWEND)
965 ME_DisplayItem *para = tableDef->tableRowStart;
966 para = para->member.para.next_para;
967 para = ME_InsertTableRowStartAtParagraph(info->editor, para);
968 tableDef->tableRowStart = para;
969 info->nestingLevel = 1;
971 ME_InsertTableCellFromCursor(info->editor);
973 } else { /* v1.0 - v3.0 */
974 ME_DisplayItem *para = info->editor->pCursors[0].pPara;
975 PARAFORMAT2 *pFmt = &para->member.para.fmt;
976 if (pFmt->dwMask & PFM_TABLE && pFmt->wEffects & PFE_TABLE &&
977 tableDef->numCellsInserted < tableDef->numCellsDefined)
979 WCHAR tab = '\t';
980 ME_InsertTextFromCursor(info->editor, 0, &tab, 1, info->style);
981 tableDef->numCellsInserted++;
984 break;
985 case rtfNestRow:
986 if (info->editor->bEmulateVersion10) /* v1.0 - v3.0 */
987 break;
988 /* else fall through since v4.1 treats rtfNestRow and rtfRow the same */
989 case rtfRow:
991 ME_DisplayItem *para, *cell, *run;
992 int i;
994 if (!tableDef)
995 break;
996 RTFFlushOutputBuffer(info);
997 if (!info->editor->bEmulateVersion10) { /* v4.1 */
998 if (!tableDef->tableRowStart)
999 break;
1000 if (!info->nestingLevel &&
1001 tableDef->tableRowStart->member.para.nFlags & MEPF_ROWEND)
1003 para = tableDef->tableRowStart;
1004 para = para->member.para.next_para;
1005 para = ME_InsertTableRowStartAtParagraph(info->editor, para);
1006 tableDef->tableRowStart = para;
1007 info->nestingLevel++;
1009 para = tableDef->tableRowStart;
1010 cell = ME_FindItemFwd(para, diCell);
1011 assert(cell && !cell->member.cell.prev_cell);
1012 if (tableDef->numCellsDefined < 1)
1014 /* 2000 twips appears to be the cell size that native richedit uses
1015 * when no cell sizes are specified. */
1016 const int defaultCellSize = 2000;
1017 int nRightBoundary = defaultCellSize;
1018 cell->member.cell.nRightBoundary = nRightBoundary;
1019 while (cell->member.cell.next_cell) {
1020 cell = cell->member.cell.next_cell;
1021 nRightBoundary += defaultCellSize;
1022 cell->member.cell.nRightBoundary = nRightBoundary;
1024 para = ME_InsertTableCellFromCursor(info->editor);
1025 cell = para->member.para.pCell;
1026 cell->member.cell.nRightBoundary = nRightBoundary;
1027 } else {
1028 for (i = 0; i < tableDef->numCellsDefined; i++)
1030 RTFCell *cellDef = &tableDef->cells[i];
1031 cell->member.cell.nRightBoundary = cellDef->rightBoundary;
1032 ME_ApplyBorderProperties(info, &cell->member.cell.border,
1033 cellDef->border);
1034 cell = cell->member.cell.next_cell;
1035 if (!cell)
1037 para = ME_InsertTableCellFromCursor(info->editor);
1038 cell = para->member.para.pCell;
1041 /* Cell for table row delimiter is empty */
1042 cell->member.cell.nRightBoundary = tableDef->cells[i-1].rightBoundary;
1045 run = ME_FindItemFwd(cell, diRun);
1046 if (info->editor->pCursors[0].pRun != run ||
1047 info->editor->pCursors[0].nOffset)
1049 int nOfs, nChars;
1050 /* Delete inserted cells that aren't defined. */
1051 info->editor->pCursors[1].pRun = run;
1052 info->editor->pCursors[1].pPara = ME_GetParagraph(run);
1053 info->editor->pCursors[1].nOffset = 0;
1054 nOfs = ME_GetCursorOfs(&info->editor->pCursors[1]);
1055 nChars = ME_GetCursorOfs(&info->editor->pCursors[0]) - nOfs;
1056 ME_InternalDeleteText(info->editor, &info->editor->pCursors[1],
1057 nChars, TRUE);
1060 para = ME_InsertTableRowEndFromCursor(info->editor);
1061 para->member.para.fmt.dxOffset = abs(info->tableDef->gapH);
1062 para->member.para.fmt.dxStartIndent = info->tableDef->leftEdge;
1063 ME_ApplyBorderProperties(info, &para->member.para.border,
1064 tableDef->border);
1065 info->nestingLevel--;
1066 if (!info->nestingLevel)
1068 if (info->canInheritInTbl) {
1069 tableDef->tableRowStart = para;
1070 } else {
1071 while (info->tableDef) {
1072 tableDef = info->tableDef;
1073 info->tableDef = tableDef->parent;
1074 heap_free(tableDef);
1077 } else {
1078 info->tableDef = tableDef->parent;
1079 heap_free(tableDef);
1081 } else { /* v1.0 - v3.0 */
1082 WCHAR endl = '\r';
1083 ME_DisplayItem *para = info->editor->pCursors[0].pPara;
1084 PARAFORMAT2 *pFmt = &para->member.para.fmt;
1085 pFmt->dxOffset = info->tableDef->gapH;
1086 pFmt->dxStartIndent = info->tableDef->leftEdge;
1088 ME_ApplyBorderProperties(info, &para->member.para.border,
1089 tableDef->border);
1090 while (tableDef->numCellsInserted < tableDef->numCellsDefined)
1092 WCHAR tab = '\t';
1093 ME_InsertTextFromCursor(info->editor, 0, &tab, 1, info->style);
1094 tableDef->numCellsInserted++;
1096 pFmt->cTabCount = min(tableDef->numCellsDefined, MAX_TAB_STOPS);
1097 if (!tableDef->numCellsDefined)
1098 pFmt->wEffects &= ~PFE_TABLE;
1099 ME_InsertTextFromCursor(info->editor, 0, &endl, 1, info->style);
1100 tableDef->numCellsInserted = 0;
1102 break;
1104 case rtfTab:
1105 case rtfPar:
1106 if (info->editor->bEmulateVersion10) { /* v1.0 - 3.0 */
1107 ME_DisplayItem *para;
1108 PARAFORMAT2 *pFmt;
1109 RTFFlushOutputBuffer(info);
1110 para = info->editor->pCursors[0].pPara;
1111 pFmt = &para->member.para.fmt;
1112 if (pFmt->dwMask & PFM_TABLE && pFmt->wEffects & PFE_TABLE)
1114 /* rtfPar is treated like a space within a table. */
1115 info->rtfClass = rtfText;
1116 info->rtfMajor = ' ';
1118 else if (info->rtfMinor == rtfPar && tableDef)
1119 tableDef->numCellsInserted = 0;
1121 break;
1125 static HRESULT insert_static_object(ME_TextEditor *editor, HENHMETAFILE hemf, HBITMAP hbmp,
1126 const SIZEL* sz)
1128 LPOLEOBJECT lpObject = NULL;
1129 LPSTORAGE lpStorage = NULL;
1130 LPOLECLIENTSITE lpClientSite = NULL;
1131 LPDATAOBJECT lpDataObject = NULL;
1132 LPOLECACHE lpOleCache = NULL;
1133 STGMEDIUM stgm;
1134 FORMATETC fm;
1135 CLSID clsid;
1136 HRESULT hr = E_FAIL;
1137 DWORD conn;
1139 if (hemf)
1141 stgm.tymed = TYMED_ENHMF;
1142 stgm.u.hEnhMetaFile = hemf;
1143 fm.cfFormat = CF_ENHMETAFILE;
1145 else if (hbmp)
1147 stgm.tymed = TYMED_GDI;
1148 stgm.u.hBitmap = hbmp;
1149 fm.cfFormat = CF_BITMAP;
1151 stgm.pUnkForRelease = NULL;
1153 fm.ptd = NULL;
1154 fm.dwAspect = DVASPECT_CONTENT;
1155 fm.lindex = -1;
1156 fm.tymed = stgm.tymed;
1158 if (!editor->reOle)
1160 if (!CreateIRichEditOle(NULL, editor, (LPVOID *)&editor->reOle))
1161 return hr;
1164 if (OleCreateDefaultHandler(&CLSID_NULL, NULL, &IID_IOleObject, (void**)&lpObject) == S_OK &&
1165 IRichEditOle_GetClientSite(editor->reOle, &lpClientSite) == S_OK &&
1166 IOleObject_SetClientSite(lpObject, lpClientSite) == S_OK &&
1167 IOleObject_GetUserClassID(lpObject, &clsid) == S_OK &&
1168 IOleObject_QueryInterface(lpObject, &IID_IOleCache, (void**)&lpOleCache) == S_OK &&
1169 IOleCache_Cache(lpOleCache, &fm, 0, &conn) == S_OK &&
1170 IOleObject_QueryInterface(lpObject, &IID_IDataObject, (void**)&lpDataObject) == S_OK &&
1171 IDataObject_SetData(lpDataObject, &fm, &stgm, TRUE) == S_OK)
1173 REOBJECT reobject;
1175 reobject.cbStruct = sizeof(reobject);
1176 reobject.cp = REO_CP_SELECTION;
1177 reobject.clsid = clsid;
1178 reobject.poleobj = lpObject;
1179 reobject.pstg = lpStorage;
1180 reobject.polesite = lpClientSite;
1181 /* convert from twips to .01 mm */
1182 reobject.sizel.cx = MulDiv(sz->cx, 254, 144);
1183 reobject.sizel.cy = MulDiv(sz->cy, 254, 144);
1184 reobject.dvaspect = DVASPECT_CONTENT;
1185 reobject.dwFlags = 0; /* FIXME */
1186 reobject.dwUser = 0;
1188 ME_InsertOLEFromCursor(editor, &reobject, 0);
1189 hr = S_OK;
1192 if (lpObject) IOleObject_Release(lpObject);
1193 if (lpClientSite) IOleClientSite_Release(lpClientSite);
1194 if (lpStorage) IStorage_Release(lpStorage);
1195 if (lpDataObject) IDataObject_Release(lpDataObject);
1196 if (lpOleCache) IOleCache_Release(lpOleCache);
1198 return hr;
1201 static void ME_RTFReadShpPictGroup( RTF_Info *info )
1203 int level = 1;
1205 for (;;)
1207 RTFGetToken (info);
1209 if (info->rtfClass == rtfEOF) return;
1210 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1212 if (--level == 0) break;
1214 else if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1216 level++;
1218 else
1220 RTFRouteToken( info );
1221 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1222 level--;
1226 RTFRouteToken( info ); /* feed "}" back to router */
1227 return;
1230 static DWORD read_hex_data( RTF_Info *info, BYTE **out )
1232 DWORD read = 0, size = 1024;
1233 BYTE *buf, val;
1234 BOOL flip;
1236 *out = NULL;
1238 if (info->rtfClass != rtfText)
1240 ERR("Called with incorrect token\n");
1241 return 0;
1244 buf = HeapAlloc( GetProcessHeap(), 0, size );
1245 if (!buf) return 0;
1247 val = info->rtfMajor;
1248 for (flip = TRUE;; flip = !flip)
1250 RTFGetToken( info );
1251 if (info->rtfClass == rtfEOF)
1253 HeapFree( GetProcessHeap(), 0, buf );
1254 return 0;
1256 if (info->rtfClass != rtfText) break;
1257 if (flip)
1259 if (read >= size)
1261 size *= 2;
1262 buf = HeapReAlloc( GetProcessHeap(), 0, buf, size );
1263 if (!buf) return 0;
1265 buf[read++] = RTFCharToHex(val) * 16 + RTFCharToHex(info->rtfMajor);
1267 else
1268 val = info->rtfMajor;
1270 if (flip) FIXME("wrong hex string\n");
1272 *out = buf;
1273 return read;
1276 static void ME_RTFReadPictGroup(RTF_Info *info)
1278 SIZEL sz;
1279 BYTE *buffer = NULL;
1280 DWORD size = 0;
1281 METAFILEPICT mfp;
1282 HENHMETAFILE hemf;
1283 HBITMAP hbmp;
1284 enum gfxkind {gfx_unknown = 0, gfx_enhmetafile, gfx_metafile, gfx_dib} gfx = gfx_unknown;
1285 int level = 1;
1287 mfp.mm = MM_TEXT;
1288 sz.cx = sz.cy = 0;
1290 for (;;)
1292 RTFGetToken( info );
1294 if (info->rtfClass == rtfText)
1296 if (level == 1)
1298 if (!buffer)
1299 size = read_hex_data( info, &buffer );
1301 else
1303 RTFSkipGroup( info );
1305 } /* We potentially have a new token so fall through. */
1307 if (info->rtfClass == rtfEOF) return;
1309 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1311 if (--level == 0) break;
1312 continue;
1314 if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1316 level++;
1317 continue;
1319 if (!RTFCheckCM( info, rtfControl, rtfPictAttr ))
1321 RTFRouteToken( info );
1322 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1323 level--;
1324 continue;
1327 if (RTFCheckMM( info, rtfPictAttr, rtfWinMetafile ))
1329 mfp.mm = info->rtfParam;
1330 gfx = gfx_metafile;
1332 else if (RTFCheckMM( info, rtfPictAttr, rtfDevIndBitmap ))
1334 if (info->rtfParam != 0) FIXME("dibitmap should be 0 (%d)\n", info->rtfParam);
1335 gfx = gfx_dib;
1337 else if (RTFCheckMM( info, rtfPictAttr, rtfEmfBlip ))
1338 gfx = gfx_enhmetafile;
1339 else if (RTFCheckMM( info, rtfPictAttr, rtfPicWid ))
1340 mfp.xExt = info->rtfParam;
1341 else if (RTFCheckMM( info, rtfPictAttr, rtfPicHt ))
1342 mfp.yExt = info->rtfParam;
1343 else if (RTFCheckMM( info, rtfPictAttr, rtfPicGoalWid ))
1344 sz.cx = info->rtfParam;
1345 else if (RTFCheckMM( info, rtfPictAttr, rtfPicGoalHt ))
1346 sz.cy = info->rtfParam;
1347 else
1348 FIXME("Non supported attribute: %d %d %d\n", info->rtfClass, info->rtfMajor, info->rtfMinor);
1351 if (buffer)
1353 switch (gfx)
1355 case gfx_enhmetafile:
1356 if ((hemf = SetEnhMetaFileBits( size, buffer )))
1357 insert_static_object( info->editor, hemf, NULL, &sz );
1358 break;
1359 case gfx_metafile:
1360 if ((hemf = SetWinMetaFileBits( size, buffer, NULL, &mfp )))
1361 insert_static_object( info->editor, hemf, NULL, &sz );
1362 break;
1363 case gfx_dib:
1365 BITMAPINFO *bi = (BITMAPINFO*)buffer;
1366 HDC hdc = GetDC(0);
1367 unsigned nc = bi->bmiHeader.biClrUsed;
1369 /* not quite right, especially for bitfields type of compression */
1370 if (!nc && bi->bmiHeader.biBitCount <= 8)
1371 nc = 1 << bi->bmiHeader.biBitCount;
1372 if ((hbmp = CreateDIBitmap( hdc, &bi->bmiHeader,
1373 CBM_INIT, (char*)(bi + 1) + nc * sizeof(RGBQUAD),
1374 bi, DIB_RGB_COLORS)) )
1375 insert_static_object( info->editor, NULL, hbmp, &sz );
1376 ReleaseDC( 0, hdc );
1377 break;
1379 default:
1380 break;
1383 HeapFree( GetProcessHeap(), 0, buffer );
1384 RTFRouteToken( info ); /* feed "}" back to router */
1385 return;
1388 /* for now, lookup the \result part and use it, whatever the object */
1389 static void ME_RTFReadObjectGroup(RTF_Info *info)
1391 for (;;)
1393 RTFGetToken (info);
1394 if (info->rtfClass == rtfEOF)
1395 return;
1396 if (RTFCheckCM(info, rtfGroup, rtfEndGroup))
1397 break;
1398 if (RTFCheckCM(info, rtfGroup, rtfBeginGroup))
1400 RTFGetToken (info);
1401 if (info->rtfClass == rtfEOF)
1402 return;
1403 if (RTFCheckCMM(info, rtfControl, rtfDestination, rtfObjResult))
1405 int level = 1;
1407 while (RTFGetToken (info) != rtfEOF)
1409 if (info->rtfClass == rtfGroup)
1411 if (info->rtfMajor == rtfBeginGroup) level++;
1412 else if (info->rtfMajor == rtfEndGroup && --level < 0) break;
1414 RTFRouteToken(info);
1417 else RTFSkipGroup(info);
1418 continue;
1420 if (!RTFCheckCM (info, rtfControl, rtfObjAttr))
1422 FIXME("Non supported attribute: %d %d %d\n", info->rtfClass, info->rtfMajor, info->rtfMinor);
1423 return;
1426 RTFRouteToken(info); /* feed "}" back to router */
1429 static void ME_RTFReadParnumGroup( RTF_Info *info )
1431 int level = 1, type = -1;
1432 WORD indent = 0, start = 1;
1433 WCHAR txt_before = 0, txt_after = 0;
1435 for (;;)
1437 RTFGetToken( info );
1439 if (RTFCheckCMM( info, rtfControl, rtfDestination, rtfParNumTextBefore ) ||
1440 RTFCheckCMM( info, rtfControl, rtfDestination, rtfParNumTextAfter ))
1442 int loc = info->rtfMinor;
1444 RTFGetToken( info );
1445 if (info->rtfClass == rtfText)
1447 if (loc == rtfParNumTextBefore)
1448 txt_before = info->rtfMajor;
1449 else
1450 txt_after = info->rtfMajor;
1451 continue;
1453 /* falling through to catch EOFs and group level changes */
1456 if (info->rtfClass == rtfEOF)
1457 return;
1459 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1461 if (--level == 0) break;
1462 continue;
1465 if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1467 level++;
1468 continue;
1471 /* Ignore non para-attr */
1472 if (!RTFCheckCM( info, rtfControl, rtfParAttr ))
1473 continue;
1475 switch (info->rtfMinor)
1477 case rtfParLevel: /* Para level is ignored */
1478 case rtfParSimple:
1479 break;
1480 case rtfParBullet:
1481 type = PFN_BULLET;
1482 break;
1484 case rtfParNumDecimal:
1485 type = PFN_ARABIC;
1486 break;
1487 case rtfParNumULetter:
1488 type = PFN_UCLETTER;
1489 break;
1490 case rtfParNumURoman:
1491 type = PFN_UCROMAN;
1492 break;
1493 case rtfParNumLLetter:
1494 type = PFN_LCLETTER;
1495 break;
1496 case rtfParNumLRoman:
1497 type = PFN_LCROMAN;
1498 break;
1500 case rtfParNumIndent:
1501 indent = info->rtfParam;
1502 break;
1503 case rtfParNumStartAt:
1504 start = info->rtfParam;
1505 break;
1509 if (type != -1)
1511 info->fmt.dwMask |= (PFM_NUMBERING | PFM_NUMBERINGSTART | PFM_NUMBERINGSTYLE | PFM_NUMBERINGTAB);
1512 info->fmt.wNumbering = type;
1513 info->fmt.wNumberingStart = start;
1514 info->fmt.wNumberingStyle = PFNS_PAREN;
1515 if (type != PFN_BULLET)
1517 if (txt_before == 0 && txt_after == 0)
1518 info->fmt.wNumberingStyle = PFNS_PLAIN;
1519 else if (txt_after == '.')
1520 info->fmt.wNumberingStyle = PFNS_PERIOD;
1521 else if (txt_before == '(' && txt_after == ')')
1522 info->fmt.wNumberingStyle = PFNS_PARENS;
1524 info->fmt.wNumberingTab = indent;
1527 TRACE("type %d indent %d start %d txt before %04x txt after %04x\n",
1528 type, indent, start, txt_before, txt_after);
1530 RTFRouteToken( info ); /* feed "}" back to router */
1533 static void ME_RTFReadHook(RTF_Info *info)
1535 switch(info->rtfClass)
1537 case rtfGroup:
1538 switch(info->rtfMajor)
1540 case rtfBeginGroup:
1541 if (info->stackTop < maxStack) {
1542 info->stack[info->stackTop].style = info->style;
1543 ME_AddRefStyle(info->style);
1544 info->stack[info->stackTop].codePage = info->codePage;
1545 info->stack[info->stackTop].unicodeLength = info->unicodeLength;
1547 info->stackTop++;
1548 info->styleChanged = FALSE;
1549 break;
1550 case rtfEndGroup:
1552 RTFFlushOutputBuffer(info);
1553 info->stackTop--;
1554 if (info->stackTop <= 0)
1555 info->rtfClass = rtfEOF;
1556 if (info->stackTop < 0)
1557 return;
1559 ME_ReleaseStyle(info->style);
1560 info->style = info->stack[info->stackTop].style;
1561 info->codePage = info->stack[info->stackTop].codePage;
1562 info->unicodeLength = info->stack[info->stackTop].unicodeLength;
1563 break;
1566 break;
1570 void
1571 ME_StreamInFill(ME_InStream *stream)
1573 stream->editstream->dwError = stream->editstream->pfnCallback(stream->editstream->dwCookie,
1574 (BYTE *)stream->buffer,
1575 sizeof(stream->buffer),
1576 (LONG *)&stream->dwSize);
1577 stream->dwUsed = 0;
1580 static LRESULT ME_StreamIn(ME_TextEditor *editor, DWORD format, EDITSTREAM *stream, BOOL stripLastCR)
1582 RTF_Info parser;
1583 ME_Style *style;
1584 int from, to, nUndoMode;
1585 int nEventMask = editor->nEventMask;
1586 ME_InStream inStream;
1587 BOOL invalidRTF = FALSE;
1588 ME_Cursor *selStart, *selEnd;
1589 LRESULT num_read = 0; /* bytes read for SF_TEXT, non-control chars inserted for SF_RTF */
1591 TRACE("stream==%p editor==%p format==0x%X\n", stream, editor, format);
1592 editor->nEventMask = 0;
1594 ME_GetSelectionOfs(editor, &from, &to);
1595 if (format & SFF_SELECTION && editor->mode & TM_RICHTEXT)
1597 ME_GetSelection(editor, &selStart, &selEnd);
1598 style = ME_GetSelectionInsertStyle(editor);
1600 ME_InternalDeleteText(editor, selStart, to - from, FALSE);
1602 /* Don't insert text at the end of the table row */
1603 if (!editor->bEmulateVersion10) { /* v4.1 */
1604 ME_DisplayItem *para = editor->pCursors->pPara;
1605 if (para->member.para.nFlags & MEPF_ROWEND)
1607 para = para->member.para.next_para;
1608 editor->pCursors[0].pPara = para;
1609 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
1610 editor->pCursors[0].nOffset = 0;
1612 if (para->member.para.nFlags & MEPF_ROWSTART)
1614 para = para->member.para.next_para;
1615 editor->pCursors[0].pPara = para;
1616 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
1617 editor->pCursors[0].nOffset = 0;
1619 editor->pCursors[1] = editor->pCursors[0];
1620 } else { /* v1.0 - 3.0 */
1621 if (editor->pCursors[0].pRun->member.run.nFlags & MERF_ENDPARA &&
1622 ME_IsInTable(editor->pCursors[0].pRun))
1623 return 0;
1625 } else {
1626 style = editor->pBuffer->pDefaultStyle;
1627 ME_AddRefStyle(style);
1628 ME_SetSelection(editor, 0, 0);
1629 ME_InternalDeleteText(editor, &editor->pCursors[1],
1630 ME_GetTextLength(editor), FALSE);
1631 from = to = 0;
1632 ME_ClearTempStyle(editor);
1633 ME_SetDefaultParaFormat(editor, &editor->pCursors[0].pPara->member.para.fmt);
1637 /* Back up undo mode to a local variable */
1638 nUndoMode = editor->nUndoMode;
1640 /* Only create an undo if SFF_SELECTION is set */
1641 if (!(format & SFF_SELECTION))
1642 editor->nUndoMode = umIgnore;
1644 inStream.editstream = stream;
1645 inStream.editstream->dwError = 0;
1646 inStream.dwSize = 0;
1647 inStream.dwUsed = 0;
1649 if (format & SF_RTF)
1651 /* Check if it's really RTF, and if it is not, use plain text */
1652 ME_StreamInFill(&inStream);
1653 if (!inStream.editstream->dwError)
1655 if ((!editor->bEmulateVersion10 && strncmp(inStream.buffer, "{\\rtf", 5) && strncmp(inStream.buffer, "{\\urtf", 6))
1656 || (editor->bEmulateVersion10 && *inStream.buffer != '{'))
1658 invalidRTF = TRUE;
1659 inStream.editstream->dwError = -16;
1664 if (!invalidRTF && !inStream.editstream->dwError)
1666 ME_Cursor start;
1667 from = ME_GetCursorOfs(&editor->pCursors[0]);
1668 if (format & SF_RTF) {
1670 /* setup the RTF parser */
1671 memset(&parser, 0, sizeof parser);
1672 RTFSetEditStream(&parser, &inStream);
1673 parser.rtfFormat = format&(SF_TEXT|SF_RTF);
1674 parser.editor = editor;
1675 parser.style = style;
1676 WriterInit(&parser);
1677 RTFInit(&parser);
1678 RTFSetReadHook(&parser, ME_RTFReadHook);
1679 RTFSetDestinationCallback(&parser, rtfShpPict, ME_RTFReadShpPictGroup);
1680 RTFSetDestinationCallback(&parser, rtfPict, ME_RTFReadPictGroup);
1681 RTFSetDestinationCallback(&parser, rtfObject, ME_RTFReadObjectGroup);
1682 RTFSetDestinationCallback(&parser, rtfParNumbering, ME_RTFReadParnumGroup);
1683 if (!parser.editor->bEmulateVersion10) /* v4.1 */
1685 RTFSetDestinationCallback(&parser, rtfNoNestTables, RTFSkipGroup);
1686 RTFSetDestinationCallback(&parser, rtfNestTableProps, RTFReadGroup);
1688 BeginFile(&parser);
1690 /* do the parsing */
1691 RTFRead(&parser);
1692 RTFFlushOutputBuffer(&parser);
1693 if (!editor->bEmulateVersion10) { /* v4.1 */
1694 if (parser.tableDef && parser.tableDef->tableRowStart &&
1695 (parser.nestingLevel > 0 || parser.canInheritInTbl))
1697 /* Delete any incomplete table row at the end of the rich text. */
1698 int nOfs, nChars;
1699 ME_DisplayItem *para;
1701 parser.rtfMinor = rtfRow;
1702 /* Complete the table row before deleting it.
1703 * By doing it this way we will have the current paragraph format set
1704 * properly to reflect that is not in the complete table, and undo items
1705 * will be added for this change to the current paragraph format. */
1706 if (parser.nestingLevel > 0)
1708 while (parser.nestingLevel > 1)
1709 ME_RTFSpecialCharHook(&parser); /* Decrements nestingLevel */
1710 para = parser.tableDef->tableRowStart;
1711 ME_RTFSpecialCharHook(&parser);
1712 } else {
1713 para = parser.tableDef->tableRowStart;
1714 ME_RTFSpecialCharHook(&parser);
1715 assert(para->member.para.nFlags & MEPF_ROWEND);
1716 para = para->member.para.next_para;
1719 editor->pCursors[1].pPara = para;
1720 editor->pCursors[1].pRun = ME_FindItemFwd(para, diRun);
1721 editor->pCursors[1].nOffset = 0;
1722 nOfs = ME_GetCursorOfs(&editor->pCursors[1]);
1723 nChars = ME_GetCursorOfs(&editor->pCursors[0]) - nOfs;
1724 ME_InternalDeleteText(editor, &editor->pCursors[1], nChars, TRUE);
1725 if (parser.tableDef)
1726 parser.tableDef->tableRowStart = NULL;
1729 ME_CheckTablesForCorruption(editor);
1730 RTFDestroy(&parser);
1732 if (parser.stackTop > 0)
1734 while (--parser.stackTop >= 0)
1736 ME_ReleaseStyle(parser.style);
1737 parser.style = parser.stack[parser.stackTop].style;
1739 if (!inStream.editstream->dwError)
1740 inStream.editstream->dwError = HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
1743 /* Remove last line break, as mandated by tests. This is not affected by
1744 CR/LF counters, since RTF streaming presents only \para tokens, which
1745 are converted according to the standard rules: \r for 2.0, \r\n for 1.0
1747 if (stripLastCR && !(format & SFF_SELECTION)) {
1748 int newto;
1749 ME_GetSelection(editor, &selStart, &selEnd);
1750 newto = ME_GetCursorOfs(selEnd);
1751 if (newto > to + (editor->bEmulateVersion10 ? 1 : 0)) {
1752 WCHAR lastchar[3] = {'\0', '\0'};
1753 int linebreakSize = editor->bEmulateVersion10 ? 2 : 1;
1754 ME_Cursor linebreakCursor = *selEnd, lastcharCursor = *selEnd;
1755 CHARFORMAT2W cf;
1757 /* Set the final eop to the char fmt of the last char */
1758 cf.cbSize = sizeof(cf);
1759 cf.dwMask = CFM_ALL2;
1760 ME_MoveCursorChars(editor, &lastcharCursor, -1, FALSE);
1761 ME_GetCharFormat(editor, &lastcharCursor, &linebreakCursor, &cf);
1762 ME_SetSelection(editor, newto, -1);
1763 ME_SetSelectionCharFormat(editor, &cf);
1764 ME_SetSelection(editor, newto, newto);
1766 ME_MoveCursorChars(editor, &linebreakCursor, -linebreakSize, FALSE);
1767 ME_GetTextW(editor, lastchar, 2, &linebreakCursor, linebreakSize, FALSE, FALSE);
1768 if (lastchar[0] == '\r' && (lastchar[1] == '\n' || lastchar[1] == '\0')) {
1769 ME_InternalDeleteText(editor, &linebreakCursor, linebreakSize, FALSE);
1773 to = ME_GetCursorOfs(&editor->pCursors[0]);
1774 num_read = to - from;
1776 style = parser.style;
1778 else if (format & SF_TEXT)
1780 num_read = ME_StreamInText(editor, format, &inStream, style);
1781 to = ME_GetCursorOfs(&editor->pCursors[0]);
1783 else
1784 ERR("EM_STREAMIN without SF_TEXT or SF_RTF\n");
1785 /* put the cursor at the top */
1786 if (!(format & SFF_SELECTION))
1787 ME_SetSelection(editor, 0, 0);
1788 ME_CursorFromCharOfs(editor, from, &start);
1789 ME_UpdateLinkAttribute(editor, &start, to - from);
1792 /* Restore saved undo mode */
1793 editor->nUndoMode = nUndoMode;
1795 /* even if we didn't add an undo, we need to commit anything on the stack */
1796 ME_CommitUndo(editor);
1798 /* If SFF_SELECTION isn't set, delete any undos from before we started too */
1799 if (!(format & SFF_SELECTION))
1800 ME_EmptyUndoStack(editor);
1802 ME_ReleaseStyle(style);
1803 editor->nEventMask = nEventMask;
1804 ME_UpdateRepaint(editor, FALSE);
1805 if (!(format & SFF_SELECTION)) {
1806 ME_ClearTempStyle(editor);
1808 ITextHost_TxShowCaret(editor->texthost, FALSE);
1809 ME_MoveCaret(editor);
1810 ITextHost_TxShowCaret(editor->texthost, TRUE);
1811 ME_SendSelChange(editor);
1812 ME_SendRequestResize(editor, FALSE);
1814 return num_read;
1818 typedef struct tagME_RTFStringStreamStruct
1820 char *string;
1821 int pos;
1822 int length;
1823 } ME_RTFStringStreamStruct;
1825 static DWORD CALLBACK ME_ReadFromRTFString(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
1827 ME_RTFStringStreamStruct *pStruct = (ME_RTFStringStreamStruct *)dwCookie;
1828 int count;
1830 count = min(cb, pStruct->length - pStruct->pos);
1831 memmove(lpBuff, pStruct->string + pStruct->pos, count);
1832 pStruct->pos += count;
1833 *pcb = count;
1834 return 0;
1837 static void
1838 ME_StreamInRTFString(ME_TextEditor *editor, BOOL selection, char *string)
1840 EDITSTREAM es;
1841 ME_RTFStringStreamStruct data;
1843 data.string = string;
1844 data.length = strlen(string);
1845 data.pos = 0;
1846 es.dwCookie = (DWORD_PTR)&data;
1847 es.pfnCallback = ME_ReadFromRTFString;
1848 ME_StreamIn(editor, SF_RTF | (selection ? SFF_SELECTION : 0), &es, TRUE);
1852 static int
1853 ME_FindText(ME_TextEditor *editor, DWORD flags, const CHARRANGE *chrg, const WCHAR *text, CHARRANGE *chrgText)
1855 const int nLen = lstrlenW(text);
1856 const int nTextLen = ME_GetTextLength(editor);
1857 int nMin, nMax;
1858 ME_Cursor cursor;
1859 WCHAR wLastChar = ' ';
1861 TRACE("flags==0x%08x, chrg->cpMin==%d, chrg->cpMax==%d text==%s\n",
1862 flags, chrg->cpMin, chrg->cpMax, debugstr_w(text));
1864 if (flags & ~(FR_DOWN | FR_MATCHCASE | FR_WHOLEWORD))
1865 FIXME("Flags 0x%08x not implemented\n",
1866 flags & ~(FR_DOWN | FR_MATCHCASE | FR_WHOLEWORD));
1868 nMin = chrg->cpMin;
1869 if (chrg->cpMax == -1)
1870 nMax = nTextLen;
1871 else
1872 nMax = chrg->cpMax > nTextLen ? nTextLen : chrg->cpMax;
1874 /* In 1.0 emulation, if cpMax reaches end of text, add the FR_DOWN flag */
1875 if (editor->bEmulateVersion10 && nMax == nTextLen)
1877 flags |= FR_DOWN;
1880 /* In 1.0 emulation, cpMin must always be no greater than cpMax */
1881 if (editor->bEmulateVersion10 && nMax < nMin)
1883 if (chrgText)
1885 chrgText->cpMin = -1;
1886 chrgText->cpMax = -1;
1888 return -1;
1891 /* when searching up, if cpMin < cpMax, then instead of searching
1892 * on [cpMin,cpMax], we search on [0,cpMin], otherwise, search on
1893 * [cpMax, cpMin]. The exception is when cpMax is -1, in which
1894 * case, it is always bigger than cpMin.
1896 if (!editor->bEmulateVersion10 && !(flags & FR_DOWN))
1898 int nSwap = nMax;
1900 nMax = nMin > nTextLen ? nTextLen : nMin;
1901 if (nMin < nSwap || chrg->cpMax == -1)
1902 nMin = 0;
1903 else
1904 nMin = nSwap;
1907 if (!nLen || nMin < 0 || nMax < 0 || nMax < nMin)
1909 if (chrgText)
1910 chrgText->cpMin = chrgText->cpMax = -1;
1911 return -1;
1914 if (flags & FR_DOWN) /* Forward search */
1916 /* If possible, find the character before where the search starts */
1917 if ((flags & FR_WHOLEWORD) && nMin)
1919 ME_CursorFromCharOfs(editor, nMin - 1, &cursor);
1920 wLastChar = *get_text( &cursor.pRun->member.run, cursor.nOffset );
1921 ME_MoveCursorChars(editor, &cursor, 1, FALSE);
1922 } else {
1923 ME_CursorFromCharOfs(editor, nMin, &cursor);
1926 while (cursor.pRun && ME_GetCursorOfs(&cursor) + nLen <= nMax)
1928 ME_DisplayItem *pCurItem = cursor.pRun;
1929 int nCurStart = cursor.nOffset;
1930 int nMatched = 0;
1932 while (pCurItem && ME_CharCompare( *get_text( &pCurItem->member.run, nCurStart + nMatched ), text[nMatched], (flags & FR_MATCHCASE)))
1934 if ((flags & FR_WHOLEWORD) && isalnumW(wLastChar))
1935 break;
1937 nMatched++;
1938 if (nMatched == nLen)
1940 ME_DisplayItem *pNextItem = pCurItem;
1941 int nNextStart = nCurStart;
1942 WCHAR wNextChar;
1944 /* Check to see if next character is a whitespace */
1945 if (flags & FR_WHOLEWORD)
1947 if (nCurStart + nMatched == pCurItem->member.run.len)
1949 pNextItem = ME_FindItemFwd(pCurItem, diRun);
1950 nNextStart = -nMatched;
1953 if (pNextItem)
1954 wNextChar = *get_text( &pNextItem->member.run, nNextStart + nMatched );
1955 else
1956 wNextChar = ' ';
1958 if (isalnumW(wNextChar))
1959 break;
1962 cursor.nOffset += cursor.pPara->member.para.nCharOfs + cursor.pRun->member.run.nCharOfs;
1963 if (chrgText)
1965 chrgText->cpMin = cursor.nOffset;
1966 chrgText->cpMax = cursor.nOffset + nLen;
1968 TRACE("found at %d-%d\n", cursor.nOffset, cursor.nOffset + nLen);
1969 return cursor.nOffset;
1971 if (nCurStart + nMatched == pCurItem->member.run.len)
1973 pCurItem = ME_FindItemFwd(pCurItem, diRun);
1974 nCurStart = -nMatched;
1977 if (pCurItem)
1978 wLastChar = *get_text( &pCurItem->member.run, nCurStart + nMatched );
1979 else
1980 wLastChar = ' ';
1982 cursor.nOffset++;
1983 if (cursor.nOffset == cursor.pRun->member.run.len)
1985 ME_NextRun(&cursor.pPara, &cursor.pRun, TRUE);
1986 cursor.nOffset = 0;
1990 else /* Backward search */
1992 /* If possible, find the character after where the search ends */
1993 if ((flags & FR_WHOLEWORD) && nMax < nTextLen - 1)
1995 ME_CursorFromCharOfs(editor, nMax + 1, &cursor);
1996 wLastChar = *get_text( &cursor.pRun->member.run, cursor.nOffset );
1997 ME_MoveCursorChars(editor, &cursor, -1, FALSE);
1998 } else {
1999 ME_CursorFromCharOfs(editor, nMax, &cursor);
2002 while (cursor.pRun && ME_GetCursorOfs(&cursor) - nLen >= nMin)
2004 ME_DisplayItem *pCurItem = cursor.pRun;
2005 ME_DisplayItem *pCurPara = cursor.pPara;
2006 int nCurEnd = cursor.nOffset;
2007 int nMatched = 0;
2009 if (nCurEnd == 0)
2011 ME_PrevRun(&pCurPara, &pCurItem, TRUE);
2012 nCurEnd = pCurItem->member.run.len;
2015 while (pCurItem && ME_CharCompare( *get_text( &pCurItem->member.run, nCurEnd - nMatched - 1 ),
2016 text[nLen - nMatched - 1], (flags & FR_MATCHCASE) ))
2018 if ((flags & FR_WHOLEWORD) && isalnumW(wLastChar))
2019 break;
2021 nMatched++;
2022 if (nMatched == nLen)
2024 ME_DisplayItem *pPrevItem = pCurItem;
2025 int nPrevEnd = nCurEnd;
2026 WCHAR wPrevChar;
2027 int nStart;
2029 /* Check to see if previous character is a whitespace */
2030 if (flags & FR_WHOLEWORD)
2032 if (nPrevEnd - nMatched == 0)
2034 pPrevItem = ME_FindItemBack(pCurItem, diRun);
2035 if (pPrevItem)
2036 nPrevEnd = pPrevItem->member.run.len + nMatched;
2039 if (pPrevItem)
2040 wPrevChar = *get_text( &pPrevItem->member.run, nPrevEnd - nMatched - 1 );
2041 else
2042 wPrevChar = ' ';
2044 if (isalnumW(wPrevChar))
2045 break;
2048 nStart = pCurPara->member.para.nCharOfs
2049 + pCurItem->member.run.nCharOfs + nCurEnd - nMatched;
2050 if (chrgText)
2052 chrgText->cpMin = nStart;
2053 chrgText->cpMax = nStart + nLen;
2055 TRACE("found at %d-%d\n", nStart, nStart + nLen);
2056 return nStart;
2058 if (nCurEnd - nMatched == 0)
2060 ME_PrevRun(&pCurPara, &pCurItem, TRUE);
2061 /* Don't care about pCurItem becoming NULL here; it's already taken
2062 * care of in the exterior loop condition */
2063 nCurEnd = pCurItem->member.run.len + nMatched;
2066 if (pCurItem)
2067 wLastChar = *get_text( &pCurItem->member.run, nCurEnd - nMatched - 1 );
2068 else
2069 wLastChar = ' ';
2071 cursor.nOffset--;
2072 if (cursor.nOffset < 0)
2074 ME_PrevRun(&cursor.pPara, &cursor.pRun, TRUE);
2075 cursor.nOffset = cursor.pRun->member.run.len;
2079 TRACE("not found\n");
2080 if (chrgText)
2081 chrgText->cpMin = chrgText->cpMax = -1;
2082 return -1;
2085 static int ME_GetTextEx(ME_TextEditor *editor, GETTEXTEX *ex, LPARAM pText)
2087 int nChars;
2088 ME_Cursor start;
2090 if (!ex->cb || !pText) return 0;
2092 if (ex->flags & ~(GT_SELECTION | GT_USECRLF))
2093 FIXME("GETTEXTEX flags 0x%08x not supported\n", ex->flags & ~(GT_SELECTION | GT_USECRLF));
2095 if (ex->flags & GT_SELECTION)
2097 int from, to;
2098 int nStartCur = ME_GetSelectionOfs(editor, &from, &to);
2099 start = editor->pCursors[nStartCur];
2100 nChars = to - from;
2102 else
2104 ME_SetCursorToStart(editor, &start);
2105 nChars = INT_MAX;
2107 if (ex->codepage == CP_UNICODE)
2109 return ME_GetTextW(editor, (LPWSTR)pText, ex->cb / sizeof(WCHAR) - 1,
2110 &start, nChars, ex->flags & GT_USECRLF, FALSE);
2112 else
2114 /* potentially each char may be a CR, why calculate the exact value with O(N) when
2115 we can just take a bigger buffer? :)
2116 The above assumption still holds with CR/LF counters, since CR->CRLF expansion
2117 occurs only in richedit 2.0 mode, in which line breaks have only one CR
2119 int crlfmul = (ex->flags & GT_USECRLF) ? 2 : 1;
2120 DWORD buflen;
2121 LPWSTR buffer;
2122 LRESULT rc;
2124 buflen = min(crlfmul * nChars, ex->cb - 1);
2125 buffer = heap_alloc((buflen + 1) * sizeof(WCHAR));
2127 nChars = ME_GetTextW(editor, buffer, buflen, &start, nChars, ex->flags & GT_USECRLF, FALSE);
2128 rc = WideCharToMultiByte(ex->codepage, 0, buffer, nChars + 1,
2129 (LPSTR)pText, ex->cb, ex->lpDefaultChar, ex->lpUsedDefChar);
2130 if (rc) rc--; /* do not count 0 terminator */
2132 heap_free(buffer);
2133 return rc;
2137 static int ME_GetTextRange(ME_TextEditor *editor, WCHAR *strText,
2138 const ME_Cursor *start, int nLen, BOOL unicode)
2140 if (!strText) return 0;
2141 if (unicode) {
2142 return ME_GetTextW(editor, strText, INT_MAX, start, nLen, FALSE, FALSE);
2143 } else {
2144 int nChars;
2145 WCHAR *p = ALLOC_N_OBJ(WCHAR, nLen+1);
2146 if (!p) return 0;
2147 nChars = ME_GetTextW(editor, p, nLen, start, nLen, FALSE, FALSE);
2148 WideCharToMultiByte(CP_ACP, 0, p, nChars+1, (char *)strText,
2149 nLen+1, NULL, NULL);
2150 FREE_OBJ(p);
2151 return nChars;
2155 static int handle_EM_EXSETSEL( ME_TextEditor *editor, int to, int from )
2157 int end;
2159 TRACE("%d - %d\n", to, from );
2161 ME_InvalidateSelection( editor );
2162 end = ME_SetSelection( editor, to, from );
2163 ME_InvalidateSelection( editor );
2164 ITextHost_TxShowCaret( editor->texthost, FALSE );
2165 ME_ShowCaret( editor );
2166 ME_SendSelChange( editor );
2168 return end;
2171 typedef struct tagME_GlobalDestStruct
2173 HGLOBAL hData;
2174 int nLength;
2175 } ME_GlobalDestStruct;
2177 static DWORD CALLBACK ME_ReadFromHGLOBALUnicode(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
2179 ME_GlobalDestStruct *pData = (ME_GlobalDestStruct *)dwCookie;
2180 int i;
2181 WORD *pSrc, *pDest;
2183 cb = cb >> 1;
2184 pDest = (WORD *)lpBuff;
2185 pSrc = GlobalLock(pData->hData);
2186 for (i = 0; i<cb && pSrc[pData->nLength+i]; i++) {
2187 pDest[i] = pSrc[pData->nLength+i];
2189 pData->nLength += i;
2190 *pcb = 2*i;
2191 GlobalUnlock(pData->hData);
2192 return 0;
2195 static DWORD CALLBACK ME_ReadFromHGLOBALRTF(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
2197 ME_GlobalDestStruct *pData = (ME_GlobalDestStruct *)dwCookie;
2198 int i;
2199 BYTE *pSrc, *pDest;
2201 pDest = lpBuff;
2202 pSrc = GlobalLock(pData->hData);
2203 for (i = 0; i<cb && pSrc[pData->nLength+i]; i++) {
2204 pDest[i] = pSrc[pData->nLength+i];
2206 pData->nLength += i;
2207 *pcb = i;
2208 GlobalUnlock(pData->hData);
2209 return 0;
2212 static const WCHAR rtfW[] = {'R','i','c','h',' ','T','e','x','t',' ','F','o','r','m','a','t',0};
2214 static HRESULT paste_rtf(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2216 EDITSTREAM es;
2217 ME_GlobalDestStruct gds;
2218 HRESULT hr;
2220 gds.hData = med->u.hGlobal;
2221 gds.nLength = 0;
2222 es.dwCookie = (DWORD_PTR)&gds;
2223 es.pfnCallback = ME_ReadFromHGLOBALRTF;
2224 hr = ME_StreamIn( editor, SF_RTF | SFF_SELECTION, &es, FALSE ) == 0 ? E_FAIL : S_OK;
2225 ReleaseStgMedium( med );
2226 return hr;
2229 static HRESULT paste_text(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2231 EDITSTREAM es;
2232 ME_GlobalDestStruct gds;
2233 HRESULT hr;
2235 gds.hData = med->u.hGlobal;
2236 gds.nLength = 0;
2237 es.dwCookie = (DWORD_PTR)&gds;
2238 es.pfnCallback = ME_ReadFromHGLOBALUnicode;
2239 hr = ME_StreamIn( editor, SF_TEXT | SF_UNICODE | SFF_SELECTION, &es, FALSE ) == 0 ? E_FAIL : S_OK;
2240 ReleaseStgMedium( med );
2241 return hr;
2244 static HRESULT paste_emf(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2246 HRESULT hr;
2247 SIZEL sz = {0, 0};
2249 hr = insert_static_object( editor, med->u.hEnhMetaFile, NULL, &sz );
2250 if (SUCCEEDED(hr))
2252 ME_CommitUndo( editor );
2253 ME_UpdateRepaint( editor, FALSE );
2255 else
2256 ReleaseStgMedium( med );
2258 return hr;
2261 static struct paste_format
2263 FORMATETC fmt;
2264 HRESULT (*paste)(ME_TextEditor *, FORMATETC *, STGMEDIUM *);
2265 const WCHAR *name;
2266 } paste_formats[] =
2268 {{ -1, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, paste_rtf, rtfW },
2269 {{ CF_UNICODETEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, paste_text },
2270 {{ CF_ENHMETAFILE, NULL, DVASPECT_CONTENT, -1, TYMED_ENHMF }, paste_emf },
2271 {{ 0 }}
2274 static void init_paste_formats(void)
2276 struct paste_format *format;
2277 static int done;
2279 if (!done)
2281 for (format = paste_formats; format->fmt.cfFormat; format++)
2283 if (format->name)
2284 format->fmt.cfFormat = RegisterClipboardFormatW( format->name );
2286 done = 1;
2290 static BOOL paste_special(ME_TextEditor *editor, UINT cf, REPASTESPECIAL *ps, BOOL check_only)
2292 HRESULT hr;
2293 STGMEDIUM med;
2294 struct paste_format *format;
2295 IDataObject *data;
2297 /* Protect read-only edit control from modification */
2298 if (editor->styleFlags & ES_READONLY)
2300 if (!check_only)
2301 MessageBeep(MB_ICONERROR);
2302 return FALSE;
2305 init_paste_formats();
2307 if (ps && ps->dwAspect != DVASPECT_CONTENT)
2308 FIXME("Ignoring aspect %x\n", ps->dwAspect);
2310 hr = OleGetClipboard( &data );
2311 if (hr != S_OK) return FALSE;
2313 if (cf == CF_TEXT) cf = CF_UNICODETEXT;
2315 hr = S_FALSE;
2316 for (format = paste_formats; format->fmt.cfFormat; format++)
2318 if (cf && cf != format->fmt.cfFormat) continue;
2319 hr = IDataObject_QueryGetData( data, &format->fmt );
2320 if (hr == S_OK)
2322 if (!check_only)
2324 hr = IDataObject_GetData( data, &format->fmt, &med );
2325 if (hr != S_OK) goto done;
2326 hr = format->paste( editor, &format->fmt, &med );
2328 break;
2332 done:
2333 IDataObject_Release( data );
2335 return hr == S_OK;
2338 static BOOL ME_Copy(ME_TextEditor *editor, const ME_Cursor *start, int nChars)
2340 LPDATAOBJECT dataObj = NULL;
2341 HRESULT hr = S_OK;
2343 if (editor->cPasswordMask)
2344 return FALSE; /* Copying or Cutting masked text isn't allowed */
2346 if(editor->lpOleCallback)
2348 CHARRANGE range;
2349 range.cpMin = ME_GetCursorOfs(start);
2350 range.cpMax = range.cpMin + nChars;
2351 hr = IRichEditOleCallback_GetClipboardData(editor->lpOleCallback, &range, RECO_COPY, &dataObj);
2353 if(FAILED(hr) || !dataObj)
2354 hr = ME_GetDataObject(editor, start, nChars, &dataObj);
2355 if(SUCCEEDED(hr)) {
2356 hr = OleSetClipboard(dataObj);
2357 IDataObject_Release(dataObj);
2359 return SUCCEEDED(hr);
2362 static BOOL copy_or_cut(ME_TextEditor *editor, BOOL cut)
2364 BOOL result;
2365 int offs, num_chars;
2366 int start_cursor = ME_GetSelectionOfs(editor, &offs, &num_chars);
2367 ME_Cursor *sel_start = &editor->pCursors[start_cursor];
2369 if (cut && (editor->styleFlags & ES_READONLY))
2371 MessageBeep(MB_ICONERROR);
2372 return FALSE;
2375 num_chars -= offs;
2376 result = ME_Copy(editor, sel_start, num_chars);
2377 if (result && cut)
2379 ME_InternalDeleteText(editor, sel_start, num_chars, FALSE);
2380 ME_CommitUndo(editor);
2381 ME_UpdateRepaint(editor, TRUE);
2383 return result;
2386 /* helper to send a msg filter notification */
2387 static BOOL
2388 ME_FilterEvent(ME_TextEditor *editor, UINT msg, WPARAM* wParam, LPARAM* lParam)
2390 MSGFILTER msgf;
2392 if (!editor->hWnd || !editor->hwndParent) return FALSE;
2393 msgf.nmhdr.hwndFrom = editor->hWnd;
2394 msgf.nmhdr.idFrom = GetWindowLongW(editor->hWnd, GWLP_ID);
2395 msgf.nmhdr.code = EN_MSGFILTER;
2396 msgf.msg = msg;
2397 msgf.wParam = *wParam;
2398 msgf.lParam = *lParam;
2399 if (SendMessageW(editor->hwndParent, WM_NOTIFY, msgf.nmhdr.idFrom, (LPARAM)&msgf))
2400 return FALSE;
2401 *wParam = msgf.wParam;
2402 *lParam = msgf.lParam;
2403 msgf.wParam = *wParam;
2405 return TRUE;
2408 static void ME_UpdateSelectionLinkAttribute(ME_TextEditor *editor)
2410 ME_DisplayItem *startPara, *endPara;
2411 ME_DisplayItem *prev_para;
2412 ME_Cursor *from, *to;
2413 ME_Cursor start;
2414 int nChars;
2416 if (!editor->AutoURLDetect_bEnable) return;
2418 ME_GetSelection(editor, &from, &to);
2420 /* Find paragraph previous to the one that contains start cursor */
2421 startPara = from->pPara;
2422 prev_para = startPara->member.para.prev_para;
2423 if (prev_para->type == diParagraph) startPara = prev_para;
2425 /* Find paragraph that contains end cursor */
2426 endPara = to->pPara->member.para.next_para;
2428 start.pPara = startPara;
2429 start.pRun = ME_FindItemFwd(startPara, diRun);
2430 start.nOffset = 0;
2431 nChars = endPara->member.para.nCharOfs - startPara->member.para.nCharOfs;
2433 ME_UpdateLinkAttribute(editor, &start, nChars);
2436 static BOOL
2437 ME_KeyDown(ME_TextEditor *editor, WORD nKey)
2439 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2440 BOOL shift_is_down = GetKeyState(VK_SHIFT) & 0x8000;
2442 if (editor->bMouseCaptured)
2443 return FALSE;
2444 if (nKey != VK_SHIFT && nKey != VK_CONTROL && nKey != VK_MENU)
2445 editor->nSelectionType = stPosition;
2447 switch (nKey)
2449 case VK_LEFT:
2450 case VK_RIGHT:
2451 case VK_HOME:
2452 case VK_END:
2453 editor->nUDArrowX = -1;
2454 /* fall through */
2455 case VK_UP:
2456 case VK_DOWN:
2457 case VK_PRIOR:
2458 case VK_NEXT:
2459 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
2460 ME_ArrowKey(editor, nKey, shift_is_down, ctrl_is_down);
2461 return TRUE;
2462 case VK_BACK:
2463 case VK_DELETE:
2464 editor->nUDArrowX = -1;
2465 /* FIXME backspace and delete aren't the same, they act different wrt paragraph style of the merged paragraph */
2466 if (editor->styleFlags & ES_READONLY)
2467 return FALSE;
2468 if (ME_IsSelection(editor))
2470 ME_DeleteSelection(editor);
2471 ME_CommitUndo(editor);
2473 else if (nKey == VK_DELETE)
2475 /* Delete stops group typing.
2476 * (See MSDN remarks on EM_STOPGROUPTYPING message) */
2477 ME_DeleteTextAtCursor(editor, 1, 1);
2478 ME_CommitUndo(editor);
2480 else if (ME_ArrowKey(editor, VK_LEFT, FALSE, FALSE))
2482 BOOL bDeletionSucceeded;
2483 /* Backspace can be grouped for a single undo */
2484 ME_ContinueCoalescingTransaction(editor);
2485 bDeletionSucceeded = ME_DeleteTextAtCursor(editor, 1, 1);
2486 if (!bDeletionSucceeded && !editor->bEmulateVersion10) { /* v4.1 */
2487 /* Deletion was prevented so the cursor is moved back to where it was.
2488 * (e.g. this happens when trying to delete cell boundaries)
2490 ME_ArrowKey(editor, VK_RIGHT, FALSE, FALSE);
2492 ME_CommitCoalescingUndo(editor);
2494 else
2495 return TRUE;
2496 ME_MoveCursorFromTableRowStartParagraph(editor);
2497 ME_UpdateSelectionLinkAttribute(editor);
2498 ME_UpdateRepaint(editor, FALSE);
2499 ME_SendRequestResize(editor, FALSE);
2500 return TRUE;
2501 case VK_RETURN:
2502 if (editor->bDialogMode)
2504 if (ctrl_is_down)
2505 return TRUE;
2507 if (!(editor->styleFlags & ES_WANTRETURN))
2509 if (editor->hwndParent)
2511 DWORD dw;
2512 dw = SendMessageW(editor->hwndParent, DM_GETDEFID, 0, 0);
2513 if (HIWORD(dw) == DC_HASDEFID)
2515 HWND hwDefCtrl = GetDlgItem(editor->hwndParent, LOWORD(dw));
2516 if (hwDefCtrl)
2518 SendMessageW(editor->hwndParent, WM_NEXTDLGCTL, (WPARAM)hwDefCtrl, TRUE);
2519 PostMessageW(hwDefCtrl, WM_KEYDOWN, VK_RETURN, 0);
2523 return TRUE;
2527 if (editor->styleFlags & ES_MULTILINE)
2529 ME_Cursor cursor = editor->pCursors[0];
2530 ME_DisplayItem *para = cursor.pPara;
2531 int from, to;
2532 const WCHAR endl = '\r';
2533 const WCHAR endlv10[] = {'\r','\n'};
2534 ME_Style *style, *eop_style;
2536 if (editor->styleFlags & ES_READONLY) {
2537 MessageBeep(MB_ICONERROR);
2538 return TRUE;
2541 ME_GetSelectionOfs(editor, &from, &to);
2542 if (editor->nTextLimit > ME_GetTextLength(editor) - (to-from))
2544 if (!editor->bEmulateVersion10) { /* v4.1 */
2545 if (para->member.para.nFlags & MEPF_ROWEND) {
2546 /* Add a new table row after this row. */
2547 para = ME_AppendTableRow(editor, para);
2548 para = para->member.para.next_para;
2549 editor->pCursors[0].pPara = para;
2550 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2551 editor->pCursors[0].nOffset = 0;
2552 editor->pCursors[1] = editor->pCursors[0];
2553 ME_CommitUndo(editor);
2554 ME_CheckTablesForCorruption(editor);
2555 ME_UpdateRepaint(editor, FALSE);
2556 return TRUE;
2558 else if (para == editor->pCursors[1].pPara &&
2559 cursor.nOffset + cursor.pRun->member.run.nCharOfs == 0 &&
2560 para->member.para.prev_para->member.para.nFlags & MEPF_ROWSTART &&
2561 !para->member.para.prev_para->member.para.nCharOfs)
2563 /* Insert a newline before the table. */
2564 para = para->member.para.prev_para;
2565 para->member.para.nFlags &= ~MEPF_ROWSTART;
2566 editor->pCursors[0].pPara = para;
2567 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2568 editor->pCursors[1] = editor->pCursors[0];
2569 ME_InsertTextFromCursor(editor, 0, &endl, 1,
2570 editor->pCursors[0].pRun->member.run.style);
2571 para = editor->pBuffer->pFirst->member.para.next_para;
2572 ME_SetDefaultParaFormat(editor, &para->member.para.fmt);
2573 para->member.para.nFlags = MEPF_REWRAP;
2574 editor->pCursors[0].pPara = para;
2575 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2576 editor->pCursors[1] = editor->pCursors[0];
2577 para->member.para.next_para->member.para.nFlags |= MEPF_ROWSTART;
2578 ME_CommitCoalescingUndo(editor);
2579 ME_CheckTablesForCorruption(editor);
2580 ME_UpdateRepaint(editor, FALSE);
2581 return TRUE;
2583 } else { /* v1.0 - 3.0 */
2584 ME_DisplayItem *para = cursor.pPara;
2585 if (ME_IsInTable(para))
2587 if (cursor.pRun->member.run.nFlags & MERF_ENDPARA)
2589 if (from == to) {
2590 ME_ContinueCoalescingTransaction(editor);
2591 para = ME_AppendTableRow(editor, para);
2592 editor->pCursors[0].pPara = para;
2593 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2594 editor->pCursors[0].nOffset = 0;
2595 editor->pCursors[1] = editor->pCursors[0];
2596 ME_CommitCoalescingUndo(editor);
2597 ME_UpdateRepaint(editor, FALSE);
2598 return TRUE;
2600 } else {
2601 ME_ContinueCoalescingTransaction(editor);
2602 if (cursor.pRun->member.run.nCharOfs + cursor.nOffset == 0 &&
2603 !ME_IsInTable(para->member.para.prev_para))
2605 /* Insert newline before table */
2606 cursor.pRun = ME_FindItemBack(para, diRun);
2607 if (cursor.pRun) {
2608 editor->pCursors[0].pRun = cursor.pRun;
2609 editor->pCursors[0].pPara = para->member.para.prev_para;
2611 editor->pCursors[0].nOffset = 0;
2612 editor->pCursors[1] = editor->pCursors[0];
2613 ME_InsertTextFromCursor(editor, 0, &endl, 1,
2614 editor->pCursors[0].pRun->member.run.style);
2615 } else {
2616 editor->pCursors[1] = editor->pCursors[0];
2617 para = ME_AppendTableRow(editor, para);
2618 editor->pCursors[0].pPara = para;
2619 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2620 editor->pCursors[0].nOffset = 0;
2621 editor->pCursors[1] = editor->pCursors[0];
2623 ME_CommitCoalescingUndo(editor);
2624 ME_UpdateRepaint(editor, FALSE);
2625 return TRUE;
2630 style = ME_GetInsertStyle(editor, 0);
2632 /* Normally the new eop style is the insert style, however in a list it is copied from the existing
2633 eop style (this prevents the list label style changing when the new eop is inserted).
2634 No extra ref is taken here on eop_style. */
2635 if (para->member.para.fmt.wNumbering)
2636 eop_style = para->member.para.eop_run->style;
2637 else
2638 eop_style = style;
2639 ME_ContinueCoalescingTransaction(editor);
2640 if (shift_is_down)
2641 ME_InsertEndRowFromCursor(editor, 0);
2642 else
2643 if (!editor->bEmulateVersion10)
2644 ME_InsertTextFromCursor(editor, 0, &endl, 1, eop_style);
2645 else
2646 ME_InsertTextFromCursor(editor, 0, endlv10, 2, eop_style);
2647 ME_CommitCoalescingUndo(editor);
2648 SetCursor(NULL);
2650 ME_UpdateSelectionLinkAttribute(editor);
2651 ME_UpdateRepaint(editor, FALSE);
2652 ME_SaveTempStyle(editor, style); /* set the temp insert style for the new para */
2653 ME_ReleaseStyle(style);
2655 return TRUE;
2657 break;
2658 case VK_ESCAPE:
2659 if (editor->bDialogMode && editor->hwndParent)
2660 PostMessageW(editor->hwndParent, WM_CLOSE, 0, 0);
2661 return TRUE;
2662 case VK_TAB:
2663 if (editor->bDialogMode && editor->hwndParent)
2664 SendMessageW(editor->hwndParent, WM_NEXTDLGCTL, shift_is_down, 0);
2665 return TRUE;
2666 case 'A':
2667 if (ctrl_is_down)
2669 handle_EM_EXSETSEL( editor, 0, -1 );
2670 return TRUE;
2672 break;
2673 case 'V':
2674 if (ctrl_is_down)
2675 return paste_special( editor, 0, NULL, FALSE );
2676 break;
2677 case 'C':
2678 case 'X':
2679 if (ctrl_is_down)
2680 return copy_or_cut(editor, nKey == 'X');
2681 break;
2682 case 'Z':
2683 if (ctrl_is_down)
2685 ME_Undo(editor);
2686 return TRUE;
2688 break;
2689 case 'Y':
2690 if (ctrl_is_down)
2692 ME_Redo(editor);
2693 return TRUE;
2695 break;
2697 default:
2698 if (nKey != VK_SHIFT && nKey != VK_CONTROL && nKey && nKey != VK_MENU)
2699 editor->nUDArrowX = -1;
2700 if (ctrl_is_down)
2702 if (nKey == 'W')
2704 CHARFORMAT2W chf;
2705 char buf[2048];
2706 chf.cbSize = sizeof(chf);
2708 ME_GetSelectionCharFormat(editor, &chf);
2709 ME_DumpStyleToBuf(&chf, buf);
2710 MessageBoxA(NULL, buf, "Style dump", MB_OK);
2712 if (nKey == 'Q')
2714 ME_CheckCharOffsets(editor);
2718 return FALSE;
2721 static LRESULT ME_Char(ME_TextEditor *editor, WPARAM charCode,
2722 LPARAM flags, BOOL unicode)
2724 WCHAR wstr;
2726 if (editor->bMouseCaptured)
2727 return 0;
2729 if (unicode)
2730 wstr = (WCHAR)charCode;
2731 else
2733 CHAR charA = charCode;
2734 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &wstr, 1);
2737 if (editor->styleFlags & ES_READONLY) {
2738 MessageBeep(MB_ICONERROR);
2739 return 0; /* FIXME really 0 ? */
2742 if ((unsigned)wstr >= ' ' || wstr == '\t')
2744 ME_Cursor cursor = editor->pCursors[0];
2745 ME_DisplayItem *para = cursor.pPara;
2746 int from, to;
2747 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2748 ME_GetSelectionOfs(editor, &from, &to);
2749 if (wstr == '\t' &&
2750 /* v4.1 allows tabs to be inserted with ctrl key down */
2751 !(ctrl_is_down && !editor->bEmulateVersion10))
2753 ME_DisplayItem *para;
2754 BOOL bSelectedRow = FALSE;
2756 para = cursor.pPara;
2757 if (ME_IsSelection(editor) &&
2758 cursor.pRun->member.run.nCharOfs + cursor.nOffset == 0 &&
2759 to == ME_GetCursorOfs(&editor->pCursors[0]) &&
2760 para->member.para.prev_para->type == diParagraph)
2762 para = para->member.para.prev_para;
2763 bSelectedRow = TRUE;
2765 if (ME_IsInTable(para))
2767 ME_TabPressedInTable(editor, bSelectedRow);
2768 ME_CommitUndo(editor);
2769 return 0;
2771 } else if (!editor->bEmulateVersion10) { /* v4.1 */
2772 if (para->member.para.nFlags & MEPF_ROWEND) {
2773 if (from == to) {
2774 para = para->member.para.next_para;
2775 if (para->member.para.nFlags & MEPF_ROWSTART)
2776 para = para->member.para.next_para;
2777 editor->pCursors[0].pPara = para;
2778 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2779 editor->pCursors[0].nOffset = 0;
2780 editor->pCursors[1] = editor->pCursors[0];
2783 } else { /* v1.0 - 3.0 */
2784 if (ME_IsInTable(cursor.pRun) &&
2785 cursor.pRun->member.run.nFlags & MERF_ENDPARA &&
2786 from == to)
2788 /* Text should not be inserted at the end of the table. */
2789 MessageBeep(-1);
2790 return 0;
2793 /* FIXME maybe it would make sense to call EM_REPLACESEL instead ? */
2794 /* WM_CHAR is restricted to nTextLimit */
2795 if(editor->nTextLimit > ME_GetTextLength(editor) - (to-from))
2797 ME_Style *style = ME_GetInsertStyle(editor, 0);
2798 ME_ContinueCoalescingTransaction(editor);
2799 ME_InsertTextFromCursor(editor, 0, &wstr, 1, style);
2800 ME_ReleaseStyle(style);
2801 ME_CommitCoalescingUndo(editor);
2802 ITextHost_TxSetCursor(editor->texthost, NULL, FALSE);
2805 ME_UpdateSelectionLinkAttribute(editor);
2806 ME_UpdateRepaint(editor, FALSE);
2808 return 0;
2811 /* Process the message and calculate the new click count.
2813 * returns: The click count if it is mouse down event, else returns 0. */
2814 static int ME_CalculateClickCount(ME_TextEditor *editor, UINT msg, WPARAM wParam,
2815 LPARAM lParam)
2817 static int clickNum = 0;
2818 if (msg < WM_MOUSEFIRST || msg > WM_MOUSELAST)
2819 return 0;
2821 if ((msg == WM_LBUTTONDBLCLK) ||
2822 (msg == WM_RBUTTONDBLCLK) ||
2823 (msg == WM_MBUTTONDBLCLK) ||
2824 (msg == WM_XBUTTONDBLCLK))
2826 msg -= (WM_LBUTTONDBLCLK - WM_LBUTTONDOWN);
2829 if ((msg == WM_LBUTTONDOWN) ||
2830 (msg == WM_RBUTTONDOWN) ||
2831 (msg == WM_MBUTTONDOWN) ||
2832 (msg == WM_XBUTTONDOWN))
2834 static MSG prevClickMsg;
2835 MSG clickMsg;
2836 /* Compare the editor instead of the hwnd so that the this
2837 * can still be done for windowless richedit controls. */
2838 clickMsg.hwnd = (HWND)editor;
2839 clickMsg.message = msg;
2840 clickMsg.wParam = wParam;
2841 clickMsg.lParam = lParam;
2842 clickMsg.time = GetMessageTime();
2843 clickMsg.pt.x = (short)LOWORD(lParam);
2844 clickMsg.pt.y = (short)HIWORD(lParam);
2845 if ((clickNum != 0) &&
2846 (clickMsg.message == prevClickMsg.message) &&
2847 (clickMsg.hwnd == prevClickMsg.hwnd) &&
2848 (clickMsg.wParam == prevClickMsg.wParam) &&
2849 (clickMsg.time - prevClickMsg.time < GetDoubleClickTime()) &&
2850 (abs(clickMsg.pt.x - prevClickMsg.pt.x) < GetSystemMetrics(SM_CXDOUBLECLK)/2) &&
2851 (abs(clickMsg.pt.y - prevClickMsg.pt.y) < GetSystemMetrics(SM_CYDOUBLECLK)/2))
2853 clickNum++;
2854 } else {
2855 clickNum = 1;
2857 prevClickMsg = clickMsg;
2858 } else {
2859 return 0;
2861 return clickNum;
2864 static BOOL is_link( ME_Run *run )
2866 return (run->style->fmt.dwMask & CFM_LINK) && (run->style->fmt.dwEffects & CFE_LINK);
2869 static BOOL ME_SetCursor(ME_TextEditor *editor)
2871 ME_Cursor cursor;
2872 POINT pt;
2873 BOOL isExact;
2874 SCROLLBARINFO sbi;
2875 DWORD messagePos = GetMessagePos();
2876 pt.x = (short)LOWORD(messagePos);
2877 pt.y = (short)HIWORD(messagePos);
2879 if (editor->hWnd)
2881 sbi.cbSize = sizeof(sbi);
2882 GetScrollBarInfo(editor->hWnd, OBJID_HSCROLL, &sbi);
2883 if (!(sbi.rgstate[0] & (STATE_SYSTEM_INVISIBLE|STATE_SYSTEM_OFFSCREEN)) &&
2884 PtInRect(&sbi.rcScrollBar, pt))
2886 ITextHost_TxSetCursor(editor->texthost,
2887 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2888 return TRUE;
2890 sbi.cbSize = sizeof(sbi);
2891 GetScrollBarInfo(editor->hWnd, OBJID_VSCROLL, &sbi);
2892 if (!(sbi.rgstate[0] & (STATE_SYSTEM_INVISIBLE|STATE_SYSTEM_OFFSCREEN)) &&
2893 PtInRect(&sbi.rcScrollBar, pt))
2895 ITextHost_TxSetCursor(editor->texthost,
2896 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2897 return TRUE;
2900 ITextHost_TxScreenToClient(editor->texthost, &pt);
2902 if (editor->nSelectionType == stLine && editor->bMouseCaptured) {
2903 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2904 return TRUE;
2906 if (!editor->bEmulateVersion10 /* v4.1 */ &&
2907 pt.y < editor->rcFormat.top &&
2908 pt.x < editor->rcFormat.left)
2910 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2911 return TRUE;
2913 if (pt.y < editor->rcFormat.top || pt.y > editor->rcFormat.bottom)
2915 if (editor->bEmulateVersion10) /* v1.0 - 3.0 */
2916 ITextHost_TxSetCursor(editor->texthost,
2917 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2918 else /* v4.1 */
2919 ITextHost_TxSetCursor(editor->texthost,
2920 LoadCursorW(NULL, (WCHAR*)IDC_IBEAM), TRUE);
2921 return TRUE;
2923 if (pt.x < editor->rcFormat.left)
2925 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2926 return TRUE;
2928 ME_CharFromPos(editor, pt.x, pt.y, &cursor, &isExact);
2929 if (isExact)
2931 ME_Run *run;
2933 run = &cursor.pRun->member.run;
2934 if (is_link( run ))
2936 ITextHost_TxSetCursor(editor->texthost,
2937 LoadCursorW(NULL, (WCHAR*)IDC_HAND),
2938 FALSE);
2939 return TRUE;
2942 if (ME_IsSelection(editor))
2944 int selStart, selEnd;
2945 int offset = ME_GetCursorOfs(&cursor);
2947 ME_GetSelectionOfs(editor, &selStart, &selEnd);
2948 if (selStart <= offset && selEnd >= offset) {
2949 ITextHost_TxSetCursor(editor->texthost,
2950 LoadCursorW(NULL, (WCHAR*)IDC_ARROW),
2951 FALSE);
2952 return TRUE;
2956 ITextHost_TxSetCursor(editor->texthost,
2957 LoadCursorW(NULL, (WCHAR*)IDC_IBEAM), TRUE);
2958 return TRUE;
2961 static void ME_SetDefaultFormatRect(ME_TextEditor *editor)
2963 ITextHost_TxGetClientRect(editor->texthost, &editor->rcFormat);
2964 editor->rcFormat.top += editor->exStyleFlags & WS_EX_CLIENTEDGE ? 1 : 0;
2965 editor->rcFormat.left += 1 + editor->selofs;
2966 editor->rcFormat.right -= 1;
2969 static BOOL ME_ShowContextMenu(ME_TextEditor *editor, int x, int y)
2971 CHARRANGE selrange;
2972 HMENU menu;
2973 int seltype = 0;
2974 if(!editor->lpOleCallback || !editor->hWnd)
2975 return FALSE;
2976 ME_GetSelectionOfs(editor, &selrange.cpMin, &selrange.cpMax);
2977 if(selrange.cpMin == selrange.cpMax)
2978 seltype |= SEL_EMPTY;
2979 else
2981 /* FIXME: Handle objects */
2982 seltype |= SEL_TEXT;
2983 if(selrange.cpMax-selrange.cpMin > 1)
2984 seltype |= SEL_MULTICHAR;
2986 if(SUCCEEDED(IRichEditOleCallback_GetContextMenu(editor->lpOleCallback, seltype, NULL, &selrange, &menu)))
2988 TrackPopupMenu(menu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, x, y, 0, editor->hwndParent, NULL);
2989 DestroyMenu(menu);
2991 return TRUE;
2994 ME_TextEditor *ME_MakeEditor(ITextHost *texthost, BOOL bEmulateVersion10)
2996 ME_TextEditor *ed = ALLOC_OBJ(ME_TextEditor);
2997 int i;
2998 DWORD props;
2999 LONG selbarwidth;
3001 ed->hWnd = NULL;
3002 ed->hwndParent = NULL;
3003 ed->sizeWindow.cx = ed->sizeWindow.cy = 0;
3004 ed->texthost = texthost;
3005 ed->reOle = NULL;
3006 ed->bEmulateVersion10 = bEmulateVersion10;
3007 ed->styleFlags = 0;
3008 ed->exStyleFlags = 0;
3009 ITextHost_TxGetPropertyBits(texthost,
3010 (TXTBIT_RICHTEXT|TXTBIT_MULTILINE|
3011 TXTBIT_READONLY|TXTBIT_USEPASSWORD|
3012 TXTBIT_HIDESELECTION|TXTBIT_SAVESELECTION|
3013 TXTBIT_AUTOWORDSEL|TXTBIT_VERTICAL|
3014 TXTBIT_WORDWRAP|TXTBIT_DISABLEDRAG),
3015 &props);
3016 ITextHost_TxGetScrollBars(texthost, &ed->styleFlags);
3017 ed->styleFlags &= (WS_VSCROLL|WS_HSCROLL|ES_AUTOVSCROLL|
3018 ES_AUTOHSCROLL|ES_DISABLENOSCROLL);
3019 ed->pBuffer = ME_MakeText();
3020 ed->nZoomNumerator = ed->nZoomDenominator = 0;
3021 ed->nAvailWidth = 0; /* wrap to client area */
3022 ME_MakeFirstParagraph(ed);
3023 /* The four cursors are for:
3024 * 0 - The position where the caret is shown
3025 * 1 - The anchored end of the selection (for normal selection)
3026 * 2 & 3 - The anchored start and end respectively for word, line,
3027 * or paragraph selection.
3029 ed->nCursors = 4;
3030 ed->pCursors = ALLOC_N_OBJ(ME_Cursor, ed->nCursors);
3031 ME_SetCursorToStart(ed, &ed->pCursors[0]);
3032 ed->pCursors[1] = ed->pCursors[0];
3033 ed->pCursors[2] = ed->pCursors[0];
3034 ed->pCursors[3] = ed->pCursors[1];
3035 ed->nLastTotalLength = ed->nTotalLength = 0;
3036 ed->nLastTotalWidth = ed->nTotalWidth = 0;
3037 ed->nUDArrowX = -1;
3038 ed->rgbBackColor = -1;
3039 ed->hbrBackground = GetSysColorBrush(COLOR_WINDOW);
3040 ed->bCaretAtEnd = FALSE;
3041 ed->nEventMask = 0;
3042 ed->nModifyStep = 0;
3043 ed->nTextLimit = TEXT_LIMIT_DEFAULT;
3044 list_init( &ed->undo_stack );
3045 list_init( &ed->redo_stack );
3046 ed->nUndoStackSize = 0;
3047 ed->nUndoLimit = STACK_SIZE_DEFAULT;
3048 ed->nUndoMode = umAddToUndo;
3049 ed->nParagraphs = 1;
3050 ed->nLastSelStart = ed->nLastSelEnd = 0;
3051 ed->pLastSelStartPara = ed->pLastSelEndPara = ed->pCursors[0].pPara;
3052 ed->bHideSelection = FALSE;
3053 ed->pfnWordBreak = NULL;
3054 ed->lpOleCallback = NULL;
3055 ed->mode = TM_MULTILEVELUNDO | TM_MULTICODEPAGE;
3056 ed->mode |= (props & TXTBIT_RICHTEXT) ? TM_RICHTEXT : TM_PLAINTEXT;
3057 ed->AutoURLDetect_bEnable = FALSE;
3058 ed->bHaveFocus = FALSE;
3059 ed->bDialogMode = FALSE;
3060 ed->bMouseCaptured = FALSE;
3061 for (i=0; i<HFONT_CACHE_SIZE; i++)
3063 ed->pFontCache[i].nRefs = 0;
3064 ed->pFontCache[i].nAge = 0;
3065 ed->pFontCache[i].hFont = NULL;
3068 ME_CheckCharOffsets(ed);
3069 SetRectEmpty(&ed->rcFormat);
3070 ed->bDefaultFormatRect = TRUE;
3071 ITextHost_TxGetSelectionBarWidth(ed->texthost, &selbarwidth);
3072 if (selbarwidth) {
3073 /* FIXME: Convert selbarwidth from HIMETRIC to pixels */
3074 ed->selofs = SELECTIONBAR_WIDTH;
3075 ed->styleFlags |= ES_SELECTIONBAR;
3076 } else {
3077 ed->selofs = 0;
3079 ed->nSelectionType = stPosition;
3081 ed->cPasswordMask = 0;
3082 if (props & TXTBIT_USEPASSWORD)
3083 ITextHost_TxGetPasswordChar(texthost, &ed->cPasswordMask);
3085 if (props & TXTBIT_AUTOWORDSEL)
3086 ed->styleFlags |= ECO_AUTOWORDSELECTION;
3087 if (props & TXTBIT_MULTILINE) {
3088 ed->styleFlags |= ES_MULTILINE;
3089 ed->bWordWrap = (props & TXTBIT_WORDWRAP) != 0;
3090 } else {
3091 ed->bWordWrap = FALSE;
3093 if (props & TXTBIT_READONLY)
3094 ed->styleFlags |= ES_READONLY;
3095 if (!(props & TXTBIT_HIDESELECTION))
3096 ed->styleFlags |= ES_NOHIDESEL;
3097 if (props & TXTBIT_SAVESELECTION)
3098 ed->styleFlags |= ES_SAVESEL;
3099 if (props & TXTBIT_VERTICAL)
3100 ed->styleFlags |= ES_VERTICAL;
3101 if (props & TXTBIT_DISABLEDRAG)
3102 ed->styleFlags |= ES_NOOLEDRAGDROP;
3104 ed->notified_cr.cpMin = ed->notified_cr.cpMax = 0;
3106 /* Default scrollbar information */
3107 ed->vert_si.cbSize = sizeof(SCROLLINFO);
3108 ed->vert_si.nMin = 0;
3109 ed->vert_si.nMax = 0;
3110 ed->vert_si.nPage = 0;
3111 ed->vert_si.nPos = 0;
3113 ed->horz_si.cbSize = sizeof(SCROLLINFO);
3114 ed->horz_si.nMin = 0;
3115 ed->horz_si.nMax = 0;
3116 ed->horz_si.nPage = 0;
3117 ed->horz_si.nPos = 0;
3119 ed->wheel_remain = 0;
3121 list_init( &ed->style_list );
3122 OleInitialize(NULL);
3124 return ed;
3127 void ME_DestroyEditor(ME_TextEditor *editor)
3129 ME_DisplayItem *pFirst = editor->pBuffer->pFirst;
3130 ME_DisplayItem *p = pFirst, *pNext = NULL;
3131 ME_Style *s, *cursor2;
3132 int i;
3134 ME_ClearTempStyle(editor);
3135 ME_EmptyUndoStack(editor);
3136 while(p) {
3137 pNext = p->next;
3138 ME_DestroyDisplayItem(p);
3139 p = pNext;
3142 LIST_FOR_EACH_ENTRY_SAFE( s, cursor2, &editor->style_list, ME_Style, entry )
3143 ME_DestroyStyle( s );
3145 ME_ReleaseStyle(editor->pBuffer->pDefaultStyle);
3146 for (i=0; i<HFONT_CACHE_SIZE; i++)
3148 if (editor->pFontCache[i].hFont)
3149 DeleteObject(editor->pFontCache[i].hFont);
3151 if (editor->rgbBackColor != -1)
3152 DeleteObject(editor->hbrBackground);
3153 if(editor->lpOleCallback)
3154 IRichEditOleCallback_Release(editor->lpOleCallback);
3155 ITextHost_Release(editor->texthost);
3156 if (editor->reOle)
3158 IRichEditOle_Release(editor->reOle);
3159 editor->reOle = NULL;
3161 OleUninitialize();
3163 FREE_OBJ(editor->pBuffer);
3164 FREE_OBJ(editor->pCursors);
3166 FREE_OBJ(editor);
3169 BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
3171 TRACE("\n");
3172 switch (fdwReason)
3174 case DLL_PROCESS_ATTACH:
3175 DisableThreadLibraryCalls(hinstDLL);
3176 me_heap = HeapCreate (0, 0x10000, 0);
3177 if (!ME_RegisterEditorClass(hinstDLL)) return FALSE;
3178 hLeft = LoadCursorW(hinstDLL, MAKEINTRESOURCEW(OCR_REVERSE));
3179 LookupInit();
3180 break;
3182 case DLL_PROCESS_DETACH:
3183 if (lpvReserved) break;
3184 UnregisterClassW(RICHEDIT_CLASS20W, 0);
3185 UnregisterClassW(MSFTEDIT_CLASS, 0);
3186 UnregisterClassA(RICHEDIT_CLASS20A, 0);
3187 UnregisterClassA("RichEdit50A", 0);
3188 if (ME_ListBoxRegistered)
3189 UnregisterClassW(REListBox20W, 0);
3190 if (ME_ComboBoxRegistered)
3191 UnregisterClassW(REComboBox20W, 0);
3192 LookupCleanup();
3193 HeapDestroy (me_heap);
3194 release_typelib();
3195 break;
3197 return TRUE;
3200 static inline int get_default_line_height( ME_TextEditor *editor )
3202 int height = 0;
3204 if (editor->pBuffer && editor->pBuffer->pDefaultStyle)
3205 height = editor->pBuffer->pDefaultStyle->tm.tmHeight;
3206 if (height <= 0) height = 24;
3208 return height;
3211 static inline int calc_wheel_change( int *remain, int amount_per_click )
3213 int change = amount_per_click * (float)*remain / WHEEL_DELTA;
3214 *remain -= WHEEL_DELTA * change / amount_per_click;
3215 return change;
3218 static const char * const edit_messages[] = {
3219 "EM_GETSEL",
3220 "EM_SETSEL",
3221 "EM_GETRECT",
3222 "EM_SETRECT",
3223 "EM_SETRECTNP",
3224 "EM_SCROLL",
3225 "EM_LINESCROLL",
3226 "EM_SCROLLCARET",
3227 "EM_GETMODIFY",
3228 "EM_SETMODIFY",
3229 "EM_GETLINECOUNT",
3230 "EM_LINEINDEX",
3231 "EM_SETHANDLE",
3232 "EM_GETHANDLE",
3233 "EM_GETTHUMB",
3234 "EM_UNKNOWN_BF",
3235 "EM_UNKNOWN_C0",
3236 "EM_LINELENGTH",
3237 "EM_REPLACESEL",
3238 "EM_UNKNOWN_C3",
3239 "EM_GETLINE",
3240 "EM_LIMITTEXT",
3241 "EM_CANUNDO",
3242 "EM_UNDO",
3243 "EM_FMTLINES",
3244 "EM_LINEFROMCHAR",
3245 "EM_UNKNOWN_CA",
3246 "EM_SETTABSTOPS",
3247 "EM_SETPASSWORDCHAR",
3248 "EM_EMPTYUNDOBUFFER",
3249 "EM_GETFIRSTVISIBLELINE",
3250 "EM_SETREADONLY",
3251 "EM_SETWORDBREAKPROC",
3252 "EM_GETWORDBREAKPROC",
3253 "EM_GETPASSWORDCHAR",
3254 "EM_SETMARGINS",
3255 "EM_GETMARGINS",
3256 "EM_GETLIMITTEXT",
3257 "EM_POSFROMCHAR",
3258 "EM_CHARFROMPOS",
3259 "EM_SETIMESTATUS",
3260 "EM_GETIMESTATUS"
3263 static const char * const richedit_messages[] = {
3264 "EM_CANPASTE",
3265 "EM_DISPLAYBAND",
3266 "EM_EXGETSEL",
3267 "EM_EXLIMITTEXT",
3268 "EM_EXLINEFROMCHAR",
3269 "EM_EXSETSEL",
3270 "EM_FINDTEXT",
3271 "EM_FORMATRANGE",
3272 "EM_GETCHARFORMAT",
3273 "EM_GETEVENTMASK",
3274 "EM_GETOLEINTERFACE",
3275 "EM_GETPARAFORMAT",
3276 "EM_GETSELTEXT",
3277 "EM_HIDESELECTION",
3278 "EM_PASTESPECIAL",
3279 "EM_REQUESTRESIZE",
3280 "EM_SELECTIONTYPE",
3281 "EM_SETBKGNDCOLOR",
3282 "EM_SETCHARFORMAT",
3283 "EM_SETEVENTMASK",
3284 "EM_SETOLECALLBACK",
3285 "EM_SETPARAFORMAT",
3286 "EM_SETTARGETDEVICE",
3287 "EM_STREAMIN",
3288 "EM_STREAMOUT",
3289 "EM_GETTEXTRANGE",
3290 "EM_FINDWORDBREAK",
3291 "EM_SETOPTIONS",
3292 "EM_GETOPTIONS",
3293 "EM_FINDTEXTEX",
3294 "EM_GETWORDBREAKPROCEX",
3295 "EM_SETWORDBREAKPROCEX",
3296 "EM_SETUNDOLIMIT",
3297 "EM_UNKNOWN_USER_83",
3298 "EM_REDO",
3299 "EM_CANREDO",
3300 "EM_GETUNDONAME",
3301 "EM_GETREDONAME",
3302 "EM_STOPGROUPTYPING",
3303 "EM_SETTEXTMODE",
3304 "EM_GETTEXTMODE",
3305 "EM_AUTOURLDETECT",
3306 "EM_GETAUTOURLDETECT",
3307 "EM_SETPALETTE",
3308 "EM_GETTEXTEX",
3309 "EM_GETTEXTLENGTHEX",
3310 "EM_SHOWSCROLLBAR",
3311 "EM_SETTEXTEX",
3312 "EM_UNKNOWN_USER_98",
3313 "EM_UNKNOWN_USER_99",
3314 "EM_SETPUNCTUATION",
3315 "EM_GETPUNCTUATION",
3316 "EM_SETWORDWRAPMODE",
3317 "EM_GETWORDWRAPMODE",
3318 "EM_SETIMECOLOR",
3319 "EM_GETIMECOLOR",
3320 "EM_SETIMEOPTIONS",
3321 "EM_GETIMEOPTIONS",
3322 "EM_CONVPOSITION",
3323 "EM_UNKNOWN_USER_109",
3324 "EM_UNKNOWN_USER_110",
3325 "EM_UNKNOWN_USER_111",
3326 "EM_UNKNOWN_USER_112",
3327 "EM_UNKNOWN_USER_113",
3328 "EM_UNKNOWN_USER_114",
3329 "EM_UNKNOWN_USER_115",
3330 "EM_UNKNOWN_USER_116",
3331 "EM_UNKNOWN_USER_117",
3332 "EM_UNKNOWN_USER_118",
3333 "EM_UNKNOWN_USER_119",
3334 "EM_SETLANGOPTIONS",
3335 "EM_GETLANGOPTIONS",
3336 "EM_GETIMECOMPMODE",
3337 "EM_FINDTEXTW",
3338 "EM_FINDTEXTEXW",
3339 "EM_RECONVERSION",
3340 "EM_SETIMEMODEBIAS",
3341 "EM_GETIMEMODEBIAS"
3344 static const char *
3345 get_msg_name(UINT msg)
3347 if (msg >= EM_GETSEL && msg <= EM_CHARFROMPOS)
3348 return edit_messages[msg - EM_GETSEL];
3349 if (msg >= EM_CANPASTE && msg <= EM_GETIMEMODEBIAS)
3350 return richedit_messages[msg - EM_CANPASTE];
3351 return "";
3354 static void ME_LinkNotify(ME_TextEditor *editor, UINT msg, WPARAM wParam, LPARAM lParam)
3356 int x,y;
3357 BOOL isExact;
3358 ME_Cursor cursor; /* The start of the clicked text. */
3360 ENLINK info;
3361 x = (short)LOWORD(lParam);
3362 y = (short)HIWORD(lParam);
3363 ME_CharFromPos(editor, x, y, &cursor, &isExact);
3364 if (!isExact) return;
3366 if (is_link( &cursor.pRun->member.run ))
3367 { /* The clicked run has CFE_LINK set */
3368 ME_DisplayItem *di;
3370 info.nmhdr.hwndFrom = NULL;
3371 info.nmhdr.idFrom = 0;
3372 info.nmhdr.code = EN_LINK;
3373 info.msg = msg;
3374 info.wParam = wParam;
3375 info.lParam = lParam;
3376 cursor.nOffset = 0;
3378 /* find the first contiguous run with CFE_LINK set */
3379 info.chrg.cpMin = ME_GetCursorOfs(&cursor);
3380 di = cursor.pRun;
3381 while (ME_PrevRun( NULL, &di, FALSE ) && is_link( &di->member.run ))
3382 info.chrg.cpMin -= di->member.run.len;
3384 /* find the last contiguous run with CFE_LINK set */
3385 info.chrg.cpMax = ME_GetCursorOfs(&cursor) + cursor.pRun->member.run.len;
3386 di = cursor.pRun;
3387 while (ME_NextRun( NULL, &di, FALSE ) && is_link( &di->member.run ))
3388 info.chrg.cpMax += di->member.run.len;
3390 ITextHost_TxNotify(editor->texthost, info.nmhdr.code, &info);
3394 void ME_ReplaceSel(ME_TextEditor *editor, BOOL can_undo, const WCHAR *str, int len)
3396 int from, to, nStartCursor;
3397 ME_Style *style;
3399 nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3400 style = ME_GetSelectionInsertStyle(editor);
3401 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to-from, FALSE);
3402 ME_InsertTextFromCursor(editor, 0, str, len, style);
3403 ME_ReleaseStyle(style);
3404 /* drop temporary style if line end */
3406 * FIXME question: does abc\n mean: put abc,
3407 * clear temp style, put \n? (would require a change)
3409 if (len>0 && str[len-1] == '\n')
3410 ME_ClearTempStyle(editor);
3411 ME_CommitUndo(editor);
3412 ME_UpdateSelectionLinkAttribute(editor);
3413 if (!can_undo)
3414 ME_EmptyUndoStack(editor);
3415 ME_UpdateRepaint(editor, FALSE);
3418 static void ME_SetText(ME_TextEditor *editor, void *text, BOOL unicode)
3420 LONG codepage = unicode ? CP_UNICODE : CP_ACP;
3421 int textLen;
3423 LPWSTR wszText = ME_ToUnicode(codepage, text, &textLen);
3424 ME_InsertTextFromCursor(editor, 0, wszText, textLen, editor->pBuffer->pDefaultStyle);
3425 ME_EndToUnicode(codepage, wszText);
3428 static LRESULT ME_WmCreate(ME_TextEditor *editor, LPARAM lParam, BOOL unicode)
3430 CREATESTRUCTW *createW = (CREATESTRUCTW*)lParam;
3431 CREATESTRUCTA *createA = (CREATESTRUCTA*)lParam;
3432 void *text = NULL;
3433 INT max;
3435 if (lParam)
3436 text = unicode ? (void*)createW->lpszName : (void*)createA->lpszName;
3438 ME_SetDefaultFormatRect(editor);
3440 max = (editor->styleFlags & ES_DISABLENOSCROLL) ? 1 : 0;
3441 if (~editor->styleFlags & ES_DISABLENOSCROLL || editor->styleFlags & WS_VSCROLL)
3442 ITextHost_TxSetScrollRange(editor->texthost, SB_VERT, 0, max, TRUE);
3444 if (~editor->styleFlags & ES_DISABLENOSCROLL || editor->styleFlags & WS_HSCROLL)
3445 ITextHost_TxSetScrollRange(editor->texthost, SB_HORZ, 0, max, TRUE);
3447 if (editor->styleFlags & ES_DISABLENOSCROLL)
3449 if (editor->styleFlags & WS_VSCROLL)
3451 ITextHost_TxEnableScrollBar(editor->texthost, SB_VERT, ESB_DISABLE_BOTH);
3452 ITextHost_TxShowScrollBar(editor->texthost, SB_VERT, TRUE);
3454 if (editor->styleFlags & WS_HSCROLL)
3456 ITextHost_TxEnableScrollBar(editor->texthost, SB_HORZ, ESB_DISABLE_BOTH);
3457 ITextHost_TxShowScrollBar(editor->texthost, SB_HORZ, TRUE);
3461 if (text)
3463 ME_SetText(editor, text, unicode);
3464 ME_SetCursorToStart(editor, &editor->pCursors[0]);
3465 ME_SetCursorToStart(editor, &editor->pCursors[1]);
3468 ME_CommitUndo(editor);
3469 ME_WrapMarkedParagraphs(editor);
3470 ME_MoveCaret(editor);
3471 return 0;
3475 #define UNSUPPORTED_MSG(e) \
3476 case e: \
3477 FIXME(#e ": stub\n"); \
3478 *phresult = S_FALSE; \
3479 return 0;
3481 /* Handle messages for windowless and windowed richedit controls.
3483 * The LRESULT that is returned is a return value for window procs,
3484 * and the phresult parameter is the COM return code needed by the
3485 * text services interface. */
3486 LRESULT ME_HandleMessage(ME_TextEditor *editor, UINT msg, WPARAM wParam,
3487 LPARAM lParam, BOOL unicode, HRESULT* phresult)
3489 *phresult = S_OK;
3491 switch(msg) {
3493 UNSUPPORTED_MSG(EM_DISPLAYBAND)
3494 UNSUPPORTED_MSG(EM_FINDWORDBREAK)
3495 UNSUPPORTED_MSG(EM_FMTLINES)
3496 UNSUPPORTED_MSG(EM_FORMATRANGE)
3497 UNSUPPORTED_MSG(EM_GETBIDIOPTIONS)
3498 UNSUPPORTED_MSG(EM_GETEDITSTYLE)
3499 UNSUPPORTED_MSG(EM_GETIMECOMPMODE)
3500 UNSUPPORTED_MSG(EM_GETIMESTATUS)
3501 UNSUPPORTED_MSG(EM_SETIMESTATUS)
3502 UNSUPPORTED_MSG(EM_GETLANGOPTIONS)
3503 UNSUPPORTED_MSG(EM_GETREDONAME)
3504 UNSUPPORTED_MSG(EM_GETTYPOGRAPHYOPTIONS)
3505 UNSUPPORTED_MSG(EM_GETUNDONAME)
3506 UNSUPPORTED_MSG(EM_GETWORDBREAKPROCEX)
3507 UNSUPPORTED_MSG(EM_SELECTIONTYPE)
3508 UNSUPPORTED_MSG(EM_SETBIDIOPTIONS)
3509 UNSUPPORTED_MSG(EM_SETEDITSTYLE)
3510 UNSUPPORTED_MSG(EM_SETLANGOPTIONS)
3511 UNSUPPORTED_MSG(EM_SETMARGINS)
3512 UNSUPPORTED_MSG(EM_SETPALETTE)
3513 UNSUPPORTED_MSG(EM_SETTABSTOPS)
3514 UNSUPPORTED_MSG(EM_SETTYPOGRAPHYOPTIONS)
3515 UNSUPPORTED_MSG(EM_SETWORDBREAKPROCEX)
3517 /* Messages specific to Richedit controls */
3519 case EM_STREAMIN:
3520 return ME_StreamIn(editor, wParam, (EDITSTREAM*)lParam, TRUE);
3521 case EM_STREAMOUT:
3522 return ME_StreamOut(editor, wParam, (EDITSTREAM *)lParam);
3523 case WM_GETDLGCODE:
3525 UINT code = DLGC_WANTCHARS|DLGC_WANTTAB|DLGC_WANTARROWS;
3527 if (lParam)
3528 editor->bDialogMode = TRUE;
3529 if (editor->styleFlags & ES_MULTILINE)
3530 code |= DLGC_WANTMESSAGE;
3531 if (!(editor->styleFlags & ES_SAVESEL))
3532 code |= DLGC_HASSETSEL;
3533 return code;
3535 case EM_EMPTYUNDOBUFFER:
3536 ME_EmptyUndoStack(editor);
3537 return 0;
3538 case EM_GETSEL:
3540 /* Note: wParam/lParam can be NULL */
3541 UINT from, to;
3542 PUINT pfrom = wParam ? (PUINT)wParam : &from;
3543 PUINT pto = lParam ? (PUINT)lParam : &to;
3544 ME_GetSelectionOfs(editor, (int *)pfrom, (int *)pto);
3545 if ((*pfrom|*pto) & 0xFFFF0000)
3546 return -1;
3547 return MAKELONG(*pfrom,*pto);
3549 case EM_EXGETSEL:
3551 CHARRANGE *pRange = (CHARRANGE *)lParam;
3552 ME_GetSelectionOfs(editor, &pRange->cpMin, &pRange->cpMax);
3553 TRACE("EM_EXGETSEL = (%d,%d)\n", pRange->cpMin, pRange->cpMax);
3554 return 0;
3556 case EM_SETUNDOLIMIT:
3558 if ((int)wParam < 0)
3559 editor->nUndoLimit = STACK_SIZE_DEFAULT;
3560 else
3561 editor->nUndoLimit = min(wParam, STACK_SIZE_MAX);
3562 /* Setting a max stack size keeps wine from getting killed
3563 for hogging memory. Windows allocates all this memory at once, so
3564 no program would realistically set a value above our maximum. */
3565 return editor->nUndoLimit;
3567 case EM_CANUNDO:
3568 return !list_empty( &editor->undo_stack );
3569 case EM_CANREDO:
3570 return !list_empty( &editor->redo_stack );
3571 case WM_UNDO: /* FIXME: actually not the same */
3572 case EM_UNDO:
3573 return ME_Undo(editor);
3574 case EM_REDO:
3575 return ME_Redo(editor);
3576 case EM_GETOPTIONS:
3578 /* these flags are equivalent to the ES_* counterparts */
3579 DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
3580 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN | ECO_SELECTIONBAR;
3581 DWORD settings = editor->styleFlags & mask;
3583 return settings;
3585 case EM_SETFONTSIZE:
3587 CHARFORMAT2W cf;
3588 LONG tmp_size, size;
3589 BOOL is_increase = ((LONG)wParam > 0);
3591 if (editor->mode & TM_PLAINTEXT)
3592 return FALSE;
3594 cf.cbSize = sizeof(cf);
3595 cf.dwMask = CFM_SIZE;
3596 ME_GetSelectionCharFormat(editor, &cf);
3597 tmp_size = (cf.yHeight / 20) + wParam;
3599 if (tmp_size <= 1)
3600 size = 1;
3601 else if (tmp_size > 12 && tmp_size < 28 && tmp_size % 2)
3602 size = tmp_size + (is_increase ? 1 : -1);
3603 else if (tmp_size > 28 && tmp_size < 36)
3604 size = is_increase ? 36 : 28;
3605 else if (tmp_size > 36 && tmp_size < 48)
3606 size = is_increase ? 48 : 36;
3607 else if (tmp_size > 48 && tmp_size < 72)
3608 size = is_increase ? 72 : 48;
3609 else if (tmp_size > 72 && tmp_size < 80)
3610 size = is_increase ? 80 : 72;
3611 else if (tmp_size > 80 && tmp_size < 1638)
3612 size = 10 * (is_increase ? (tmp_size / 10 + 1) : (tmp_size / 10));
3613 else if (tmp_size >= 1638)
3614 size = 1638;
3615 else
3616 size = tmp_size;
3618 cf.yHeight = size * 20; /* convert twips to points */
3619 ME_SetSelectionCharFormat(editor, &cf);
3620 ME_CommitUndo(editor);
3621 ME_WrapMarkedParagraphs(editor);
3622 ME_UpdateScrollBar(editor);
3623 ME_Repaint(editor);
3625 return TRUE;
3627 case EM_SETOPTIONS:
3629 /* these flags are equivalent to ES_* counterparts, except for
3630 * ECO_AUTOWORDSELECTION that doesn't have an ES_* counterpart,
3631 * but is still stored in editor->styleFlags. */
3632 const DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
3633 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN |
3634 ECO_SELECTIONBAR | ECO_AUTOWORDSELECTION;
3635 DWORD settings = mask & editor->styleFlags;
3636 DWORD oldSettings = settings;
3637 DWORD changedSettings;
3639 switch(wParam)
3641 case ECOOP_SET:
3642 settings = lParam;
3643 break;
3644 case ECOOP_OR:
3645 settings |= lParam;
3646 break;
3647 case ECOOP_AND:
3648 settings &= lParam;
3649 break;
3650 case ECOOP_XOR:
3651 settings ^= lParam;
3653 changedSettings = oldSettings ^ settings;
3655 if (changedSettings) {
3656 editor->styleFlags = (editor->styleFlags & ~mask) | (settings & mask);
3658 if (changedSettings & ECO_SELECTIONBAR)
3660 ITextHost_TxInvalidateRect(editor->texthost, &editor->rcFormat, TRUE);
3661 if (settings & ECO_SELECTIONBAR) {
3662 assert(!editor->selofs);
3663 editor->selofs = SELECTIONBAR_WIDTH;
3664 editor->rcFormat.left += editor->selofs;
3665 } else {
3666 editor->rcFormat.left -= editor->selofs;
3667 editor->selofs = 0;
3669 ME_RewrapRepaint(editor);
3672 if ((changedSettings & settings & ES_NOHIDESEL) && !editor->bHaveFocus)
3673 ME_InvalidateSelection( editor );
3675 if (changedSettings & settings & ECO_VERTICAL)
3676 FIXME("ECO_VERTICAL not implemented yet!\n");
3677 if (changedSettings & settings & ECO_AUTOHSCROLL)
3678 FIXME("ECO_AUTOHSCROLL not implemented yet!\n");
3679 if (changedSettings & settings & ECO_AUTOVSCROLL)
3680 FIXME("ECO_AUTOVSCROLL not implemented yet!\n");
3681 if (changedSettings & settings & ECO_WANTRETURN)
3682 FIXME("ECO_WANTRETURN not implemented yet!\n");
3683 if (changedSettings & settings & ECO_AUTOWORDSELECTION)
3684 FIXME("ECO_AUTOWORDSELECTION not implemented yet!\n");
3687 return settings;
3689 case EM_SETSEL:
3691 return handle_EM_EXSETSEL( editor, wParam, lParam );
3693 case EM_SETSCROLLPOS:
3695 POINT *point = (POINT *)lParam;
3696 ME_ScrollAbs(editor, point->x, point->y);
3697 return 0;
3699 case EM_AUTOURLDETECT:
3701 if (wParam==1 || wParam ==0)
3703 editor->AutoURLDetect_bEnable = (BOOL)wParam;
3704 return 0;
3706 return E_INVALIDARG;
3708 case EM_GETAUTOURLDETECT:
3710 return editor->AutoURLDetect_bEnable;
3712 case EM_EXSETSEL:
3714 CHARRANGE range = *(CHARRANGE *)lParam;
3716 return handle_EM_EXSETSEL( editor, range.cpMin, range.cpMax );
3718 case EM_SHOWSCROLLBAR:
3720 DWORD flags;
3722 switch (wParam)
3724 case SB_HORZ:
3725 flags = WS_HSCROLL;
3726 break;
3727 case SB_VERT:
3728 flags = WS_VSCROLL;
3729 break;
3730 case SB_BOTH:
3731 flags = WS_HSCROLL|WS_VSCROLL;
3732 break;
3733 default:
3734 return 0;
3737 if (lParam) {
3738 editor->styleFlags |= flags;
3739 if (flags & WS_HSCROLL)
3740 ITextHost_TxShowScrollBar(editor->texthost, SB_HORZ,
3741 editor->nTotalWidth > editor->sizeWindow.cx);
3742 if (flags & WS_VSCROLL)
3743 ITextHost_TxShowScrollBar(editor->texthost, SB_VERT,
3744 editor->nTotalLength > editor->sizeWindow.cy);
3745 } else {
3746 editor->styleFlags &= ~flags;
3747 ITextHost_TxShowScrollBar(editor->texthost, wParam, FALSE);
3749 return 0;
3751 case EM_SETTEXTEX:
3753 LPWSTR wszText;
3754 SETTEXTEX *pStruct = (SETTEXTEX *)wParam;
3755 int from, to, len;
3756 ME_Style *style;
3757 BOOL bRtf, bUnicode, bSelection, bUTF8;
3758 int oldModify = editor->nModifyStep;
3759 static const char utf8_bom[] = {0xef, 0xbb, 0xbf};
3761 if (!pStruct) return 0;
3763 /* If we detect ascii rtf at the start of the string,
3764 * we know it isn't unicode. */
3765 bRtf = (lParam && (!strncmp((char *)lParam, "{\\rtf", 5) ||
3766 !strncmp((char *)lParam, "{\\urtf", 6)));
3767 bUnicode = !bRtf && pStruct->codepage == CP_UNICODE;
3768 bUTF8 = (lParam && (!strncmp((char *)lParam, utf8_bom, 3)));
3770 TRACE("EM_SETTEXTEX - %s, flags %d, cp %d\n",
3771 bUnicode ? debugstr_w((LPCWSTR)lParam) : debugstr_a((LPCSTR)lParam),
3772 pStruct->flags, pStruct->codepage);
3774 bSelection = (pStruct->flags & ST_SELECTION) != 0;
3775 if (bSelection) {
3776 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3777 style = ME_GetSelectionInsertStyle(editor);
3778 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to - from, FALSE);
3779 } else {
3780 ME_Cursor start;
3781 ME_SetCursorToStart(editor, &start);
3782 ME_InternalDeleteText(editor, &start, ME_GetTextLength(editor), FALSE);
3783 style = editor->pBuffer->pDefaultStyle;
3786 if (bRtf) {
3787 ME_StreamInRTFString(editor, bSelection, (char *)lParam);
3788 if (bSelection) {
3789 /* FIXME: The length returned doesn't include the rtf control
3790 * characters, only the actual text. */
3791 len = lParam ? strlen((char *)lParam) : 0;
3793 } else {
3794 if (bUTF8 && !bUnicode) {
3795 wszText = ME_ToUnicode(CP_UTF8, (void *)(lParam+3), &len);
3796 ME_InsertTextFromCursor(editor, 0, wszText, len, style);
3797 ME_EndToUnicode(CP_UTF8, wszText);
3798 } else {
3799 wszText = ME_ToUnicode(pStruct->codepage, (void *)lParam, &len);
3800 ME_InsertTextFromCursor(editor, 0, wszText, len, style);
3801 ME_EndToUnicode(pStruct->codepage, wszText);
3805 if (bSelection) {
3806 ME_ReleaseStyle(style);
3807 ME_UpdateSelectionLinkAttribute(editor);
3808 } else {
3809 ME_Cursor cursor;
3810 len = 1;
3811 ME_SetCursorToStart(editor, &cursor);
3812 ME_UpdateLinkAttribute(editor, &cursor, INT_MAX);
3814 ME_CommitUndo(editor);
3815 if (!(pStruct->flags & ST_KEEPUNDO))
3817 editor->nModifyStep = oldModify;
3818 ME_EmptyUndoStack(editor);
3820 ME_UpdateRepaint(editor, FALSE);
3821 return len;
3823 case EM_SETBKGNDCOLOR:
3825 LRESULT lColor;
3826 if (editor->rgbBackColor != -1) {
3827 DeleteObject(editor->hbrBackground);
3828 lColor = editor->rgbBackColor;
3830 else lColor = ITextHost_TxGetSysColor(editor->texthost, COLOR_WINDOW);
3832 if (wParam)
3834 editor->rgbBackColor = -1;
3835 editor->hbrBackground = GetSysColorBrush(COLOR_WINDOW);
3837 else
3839 editor->rgbBackColor = lParam;
3840 editor->hbrBackground = CreateSolidBrush(editor->rgbBackColor);
3842 ITextHost_TxInvalidateRect(editor->texthost, NULL, TRUE);
3843 return lColor;
3845 case EM_GETMODIFY:
3846 return editor->nModifyStep == 0 ? 0 : -1;
3847 case EM_SETMODIFY:
3849 if (wParam)
3850 editor->nModifyStep = 1;
3851 else
3852 editor->nModifyStep = 0;
3854 return 0;
3856 case EM_SETREADONLY:
3858 if (wParam)
3859 editor->styleFlags |= ES_READONLY;
3860 else
3861 editor->styleFlags &= ~ES_READONLY;
3862 return 1;
3864 case EM_SETEVENTMASK:
3866 DWORD nOldMask = editor->nEventMask;
3868 editor->nEventMask = lParam;
3869 return nOldMask;
3871 case EM_GETEVENTMASK:
3872 return editor->nEventMask;
3873 case EM_SETCHARFORMAT:
3875 CHARFORMAT2W buf, *p;
3876 BOOL bRepaint = TRUE;
3877 p = ME_ToCF2W(&buf, (CHARFORMAT2W *)lParam);
3878 if (p == NULL) return 0;
3879 if (wParam & SCF_ALL) {
3880 if (editor->mode & TM_PLAINTEXT) {
3881 ME_SetDefaultCharFormat(editor, p);
3882 } else {
3883 ME_Cursor start;
3884 ME_SetCursorToStart(editor, &start);
3885 ME_SetCharFormat(editor, &start, NULL, p);
3886 editor->nModifyStep = 1;
3888 } else if (wParam & SCF_SELECTION) {
3889 if (editor->mode & TM_PLAINTEXT)
3890 return 0;
3891 if (wParam & SCF_WORD) {
3892 ME_Cursor start;
3893 ME_Cursor end = editor->pCursors[0];
3894 ME_MoveCursorWords(editor, &end, +1);
3895 start = end;
3896 ME_MoveCursorWords(editor, &start, -1);
3897 ME_SetCharFormat(editor, &start, &end, p);
3899 bRepaint = ME_IsSelection(editor);
3900 ME_SetSelectionCharFormat(editor, p);
3901 if (bRepaint) editor->nModifyStep = 1;
3902 } else { /* SCF_DEFAULT */
3903 ME_SetDefaultCharFormat(editor, p);
3905 ME_CommitUndo(editor);
3906 if (bRepaint)
3908 ME_WrapMarkedParagraphs(editor);
3909 ME_UpdateScrollBar(editor);
3910 ME_Repaint(editor);
3912 return 1;
3914 case EM_GETCHARFORMAT:
3916 CHARFORMAT2W tmp, *dst = (CHARFORMAT2W *)lParam;
3917 if (dst->cbSize != sizeof(CHARFORMATA) &&
3918 dst->cbSize != sizeof(CHARFORMATW) &&
3919 dst->cbSize != sizeof(CHARFORMAT2A) &&
3920 dst->cbSize != sizeof(CHARFORMAT2W))
3921 return 0;
3922 tmp.cbSize = sizeof(tmp);
3923 if (!wParam)
3924 ME_GetDefaultCharFormat(editor, &tmp);
3925 else
3926 ME_GetSelectionCharFormat(editor, &tmp);
3927 ME_CopyToCFAny(dst, &tmp);
3928 return tmp.dwMask;
3930 case EM_SETPARAFORMAT:
3932 BOOL result = ME_SetSelectionParaFormat(editor, (PARAFORMAT2 *)lParam);
3933 ME_WrapMarkedParagraphs(editor);
3934 ME_UpdateScrollBar(editor);
3935 ME_Repaint(editor);
3936 ME_CommitUndo(editor);
3937 return result;
3939 case EM_GETPARAFORMAT:
3940 ME_GetSelectionParaFormat(editor, (PARAFORMAT2 *)lParam);
3941 return ((PARAFORMAT2 *)lParam)->dwMask;
3942 case EM_GETFIRSTVISIBLELINE:
3944 ME_DisplayItem *p = editor->pBuffer->pFirst;
3945 int y = editor->vert_si.nPos;
3946 int ypara = 0;
3947 int count = 0;
3948 int ystart, yend;
3949 while(p) {
3950 p = ME_FindItemFwd(p, diStartRowOrParagraphOrEnd);
3951 if (p->type == diTextEnd)
3952 break;
3953 if (p->type == diParagraph) {
3954 ypara = p->member.para.pt.y;
3955 continue;
3957 ystart = ypara + p->member.row.pt.y;
3958 yend = ystart + p->member.row.nHeight;
3959 if (y < yend) {
3960 break;
3962 count++;
3964 return count;
3966 case EM_HIDESELECTION:
3968 editor->bHideSelection = (wParam != 0);
3969 ME_InvalidateSelection(editor);
3970 return 0;
3972 case EM_LINESCROLL:
3974 if (!(editor->styleFlags & ES_MULTILINE))
3975 return FALSE;
3976 ME_ScrollDown( editor, lParam * get_default_line_height( editor ) );
3977 return TRUE;
3979 case WM_CLEAR:
3981 int from, to;
3982 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3983 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to-from, FALSE);
3984 ME_CommitUndo(editor);
3985 ME_UpdateRepaint(editor, TRUE);
3986 return 0;
3988 case EM_REPLACESEL:
3990 int len = 0;
3991 LONG codepage = unicode ? CP_UNICODE : CP_ACP;
3992 LPWSTR wszText = ME_ToUnicode(codepage, (void *)lParam, &len);
3994 TRACE("EM_REPLACESEL - %s\n", debugstr_w(wszText));
3996 ME_ReplaceSel(editor, !!wParam, wszText, len);
3997 ME_EndToUnicode(codepage, wszText);
3998 return len;
4000 case EM_SCROLLCARET:
4001 ME_EnsureVisible(editor, &editor->pCursors[0]);
4002 return 0;
4003 case WM_SETFONT:
4005 LOGFONTW lf;
4006 CHARFORMAT2W fmt;
4007 HDC hDC;
4008 BOOL bRepaint = LOWORD(lParam);
4010 if (!wParam)
4011 wParam = (WPARAM)GetStockObject(SYSTEM_FONT);
4013 if (!GetObjectW((HGDIOBJ)wParam, sizeof(LOGFONTW), &lf))
4014 return 0;
4016 hDC = ITextHost_TxGetDC(editor->texthost);
4017 ME_CharFormatFromLogFont(hDC, &lf, &fmt);
4018 ITextHost_TxReleaseDC(editor->texthost, hDC);
4019 if (editor->mode & TM_RICHTEXT) {
4020 ME_Cursor start;
4021 ME_SetCursorToStart(editor, &start);
4022 ME_SetCharFormat(editor, &start, NULL, &fmt);
4024 ME_SetDefaultCharFormat(editor, &fmt);
4026 ME_CommitUndo(editor);
4027 ME_MarkAllForWrapping(editor);
4028 ME_WrapMarkedParagraphs(editor);
4029 ME_UpdateScrollBar(editor);
4030 if (bRepaint)
4031 ME_Repaint(editor);
4032 return 0;
4034 case WM_SETTEXT:
4036 ME_Cursor cursor;
4037 ME_SetCursorToStart(editor, &cursor);
4038 ME_InternalDeleteText(editor, &cursor, ME_GetTextLength(editor), FALSE);
4039 if (lParam)
4041 TRACE("WM_SETTEXT lParam==%lx\n",lParam);
4042 if (!strncmp((char *)lParam, "{\\rtf", 5) ||
4043 !strncmp((char *)lParam, "{\\urtf", 6))
4045 /* Undocumented: WM_SETTEXT supports RTF text */
4046 ME_StreamInRTFString(editor, 0, (char *)lParam);
4048 else
4049 ME_SetText(editor, (void*)lParam, unicode);
4051 else
4052 TRACE("WM_SETTEXT - NULL\n");
4053 ME_SetCursorToStart(editor, &cursor);
4054 ME_UpdateLinkAttribute(editor, &cursor, INT_MAX);
4055 ME_SetSelection(editor, 0, 0);
4056 editor->nModifyStep = 0;
4057 ME_CommitUndo(editor);
4058 ME_EmptyUndoStack(editor);
4059 ME_UpdateRepaint(editor, FALSE);
4060 return 1;
4062 case EM_CANPASTE:
4063 return paste_special( editor, 0, NULL, TRUE );
4064 case WM_PASTE:
4065 case WM_MBUTTONDOWN:
4066 wParam = 0;
4067 lParam = 0;
4068 /* fall through */
4069 case EM_PASTESPECIAL:
4070 paste_special( editor, wParam, (REPASTESPECIAL *)lParam, FALSE );
4071 return 0;
4072 case WM_CUT:
4073 case WM_COPY:
4074 copy_or_cut(editor, msg == WM_CUT);
4075 return 0;
4076 case WM_GETTEXTLENGTH:
4078 GETTEXTLENGTHEX how;
4080 /* CR/LF conversion required in 2.0 mode, verbatim in 1.0 mode */
4081 how.flags = GTL_CLOSE | (editor->bEmulateVersion10 ? 0 : GTL_USECRLF) | GTL_NUMCHARS;
4082 how.codepage = unicode ? CP_UNICODE : CP_ACP;
4083 return ME_GetTextLengthEx(editor, &how);
4085 case EM_GETTEXTLENGTHEX:
4086 return ME_GetTextLengthEx(editor, (GETTEXTLENGTHEX *)wParam);
4087 case WM_GETTEXT:
4089 GETTEXTEX ex;
4090 ex.cb = wParam * (unicode ? sizeof(WCHAR) : sizeof(CHAR));
4091 ex.flags = GT_USECRLF;
4092 ex.codepage = unicode ? CP_UNICODE : CP_ACP;
4093 ex.lpDefaultChar = NULL;
4094 ex.lpUsedDefChar = NULL;
4095 return ME_GetTextEx(editor, &ex, lParam);
4097 case EM_GETTEXTEX:
4098 return ME_GetTextEx(editor, (GETTEXTEX*)wParam, lParam);
4099 case EM_GETSELTEXT:
4101 int nFrom, nTo, nStartCur = ME_GetSelectionOfs(editor, &nFrom, &nTo);
4102 ME_Cursor *from = &editor->pCursors[nStartCur];
4103 return ME_GetTextRange(editor, (WCHAR *)lParam, from,
4104 nTo - nFrom, unicode);
4106 case EM_GETSCROLLPOS:
4108 POINT *point = (POINT *)lParam;
4109 point->x = editor->horz_si.nPos;
4110 point->y = editor->vert_si.nPos;
4111 /* 16-bit scaled value is returned as stored in scrollinfo */
4112 if (editor->horz_si.nMax > 0xffff)
4113 point->x = MulDiv(point->x, 0xffff, editor->horz_si.nMax);
4114 if (editor->vert_si.nMax > 0xffff)
4115 point->y = MulDiv(point->y, 0xffff, editor->vert_si.nMax);
4116 return 1;
4118 case EM_GETTEXTRANGE:
4120 TEXTRANGEW *rng = (TEXTRANGEW *)lParam;
4121 ME_Cursor start;
4122 int nStart = rng->chrg.cpMin;
4123 int nEnd = rng->chrg.cpMax;
4124 int textlength = ME_GetTextLength(editor);
4126 TRACE("EM_GETTEXTRANGE min=%d max=%d unicode=%d textlength=%d\n",
4127 rng->chrg.cpMin, rng->chrg.cpMax, unicode, textlength);
4128 if (nStart < 0) return 0;
4129 if ((nStart == 0 && nEnd == -1) || nEnd > textlength)
4130 nEnd = textlength;
4131 if (nStart >= nEnd) return 0;
4133 ME_CursorFromCharOfs(editor, nStart, &start);
4134 return ME_GetTextRange(editor, rng->lpstrText, &start, nEnd - nStart, unicode);
4136 case EM_GETLINE:
4138 ME_DisplayItem *run;
4139 const unsigned int nMaxChars = *(WORD *) lParam;
4140 unsigned int nCharsLeft = nMaxChars;
4141 char *dest = (char *) lParam;
4142 BOOL wroteNull = FALSE;
4144 TRACE("EM_GETLINE: row=%d, nMaxChars=%d (%s)\n", (int) wParam, nMaxChars,
4145 unicode ? "Unicode" : "Ansi");
4147 run = ME_FindRowWithNumber(editor, wParam);
4148 if (run == NULL)
4149 return 0;
4151 while (nCharsLeft && (run = ME_FindItemFwd(run, diRunOrStartRow))
4152 && run->type == diRun)
4154 WCHAR *str = get_text( &run->member.run, 0 );
4155 unsigned int nCopy;
4157 nCopy = min(nCharsLeft, run->member.run.len);
4159 if (unicode)
4160 memcpy(dest, str, nCopy * sizeof(WCHAR));
4161 else
4162 nCopy = WideCharToMultiByte(CP_ACP, 0, str, nCopy, dest,
4163 nCharsLeft, NULL, NULL);
4164 dest += nCopy * (unicode ? sizeof(WCHAR) : 1);
4165 nCharsLeft -= nCopy;
4168 /* append line termination, space allowing */
4169 if (nCharsLeft > 0)
4171 if (unicode)
4172 *((WCHAR *)dest) = '\0';
4173 else
4174 *dest = '\0';
4175 nCharsLeft--;
4176 wroteNull = TRUE;
4179 TRACE("EM_GETLINE: got %u characters\n", nMaxChars - nCharsLeft);
4180 return nMaxChars - nCharsLeft - (wroteNull ? 1 : 0);
4182 case EM_GETLINECOUNT:
4184 ME_DisplayItem *item = editor->pBuffer->pFirst->next;
4185 int nRows = 0;
4187 ME_DisplayItem *prev_para = NULL, *last_para = NULL;
4189 while (item != editor->pBuffer->pLast)
4191 assert(item->type == diParagraph);
4192 prev_para = ME_FindItemBack(item, diRun);
4193 if (prev_para) {
4194 assert(prev_para->member.run.nFlags & MERF_ENDPARA);
4196 nRows += item->member.para.nRows;
4197 item = item->member.para.next_para;
4199 last_para = ME_FindItemBack(item, diRun);
4200 assert(last_para);
4201 assert(last_para->member.run.nFlags & MERF_ENDPARA);
4202 if (editor->bEmulateVersion10 && prev_para &&
4203 last_para->member.run.nCharOfs == 0 &&
4204 prev_para->member.run.len == 1 &&
4205 *get_text( &prev_para->member.run, 0 ) == '\r')
4207 /* In 1.0 emulation, the last solitary \r at the very end of the text
4208 (if one exists) is NOT a line break.
4209 FIXME: this is an ugly hack. This should have a more regular model. */
4210 nRows--;
4213 TRACE("EM_GETLINECOUNT: nRows==%d\n", nRows);
4214 return max(1, nRows);
4216 case EM_LINEFROMCHAR:
4218 if (wParam == -1)
4219 return ME_RowNumberFromCharOfs(editor, ME_GetCursorOfs(&editor->pCursors[1]));
4220 else
4221 return ME_RowNumberFromCharOfs(editor, wParam);
4223 case EM_EXLINEFROMCHAR:
4225 if (lParam == -1)
4226 return ME_RowNumberFromCharOfs(editor, ME_GetCursorOfs(&editor->pCursors[1]));
4227 else
4228 return ME_RowNumberFromCharOfs(editor, lParam);
4230 case EM_LINEINDEX:
4232 ME_DisplayItem *item, *para;
4233 int nCharOfs;
4235 if (wParam == -1)
4236 item = ME_FindItemBack(editor->pCursors[0].pRun, diStartRow);
4237 else
4238 item = ME_FindRowWithNumber(editor, wParam);
4239 if (!item)
4240 return -1;
4241 para = ME_GetParagraph(item);
4242 item = ME_FindItemFwd(item, diRun);
4243 nCharOfs = para->member.para.nCharOfs + item->member.run.nCharOfs;
4244 TRACE("EM_LINEINDEX: nCharOfs==%d\n", nCharOfs);
4245 return nCharOfs;
4247 case EM_LINELENGTH:
4249 ME_DisplayItem *item, *item_end;
4250 int nChars = 0, nThisLineOfs = 0, nNextLineOfs = 0;
4251 ME_DisplayItem *para, *run;
4253 if (wParam > ME_GetTextLength(editor))
4254 return 0;
4255 if (wParam == -1)
4257 FIXME("EM_LINELENGTH: returning number of unselected characters on lines with selection unsupported.\n");
4258 return 0;
4260 ME_RunOfsFromCharOfs(editor, wParam, &para, &run, NULL);
4261 item = ME_RowStart(run);
4262 nThisLineOfs = ME_CharOfsFromRunOfs(editor, para, ME_FindItemFwd(item, diRun), 0);
4263 item_end = ME_FindItemFwd(item, diStartRowOrParagraphOrEnd);
4264 if (item_end->type == diStartRow) {
4265 nNextLineOfs = ME_CharOfsFromRunOfs(editor, para, ME_FindItemFwd(item_end, diRun), 0);
4266 } else {
4267 ME_DisplayItem *endRun = ME_FindItemBack(item_end, diRun);
4268 assert(endRun && endRun->member.run.nFlags & MERF_ENDPARA);
4269 nNextLineOfs = item_end->member.para.nCharOfs - endRun->member.run.len;
4271 nChars = nNextLineOfs - nThisLineOfs;
4272 TRACE("EM_LINELENGTH(%ld)==%d\n",wParam, nChars);
4273 return nChars;
4275 case EM_EXLIMITTEXT:
4277 if ((int)lParam < 0)
4278 return 0;
4279 if (lParam == 0)
4280 editor->nTextLimit = 65536;
4281 else
4282 editor->nTextLimit = (int) lParam;
4283 return 0;
4285 case EM_LIMITTEXT:
4287 if (wParam == 0)
4288 editor->nTextLimit = 65536;
4289 else
4290 editor->nTextLimit = (int) wParam;
4291 return 0;
4293 case EM_GETLIMITTEXT:
4295 return editor->nTextLimit;
4297 case EM_FINDTEXT:
4299 LRESULT r;
4300 if(!unicode){
4301 FINDTEXTA *ft = (FINDTEXTA *)lParam;
4302 int nChars = MultiByteToWideChar(CP_ACP, 0, ft->lpstrText, -1, NULL, 0);
4303 WCHAR *tmp;
4305 if ((tmp = ALLOC_N_OBJ(WCHAR, nChars)) != NULL)
4306 MultiByteToWideChar(CP_ACP, 0, ft->lpstrText, -1, tmp, nChars);
4307 r = ME_FindText(editor, wParam, &ft->chrg, tmp, NULL);
4308 FREE_OBJ( tmp );
4309 }else{
4310 FINDTEXTW *ft = (FINDTEXTW *)lParam;
4311 r = ME_FindText(editor, wParam, &ft->chrg, ft->lpstrText, NULL);
4313 return r;
4315 case EM_FINDTEXTEX:
4317 LRESULT r;
4318 if(!unicode){
4319 FINDTEXTEXA *ex = (FINDTEXTEXA *)lParam;
4320 int nChars = MultiByteToWideChar(CP_ACP, 0, ex->lpstrText, -1, NULL, 0);
4321 WCHAR *tmp;
4323 if ((tmp = ALLOC_N_OBJ(WCHAR, nChars)) != NULL)
4324 MultiByteToWideChar(CP_ACP, 0, ex->lpstrText, -1, tmp, nChars);
4325 r = ME_FindText(editor, wParam, &ex->chrg, tmp, &ex->chrgText);
4326 FREE_OBJ( tmp );
4327 }else{
4328 FINDTEXTEXW *ex = (FINDTEXTEXW *)lParam;
4329 r = ME_FindText(editor, wParam, &ex->chrg, ex->lpstrText, &ex->chrgText);
4331 return r;
4333 case EM_FINDTEXTW:
4335 FINDTEXTW *ft = (FINDTEXTW *)lParam;
4336 return ME_FindText(editor, wParam, &ft->chrg, ft->lpstrText, NULL);
4338 case EM_FINDTEXTEXW:
4340 FINDTEXTEXW *ex = (FINDTEXTEXW *)lParam;
4341 return ME_FindText(editor, wParam, &ex->chrg, ex->lpstrText, &ex->chrgText);
4343 case EM_GETZOOM:
4344 if (!wParam || !lParam)
4345 return FALSE;
4346 *(int *)wParam = editor->nZoomNumerator;
4347 *(int *)lParam = editor->nZoomDenominator;
4348 return TRUE;
4349 case EM_SETZOOM:
4350 return ME_SetZoom(editor, wParam, lParam);
4351 case EM_CHARFROMPOS:
4353 ME_Cursor cursor;
4354 if (ME_CharFromPos(editor, ((POINTL *)lParam)->x, ((POINTL *)lParam)->y,
4355 &cursor, NULL))
4356 return ME_GetCursorOfs(&cursor);
4357 else
4358 return -1;
4360 case EM_POSFROMCHAR:
4362 ME_DisplayItem *pPara, *pRun;
4363 int nCharOfs, nOffset, nLength;
4364 POINTL pt = {0,0};
4366 nCharOfs = wParam;
4367 /* detect which API version we're dealing with */
4368 if (wParam >= 0x40000)
4369 nCharOfs = lParam;
4370 nLength = ME_GetTextLength(editor);
4371 nCharOfs = min(nCharOfs, nLength);
4372 nCharOfs = max(nCharOfs, 0);
4374 ME_RunOfsFromCharOfs(editor, nCharOfs, &pPara, &pRun, &nOffset);
4375 assert(pRun->type == diRun);
4376 pt.y = pRun->member.run.pt.y;
4377 pt.x = pRun->member.run.pt.x + ME_PointFromChar(editor, &pRun->member.run, nOffset, TRUE);
4378 pt.y += pPara->member.para.pt.y + editor->rcFormat.top;
4379 pt.x += editor->rcFormat.left;
4381 pt.x -= editor->horz_si.nPos;
4382 pt.y -= editor->vert_si.nPos;
4384 if (wParam >= 0x40000) {
4385 *(POINTL *)wParam = pt;
4387 return (wParam >= 0x40000) ? 0 : MAKELONG( pt.x, pt.y );
4389 case WM_CREATE:
4390 return ME_WmCreate(editor, lParam, unicode);
4391 case WM_DESTROY:
4392 ME_DestroyEditor(editor);
4393 return 0;
4394 case WM_SETCURSOR:
4396 POINT cursor_pos;
4397 if (wParam == (WPARAM)editor->hWnd && GetCursorPos(&cursor_pos) &&
4398 ScreenToClient(editor->hWnd, &cursor_pos))
4399 ME_LinkNotify(editor, msg, 0, MAKELPARAM(cursor_pos.x, cursor_pos.y));
4400 return ME_SetCursor(editor);
4402 case WM_LBUTTONDBLCLK:
4403 case WM_LBUTTONDOWN:
4405 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4406 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4407 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4408 return 0;
4409 ITextHost_TxSetFocus(editor->texthost);
4410 ME_LButtonDown(editor, (short)LOWORD(lParam), (short)HIWORD(lParam),
4411 ME_CalculateClickCount(editor, msg, wParam, lParam));
4412 ITextHost_TxSetCapture(editor->texthost, TRUE);
4413 editor->bMouseCaptured = TRUE;
4414 ME_LinkNotify(editor, msg, wParam, lParam);
4415 if (!ME_SetCursor(editor)) goto do_default;
4416 break;
4418 case WM_MOUSEMOVE:
4419 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4420 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4421 return 0;
4422 if (editor->bMouseCaptured)
4423 ME_MouseMove(editor, (short)LOWORD(lParam), (short)HIWORD(lParam));
4424 else
4425 ME_LinkNotify(editor, msg, wParam, lParam);
4426 /* Set cursor if mouse is captured, since WM_SETCURSOR won't be received. */
4427 if (editor->bMouseCaptured)
4428 ME_SetCursor(editor);
4429 break;
4430 case WM_LBUTTONUP:
4431 if (editor->bMouseCaptured) {
4432 ITextHost_TxSetCapture(editor->texthost, FALSE);
4433 editor->bMouseCaptured = FALSE;
4435 if (editor->nSelectionType == stDocument)
4436 editor->nSelectionType = stPosition;
4437 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4438 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4439 return 0;
4440 else
4442 ME_SetCursor(editor);
4443 ME_LinkNotify(editor, msg, wParam, lParam);
4445 break;
4446 case WM_RBUTTONUP:
4447 case WM_RBUTTONDOWN:
4448 case WM_RBUTTONDBLCLK:
4449 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4450 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4451 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4452 return 0;
4453 ME_LinkNotify(editor, msg, wParam, lParam);
4454 goto do_default;
4455 case WM_CONTEXTMENU:
4456 if (!ME_ShowContextMenu(editor, (short)LOWORD(lParam), (short)HIWORD(lParam)))
4457 goto do_default;
4458 break;
4459 case WM_SETFOCUS:
4460 editor->bHaveFocus = TRUE;
4461 ME_ShowCaret(editor);
4462 ME_SendOldNotify(editor, EN_SETFOCUS);
4463 if (!editor->bHideSelection && !(editor->styleFlags & ES_NOHIDESEL))
4464 ME_InvalidateSelection( editor );
4465 return 0;
4466 case WM_KILLFOCUS:
4467 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4468 editor->bHaveFocus = FALSE;
4469 editor->wheel_remain = 0;
4470 ME_HideCaret(editor);
4471 ME_SendOldNotify(editor, EN_KILLFOCUS);
4472 if (!editor->bHideSelection && !(editor->styleFlags & ES_NOHIDESEL))
4473 ME_InvalidateSelection( editor );
4474 return 0;
4475 case WM_COMMAND:
4476 TRACE("editor wnd command = %d\n", LOWORD(wParam));
4477 return 0;
4478 case WM_KEYUP:
4479 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4480 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4481 return 0;
4482 goto do_default;
4483 case WM_KEYDOWN:
4484 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4485 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4486 return 0;
4487 if (ME_KeyDown(editor, LOWORD(wParam)))
4488 return 0;
4489 goto do_default;
4490 case WM_CHAR:
4491 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4492 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4493 return 0;
4494 return ME_Char(editor, wParam, lParam, unicode);
4495 case WM_UNICHAR:
4496 if (unicode)
4498 if(wParam == UNICODE_NOCHAR) return TRUE;
4499 if(wParam <= 0x000fffff)
4501 if(wParam > 0xffff) /* convert to surrogates */
4503 wParam -= 0x10000;
4504 ME_Char(editor, (wParam >> 10) + 0xd800, 0, TRUE);
4505 ME_Char(editor, (wParam & 0x03ff) + 0xdc00, 0, TRUE);
4506 } else {
4507 ME_Char(editor, wParam, 0, TRUE);
4510 return 0;
4512 break;
4513 case EM_STOPGROUPTYPING:
4514 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4515 return 0;
4516 case WM_HSCROLL:
4518 const int scrollUnit = 7;
4520 switch(LOWORD(wParam))
4522 case SB_LEFT:
4523 ME_ScrollAbs(editor, 0, 0);
4524 break;
4525 case SB_RIGHT:
4526 ME_ScrollAbs(editor,
4527 editor->horz_si.nMax - (int)editor->horz_si.nPage,
4528 editor->vert_si.nMax - (int)editor->vert_si.nPage);
4529 break;
4530 case SB_LINELEFT:
4531 ME_ScrollLeft(editor, scrollUnit);
4532 break;
4533 case SB_LINERIGHT:
4534 ME_ScrollRight(editor, scrollUnit);
4535 break;
4536 case SB_PAGELEFT:
4537 ME_ScrollLeft(editor, editor->sizeWindow.cx);
4538 break;
4539 case SB_PAGERIGHT:
4540 ME_ScrollRight(editor, editor->sizeWindow.cx);
4541 break;
4542 case SB_THUMBTRACK:
4543 case SB_THUMBPOSITION:
4545 int pos = HIWORD(wParam);
4546 if (editor->horz_si.nMax > 0xffff)
4547 pos = MulDiv(pos, editor->horz_si.nMax, 0xffff);
4548 ME_HScrollAbs(editor, pos);
4549 break;
4552 break;
4554 case EM_SCROLL: /* fall through */
4555 case WM_VSCROLL:
4557 int origNPos;
4558 int lineHeight = get_default_line_height( editor );
4560 origNPos = editor->vert_si.nPos;
4562 switch(LOWORD(wParam))
4564 case SB_TOP:
4565 ME_ScrollAbs(editor, 0, 0);
4566 break;
4567 case SB_BOTTOM:
4568 ME_ScrollAbs(editor,
4569 editor->horz_si.nMax - (int)editor->horz_si.nPage,
4570 editor->vert_si.nMax - (int)editor->vert_si.nPage);
4571 break;
4572 case SB_LINEUP:
4573 ME_ScrollUp(editor,lineHeight);
4574 break;
4575 case SB_LINEDOWN:
4576 ME_ScrollDown(editor,lineHeight);
4577 break;
4578 case SB_PAGEUP:
4579 ME_ScrollUp(editor,editor->sizeWindow.cy);
4580 break;
4581 case SB_PAGEDOWN:
4582 ME_ScrollDown(editor,editor->sizeWindow.cy);
4583 break;
4584 case SB_THUMBTRACK:
4585 case SB_THUMBPOSITION:
4587 int pos = HIWORD(wParam);
4588 if (editor->vert_si.nMax > 0xffff)
4589 pos = MulDiv(pos, editor->vert_si.nMax, 0xffff);
4590 ME_VScrollAbs(editor, pos);
4591 break;
4594 if (msg == EM_SCROLL)
4595 return 0x00010000 | (((editor->vert_si.nPos - origNPos)/lineHeight) & 0xffff);
4596 break;
4598 case WM_MOUSEWHEEL:
4600 int delta;
4601 BOOL ctrl_is_down;
4603 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4604 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4605 return 0;
4607 ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
4609 delta = GET_WHEEL_DELTA_WPARAM(wParam);
4611 /* if scrolling changes direction, ignore left overs */
4612 if ((delta < 0 && editor->wheel_remain < 0) ||
4613 (delta > 0 && editor->wheel_remain > 0))
4614 editor->wheel_remain += delta;
4615 else
4616 editor->wheel_remain = delta;
4618 if (editor->wheel_remain)
4620 if (ctrl_is_down) {
4621 int numerator;
4622 if (!editor->nZoomNumerator || !editor->nZoomDenominator)
4624 numerator = 100;
4625 } else {
4626 numerator = editor->nZoomNumerator * 100 / editor->nZoomDenominator;
4628 numerator += calc_wheel_change( &editor->wheel_remain, 10 );
4629 if (numerator >= 10 && numerator <= 500)
4630 ME_SetZoom(editor, numerator, 100);
4631 } else {
4632 UINT max_lines = 3;
4633 int lines = 0;
4635 SystemParametersInfoW( SPI_GETWHEELSCROLLLINES, 0, &max_lines, 0 );
4636 if (max_lines)
4637 lines = calc_wheel_change( &editor->wheel_remain, (int)max_lines );
4638 if (lines)
4639 ME_ScrollDown( editor, -lines * get_default_line_height( editor ) );
4642 break;
4644 case EM_GETRECT:
4646 *((RECT *)lParam) = editor->rcFormat;
4647 if (editor->bDefaultFormatRect)
4648 ((RECT *)lParam)->left -= editor->selofs;
4649 return 0;
4651 case EM_SETRECT:
4652 case EM_SETRECTNP:
4654 if (lParam)
4656 int border = 0;
4657 RECT clientRect;
4658 RECT *rc = (RECT *)lParam;
4660 border = editor->exStyleFlags & WS_EX_CLIENTEDGE ? 1 : 0;
4661 ITextHost_TxGetClientRect(editor->texthost, &clientRect);
4662 if (wParam == 0)
4664 editor->rcFormat.top = max(0, rc->top - border);
4665 editor->rcFormat.left = max(0, rc->left - border);
4666 editor->rcFormat.bottom = min(clientRect.bottom, rc->bottom);
4667 editor->rcFormat.right = min(clientRect.right, rc->right + border);
4668 } else if (wParam == 1) {
4669 /* MSDN incorrectly says a wParam value of 1 causes the
4670 * lParam rect to be used as a relative offset,
4671 * however, the tests show it just prevents min/max bound
4672 * checking. */
4673 editor->rcFormat.top = rc->top - border;
4674 editor->rcFormat.left = rc->left - border;
4675 editor->rcFormat.bottom = rc->bottom;
4676 editor->rcFormat.right = rc->right + border;
4677 } else {
4678 return 0;
4680 editor->bDefaultFormatRect = FALSE;
4682 else
4684 ME_SetDefaultFormatRect(editor);
4685 editor->bDefaultFormatRect = TRUE;
4687 ME_MarkAllForWrapping(editor);
4688 ME_WrapMarkedParagraphs(editor);
4689 ME_UpdateScrollBar(editor);
4690 if (msg != EM_SETRECTNP)
4691 ME_Repaint(editor);
4692 return 0;
4694 case EM_REQUESTRESIZE:
4695 ME_SendRequestResize(editor, TRUE);
4696 return 0;
4697 case WM_SETREDRAW:
4698 goto do_default;
4699 case WM_WINDOWPOSCHANGED:
4701 RECT clientRect;
4702 WINDOWPOS *winpos = (WINDOWPOS *)lParam;
4704 if (winpos->flags & SWP_NOCLIENTSIZE) goto do_default;
4705 ITextHost_TxGetClientRect(editor->texthost, &clientRect);
4706 if (editor->bDefaultFormatRect) {
4707 ME_SetDefaultFormatRect(editor);
4708 } else {
4709 editor->rcFormat.right += clientRect.right - editor->prevClientRect.right;
4710 editor->rcFormat.bottom += clientRect.bottom - editor->prevClientRect.bottom;
4712 editor->prevClientRect = clientRect;
4713 ME_RewrapRepaint(editor);
4714 goto do_default;
4716 /* IME messages to make richedit controls IME aware */
4717 case WM_IME_SETCONTEXT:
4718 case WM_IME_CONTROL:
4719 case WM_IME_SELECT:
4720 case WM_IME_COMPOSITIONFULL:
4721 return 0;
4722 case WM_IME_STARTCOMPOSITION:
4724 editor->imeStartIndex=ME_GetCursorOfs(&editor->pCursors[0]);
4725 ME_DeleteSelection(editor);
4726 ME_CommitUndo(editor);
4727 ME_UpdateRepaint(editor, FALSE);
4728 return 0;
4730 case WM_IME_COMPOSITION:
4732 HIMC hIMC;
4734 ME_Style *style = ME_GetInsertStyle(editor, 0);
4735 hIMC = ITextHost_TxImmGetContext(editor->texthost);
4736 ME_DeleteSelection(editor);
4737 ME_SaveTempStyle(editor, style);
4738 if (lParam & (GCS_RESULTSTR|GCS_COMPSTR))
4740 LPWSTR lpCompStr = NULL;
4741 DWORD dwBufLen;
4742 DWORD dwIndex = lParam & GCS_RESULTSTR;
4743 if (!dwIndex)
4744 dwIndex = GCS_COMPSTR;
4746 dwBufLen = ImmGetCompositionStringW(hIMC, dwIndex, NULL, 0);
4747 lpCompStr = HeapAlloc(GetProcessHeap(),0,dwBufLen + sizeof(WCHAR));
4748 ImmGetCompositionStringW(hIMC, dwIndex, lpCompStr, dwBufLen);
4749 lpCompStr[dwBufLen/sizeof(WCHAR)] = 0;
4750 ME_InsertTextFromCursor(editor,0,lpCompStr,dwBufLen/sizeof(WCHAR),style);
4751 HeapFree(GetProcessHeap(), 0, lpCompStr);
4753 if (dwIndex == GCS_COMPSTR)
4754 ME_SetSelection(editor,editor->imeStartIndex,
4755 editor->imeStartIndex + dwBufLen/sizeof(WCHAR));
4757 ME_ReleaseStyle(style);
4758 ME_CommitUndo(editor);
4759 ME_UpdateRepaint(editor, FALSE);
4760 return 0;
4762 case WM_IME_ENDCOMPOSITION:
4764 ME_DeleteSelection(editor);
4765 editor->imeStartIndex=-1;
4766 return 0;
4768 case EM_GETOLEINTERFACE:
4770 if (!editor->reOle)
4771 if (!CreateIRichEditOle(NULL, editor, (LPVOID *)&editor->reOle))
4772 return 0;
4773 *(LPVOID *)lParam = editor->reOle;
4774 IRichEditOle_AddRef(editor->reOle);
4775 return 1;
4777 case EM_GETPASSWORDCHAR:
4779 return editor->cPasswordMask;
4781 case EM_SETOLECALLBACK:
4782 if(editor->lpOleCallback)
4783 IRichEditOleCallback_Release(editor->lpOleCallback);
4784 editor->lpOleCallback = (IRichEditOleCallback*)lParam;
4785 if(editor->lpOleCallback)
4786 IRichEditOleCallback_AddRef(editor->lpOleCallback);
4787 return TRUE;
4788 case EM_GETWORDBREAKPROC:
4789 return (LRESULT)editor->pfnWordBreak;
4790 case EM_SETWORDBREAKPROC:
4792 EDITWORDBREAKPROCW pfnOld = editor->pfnWordBreak;
4794 editor->pfnWordBreak = (EDITWORDBREAKPROCW)lParam;
4795 return (LRESULT)pfnOld;
4797 case EM_GETTEXTMODE:
4798 return editor->mode;
4799 case EM_SETTEXTMODE:
4801 int mask = 0;
4802 int changes = 0;
4804 if (ME_GetTextLength(editor) ||
4805 !list_empty( &editor->undo_stack ) || !list_empty( &editor->redo_stack ))
4806 return E_UNEXPECTED;
4808 /* Check for mutually exclusive flags in adjacent bits of wParam */
4809 if ((wParam & (TM_RICHTEXT | TM_MULTILEVELUNDO | TM_MULTICODEPAGE)) &
4810 (wParam & (TM_PLAINTEXT | TM_SINGLELEVELUNDO | TM_SINGLECODEPAGE)) << 1)
4811 return E_INVALIDARG;
4813 if (wParam & (TM_RICHTEXT | TM_PLAINTEXT))
4815 mask |= TM_RICHTEXT | TM_PLAINTEXT;
4816 changes |= wParam & (TM_RICHTEXT | TM_PLAINTEXT);
4817 if (wParam & TM_PLAINTEXT) {
4818 /* Clear selection since it should be possible to select the
4819 * end of text run for rich text */
4820 ME_InvalidateSelection(editor);
4821 ME_SetCursorToStart(editor, &editor->pCursors[0]);
4822 editor->pCursors[1] = editor->pCursors[0];
4823 /* plain text can only have the default style. */
4824 ME_ClearTempStyle(editor);
4825 ME_AddRefStyle(editor->pBuffer->pDefaultStyle);
4826 ME_ReleaseStyle(editor->pCursors[0].pRun->member.run.style);
4827 editor->pCursors[0].pRun->member.run.style = editor->pBuffer->pDefaultStyle;
4830 /* FIXME: Currently no support for undo level and code page options */
4831 editor->mode = (editor->mode & ~mask) | changes;
4832 return 0;
4834 case EM_SETPASSWORDCHAR:
4836 editor->cPasswordMask = wParam;
4837 ME_RewrapRepaint(editor);
4838 return 0;
4840 case EM_SETTARGETDEVICE:
4841 if (wParam == 0)
4843 BOOL new = (lParam == 0 && (editor->styleFlags & ES_MULTILINE));
4844 if (editor->nAvailWidth || editor->bWordWrap != new)
4846 editor->bWordWrap = new;
4847 editor->nAvailWidth = 0; /* wrap to client area */
4848 ME_RewrapRepaint(editor);
4850 } else {
4851 int width = max(0, lParam);
4852 if ((editor->styleFlags & ES_MULTILINE) &&
4853 (!editor->bWordWrap || editor->nAvailWidth != width))
4855 editor->nAvailWidth = width;
4856 editor->bWordWrap = TRUE;
4857 ME_RewrapRepaint(editor);
4859 FIXME("EM_SETTARGETDEVICE doesn't use non-NULL target devices\n");
4861 return TRUE;
4862 default:
4863 do_default:
4864 *phresult = S_FALSE;
4865 break;
4867 return 0L;
4870 static BOOL create_windowed_editor(HWND hwnd, CREATESTRUCTW *create, BOOL emulate_10)
4872 ITextHost *host = ME_CreateTextHost( hwnd, create, emulate_10 );
4873 ME_TextEditor *editor;
4875 if (!host) return FALSE;
4877 editor = ME_MakeEditor( host, emulate_10 );
4878 if (!editor)
4880 ITextHost_Release( host );
4881 return FALSE;
4884 editor->exStyleFlags = GetWindowLongW( hwnd, GWL_EXSTYLE );
4885 editor->styleFlags |= GetWindowLongW( hwnd, GWL_STYLE ) & ES_WANTRETURN;
4886 editor->hWnd = hwnd; /* FIXME: Remove editor's dependence on hWnd */
4887 editor->hwndParent = create->hwndParent;
4889 SetWindowLongPtrW( hwnd, 0, (LONG_PTR)editor );
4891 return TRUE;
4894 static LRESULT RichEditWndProc_common(HWND hWnd, UINT msg, WPARAM wParam,
4895 LPARAM lParam, BOOL unicode)
4897 ME_TextEditor *editor;
4898 HRESULT hresult;
4899 LRESULT lresult = 0;
4901 TRACE("enter hwnd %p msg %04x (%s) %lx %lx, unicode %d\n",
4902 hWnd, msg, get_msg_name(msg), wParam, lParam, unicode);
4904 editor = (ME_TextEditor *)GetWindowLongPtrW(hWnd, 0);
4905 if (!editor)
4907 if (msg == WM_NCCREATE)
4909 CREATESTRUCTW *pcs = (CREATESTRUCTW *)lParam;
4911 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd, pcs->style);
4912 return create_windowed_editor( hWnd, pcs, FALSE );
4914 else
4916 return DefWindowProcW(hWnd, msg, wParam, lParam);
4920 switch (msg)
4922 case WM_PAINT:
4924 HDC hDC;
4925 RECT rc;
4926 PAINTSTRUCT ps;
4928 hDC = BeginPaint(editor->hWnd, &ps);
4929 if (!editor->bEmulateVersion10 || (editor->nEventMask & ENM_UPDATE))
4930 ME_SendOldNotify(editor, EN_UPDATE);
4931 /* Erase area outside of the formatting rectangle */
4932 if (ps.rcPaint.top < editor->rcFormat.top)
4934 rc = ps.rcPaint;
4935 rc.bottom = editor->rcFormat.top;
4936 FillRect(hDC, &rc, editor->hbrBackground);
4937 ps.rcPaint.top = editor->rcFormat.top;
4939 if (ps.rcPaint.bottom > editor->rcFormat.bottom) {
4940 rc = ps.rcPaint;
4941 rc.top = editor->rcFormat.bottom;
4942 FillRect(hDC, &rc, editor->hbrBackground);
4943 ps.rcPaint.bottom = editor->rcFormat.bottom;
4945 if (ps.rcPaint.left < editor->rcFormat.left) {
4946 rc = ps.rcPaint;
4947 rc.right = editor->rcFormat.left;
4948 FillRect(hDC, &rc, editor->hbrBackground);
4949 ps.rcPaint.left = editor->rcFormat.left;
4951 if (ps.rcPaint.right > editor->rcFormat.right) {
4952 rc = ps.rcPaint;
4953 rc.left = editor->rcFormat.right;
4954 FillRect(hDC, &rc, editor->hbrBackground);
4955 ps.rcPaint.right = editor->rcFormat.right;
4958 ME_PaintContent(editor, hDC, &ps.rcPaint);
4959 EndPaint(editor->hWnd, &ps);
4960 return 0;
4962 case WM_ERASEBKGND:
4964 HDC hDC = (HDC)wParam;
4965 RECT rc;
4967 if (GetUpdateRect(editor->hWnd, &rc, TRUE))
4968 FillRect(hDC, &rc, editor->hbrBackground);
4969 return 1;
4971 case EM_SETOPTIONS:
4973 DWORD dwStyle;
4974 const DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
4975 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN |
4976 ECO_SELECTIONBAR;
4977 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
4978 dwStyle = GetWindowLongW(hWnd, GWL_STYLE);
4979 dwStyle = (dwStyle & ~mask) | (lresult & mask);
4980 SetWindowLongW(hWnd, GWL_STYLE, dwStyle);
4981 return lresult;
4983 case EM_SETREADONLY:
4985 DWORD dwStyle;
4986 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
4987 dwStyle = GetWindowLongW(hWnd, GWL_STYLE);
4988 dwStyle &= ~ES_READONLY;
4989 if (wParam)
4990 dwStyle |= ES_READONLY;
4991 SetWindowLongW(hWnd, GWL_STYLE, dwStyle);
4992 return lresult;
4994 default:
4995 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
4998 if (hresult == S_FALSE)
4999 lresult = DefWindowProcW(hWnd, msg, wParam, lParam);
5001 TRACE("exit hwnd %p msg %04x (%s) %lx %lx, unicode %d -> %lu\n",
5002 hWnd, msg, get_msg_name(msg), wParam, lParam, unicode, lresult);
5004 return lresult;
5007 static LRESULT WINAPI RichEditWndProcW(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5009 BOOL unicode = TRUE;
5011 /* Under Win9x RichEdit20W returns ANSI strings, see the tests. */
5012 if (msg == WM_GETTEXT && (GetVersion() & 0x80000000))
5013 unicode = FALSE;
5015 return RichEditWndProc_common(hWnd, msg, wParam, lParam, unicode);
5018 static LRESULT WINAPI RichEditWndProcA(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5020 return RichEditWndProc_common(hWnd, msg, wParam, lParam, FALSE);
5023 /******************************************************************
5024 * RichEditANSIWndProc (RICHED20.10)
5026 LRESULT WINAPI RichEditANSIWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5028 return RichEditWndProcA(hWnd, msg, wParam, lParam);
5031 /******************************************************************
5032 * RichEdit10ANSIWndProc (RICHED20.9)
5034 LRESULT WINAPI RichEdit10ANSIWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5036 if (msg == WM_NCCREATE && !GetWindowLongPtrW(hWnd, 0))
5038 CREATESTRUCTW *pcs = (CREATESTRUCTW *)lParam;
5040 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd, pcs->style);
5041 return create_windowed_editor( hWnd, pcs, TRUE );
5043 return RichEditANSIWndProc(hWnd, msg, wParam, lParam);
5046 void ME_SendOldNotify(ME_TextEditor *editor, int nCode)
5048 ITextHost_TxNotify(editor->texthost, nCode, NULL);
5051 /* Fill buffer with srcChars unicode characters from the start cursor.
5053 * buffer: destination buffer
5054 * buflen: length of buffer in characters excluding the NULL terminator.
5055 * start: start of editor text to copy into buffer.
5056 * srcChars: Number of characters to use from the editor text.
5057 * bCRLF: if true, replaces all end of lines with \r\n pairs.
5059 * returns the number of characters written excluding the NULL terminator.
5061 * The written text is always NULL terminated.
5063 int ME_GetTextW(ME_TextEditor *editor, WCHAR *buffer, int buflen,
5064 const ME_Cursor *start, int srcChars, BOOL bCRLF,
5065 BOOL bEOP)
5067 ME_DisplayItem *pRun, *pNextRun;
5068 const WCHAR *pStart = buffer;
5069 const WCHAR cr_lf[] = {'\r', '\n', 0};
5070 const WCHAR *str;
5071 int nLen;
5073 /* bCRLF flag is only honored in 2.0 and up. 1.0 must always return text verbatim */
5074 if (editor->bEmulateVersion10) bCRLF = FALSE;
5076 pRun = start->pRun;
5077 assert(pRun);
5078 pNextRun = ME_FindItemFwd(pRun, diRun);
5080 nLen = pRun->member.run.len - start->nOffset;
5081 str = get_text( &pRun->member.run, start->nOffset );
5083 while (srcChars && buflen && pNextRun)
5085 int nFlags = pRun->member.run.nFlags;
5087 if (bCRLF && nFlags & MERF_ENDPARA && ~nFlags & MERF_ENDCELL)
5089 if (buflen == 1) break;
5090 /* FIXME: native fails to reduce srcChars here for WM_GETTEXT or
5091 * EM_GETTEXTEX, however, this is done for copying text which
5092 * also uses this function. */
5093 srcChars -= min(nLen, srcChars);
5094 nLen = 2;
5095 str = cr_lf;
5096 } else {
5097 nLen = min(nLen, srcChars);
5098 srcChars -= nLen;
5101 nLen = min(nLen, buflen);
5102 buflen -= nLen;
5104 CopyMemory(buffer, str, sizeof(WCHAR) * nLen);
5106 buffer += nLen;
5108 pRun = pNextRun;
5109 pNextRun = ME_FindItemFwd(pRun, diRun);
5111 nLen = pRun->member.run.len;
5112 str = get_text( &pRun->member.run, 0 );
5114 /* append '\r' to the last paragraph. */
5115 if (pRun->next->type == diTextEnd && bEOP)
5117 *buffer = '\r';
5118 buffer ++;
5120 *buffer = 0;
5121 return buffer - pStart;
5124 static BOOL ME_RegisterEditorClass(HINSTANCE hInstance)
5126 WNDCLASSW wcW;
5127 WNDCLASSA wcA;
5129 wcW.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS;
5130 wcW.lpfnWndProc = RichEditWndProcW;
5131 wcW.cbClsExtra = 0;
5132 wcW.cbWndExtra = sizeof(ME_TextEditor *);
5133 wcW.hInstance = NULL; /* hInstance would register DLL-local class */
5134 wcW.hIcon = NULL;
5135 wcW.hCursor = LoadCursorW(NULL, (LPWSTR)IDC_IBEAM);
5136 wcW.hbrBackground = GetStockObject(NULL_BRUSH);
5137 wcW.lpszMenuName = NULL;
5139 if (is_version_nt())
5141 wcW.lpszClassName = RICHEDIT_CLASS20W;
5142 if (!RegisterClassW(&wcW)) return FALSE;
5143 wcW.lpszClassName = MSFTEDIT_CLASS;
5144 if (!RegisterClassW(&wcW)) return FALSE;
5146 else
5148 /* WNDCLASSA/W have the same layout */
5149 wcW.lpszClassName = (LPCWSTR)"RichEdit20W";
5150 if (!RegisterClassA((WNDCLASSA *)&wcW)) return FALSE;
5151 wcW.lpszClassName = (LPCWSTR)"RichEdit50W";
5152 if (!RegisterClassA((WNDCLASSA *)&wcW)) return FALSE;
5155 wcA.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS;
5156 wcA.lpfnWndProc = RichEditWndProcA;
5157 wcA.cbClsExtra = 0;
5158 wcA.cbWndExtra = sizeof(ME_TextEditor *);
5159 wcA.hInstance = NULL; /* hInstance would register DLL-local class */
5160 wcA.hIcon = NULL;
5161 wcA.hCursor = LoadCursorW(NULL, (LPWSTR)IDC_IBEAM);
5162 wcA.hbrBackground = GetStockObject(NULL_BRUSH);
5163 wcA.lpszMenuName = NULL;
5164 wcA.lpszClassName = RICHEDIT_CLASS20A;
5165 if (!RegisterClassA(&wcA)) return FALSE;
5166 wcA.lpszClassName = "RichEdit50A";
5167 if (!RegisterClassA(&wcA)) return FALSE;
5169 return TRUE;
5172 static LRESULT WINAPI REComboWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
5173 /* FIXME: Not implemented */
5174 TRACE("hWnd %p msg %04x (%s) %08lx %08lx\n",
5175 hWnd, msg, get_msg_name(msg), wParam, lParam);
5176 return DefWindowProcW(hWnd, msg, wParam, lParam);
5179 static LRESULT WINAPI REListWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
5180 /* FIXME: Not implemented */
5181 TRACE("hWnd %p msg %04x (%s) %08lx %08lx\n",
5182 hWnd, msg, get_msg_name(msg), wParam, lParam);
5183 return DefWindowProcW(hWnd, msg, wParam, lParam);
5186 /******************************************************************
5187 * REExtendedRegisterClass (RICHED20.8)
5189 * FIXME undocumented
5190 * Need to check for errors and implement controls and callbacks
5192 LRESULT WINAPI REExtendedRegisterClass(void)
5194 WNDCLASSW wcW;
5195 UINT result;
5197 FIXME("semi stub\n");
5199 wcW.cbClsExtra = 0;
5200 wcW.cbWndExtra = 4;
5201 wcW.hInstance = NULL;
5202 wcW.hIcon = NULL;
5203 wcW.hCursor = NULL;
5204 wcW.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
5205 wcW.lpszMenuName = NULL;
5207 if (!ME_ListBoxRegistered)
5209 wcW.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS;
5210 wcW.lpfnWndProc = REListWndProc;
5211 wcW.lpszClassName = REListBox20W;
5212 if (RegisterClassW(&wcW)) ME_ListBoxRegistered = TRUE;
5215 if (!ME_ComboBoxRegistered)
5217 wcW.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS | CS_VREDRAW | CS_HREDRAW;
5218 wcW.lpfnWndProc = REComboWndProc;
5219 wcW.lpszClassName = REComboBox20W;
5220 if (RegisterClassW(&wcW)) ME_ComboBoxRegistered = TRUE;
5223 result = 0;
5224 if (ME_ListBoxRegistered)
5225 result += 1;
5226 if (ME_ComboBoxRegistered)
5227 result += 2;
5229 return result;
5232 static int wchar_comp( const void *key, const void *elem )
5234 return *(const WCHAR *)key - *(const WCHAR *)elem;
5237 /* neutral characters end the url if the next non-neutral character is a space character,
5238 otherwise they are included in the url. */
5239 static BOOL isurlneutral( WCHAR c )
5241 /* NB this list is sorted */
5242 static const WCHAR neutral_chars[] = {'!','\"','\'','(',')',',','-','.',':',';','<','>','?','[',']','{','}'};
5244 /* Some shortcuts */
5245 if (isalnum( c )) return FALSE;
5246 if (c > neutral_chars[sizeof(neutral_chars) / sizeof(neutral_chars[0]) - 1]) return FALSE;
5248 return !!bsearch( &c, neutral_chars, sizeof(neutral_chars) / sizeof(neutral_chars[0]),
5249 sizeof(c), wchar_comp );
5253 * This proc takes a selection, and scans it forward in order to select the span
5254 * of a possible URL candidate. A possible URL candidate must start with isalnum
5255 * or one of the following special characters: *|/\+%#@ and must consist entirely
5256 * of the characters allowed to start the URL, plus : (colon) which may occur
5257 * at most once, and not at either end.
5259 static BOOL ME_FindNextURLCandidate(ME_TextEditor *editor,
5260 const ME_Cursor *start,
5261 int nChars,
5262 ME_Cursor *candidate_min,
5263 ME_Cursor *candidate_max)
5265 ME_Cursor cursor = *start, neutral_end, space_end;
5266 BOOL candidateStarted = FALSE, quoted = FALSE;
5267 WCHAR c;
5269 while (nChars > 0)
5271 WCHAR *str = get_text( &cursor.pRun->member.run, 0 );
5272 int run_len = cursor.pRun->member.run.len;
5274 nChars -= run_len - cursor.nOffset;
5276 /* Find start of candidate */
5277 if (!candidateStarted)
5279 while (cursor.nOffset < run_len)
5281 c = str[cursor.nOffset];
5282 if (!isspaceW( c ) && !isurlneutral( c ))
5284 *candidate_min = cursor;
5285 candidateStarted = TRUE;
5286 neutral_end.pPara = NULL;
5287 space_end.pPara = NULL;
5288 cursor.nOffset++;
5289 break;
5291 quoted = (c == '<');
5292 cursor.nOffset++;
5296 /* Find end of candidate */
5297 if (candidateStarted)
5299 while (cursor.nOffset < run_len)
5301 c = str[cursor.nOffset];
5302 if (isspaceW( c ))
5304 if (quoted && c != '\r')
5306 if (!space_end.pPara)
5308 if (neutral_end.pPara)
5309 space_end = neutral_end;
5310 else
5311 space_end = cursor;
5314 else
5315 goto done;
5317 else if (isurlneutral( c ))
5319 if (quoted && c == '>')
5321 neutral_end.pPara = NULL;
5322 space_end.pPara = NULL;
5323 goto done;
5325 if (!neutral_end.pPara)
5326 neutral_end = cursor;
5328 else
5329 neutral_end.pPara = NULL;
5331 cursor.nOffset++;
5335 cursor.nOffset = 0;
5336 if (!ME_NextRun(&cursor.pPara, &cursor.pRun, TRUE))
5337 goto done;
5340 done:
5341 if (candidateStarted)
5343 if (space_end.pPara)
5344 *candidate_max = space_end;
5345 else if (neutral_end.pPara)
5346 *candidate_max = neutral_end;
5347 else
5348 *candidate_max = cursor;
5349 return TRUE;
5351 *candidate_max = *candidate_min = cursor;
5352 return FALSE;
5356 * This proc evaluates the selection and returns TRUE if it can be considered an URL
5358 static BOOL ME_IsCandidateAnURL(ME_TextEditor *editor, const ME_Cursor *start, int nChars)
5360 #define MAX_PREFIX_LEN 9
5361 struct prefix_s {
5362 const WCHAR text[MAX_PREFIX_LEN];
5363 int length;
5364 }prefixes[] = {
5365 {{'p','r','o','s','p','e','r','o',':'}, 9},
5366 {{'t','e','l','n','e','t',':'}, 7},
5367 {{'g','o','p','h','e','r',':'}, 7},
5368 {{'m','a','i','l','t','o',':'}, 7},
5369 {{'h','t','t','p','s',':'}, 6},
5370 {{'f','i','l','e',':'}, 5},
5371 {{'n','e','w','s',':'}, 5},
5372 {{'w','a','i','s',':'}, 5},
5373 {{'n','n','t','p',':'}, 5},
5374 {{'h','t','t','p',':'}, 5},
5375 {{'w','w','w','.'}, 4},
5376 {{'f','t','p',':'}, 4},
5378 WCHAR bufferW[MAX_PREFIX_LEN + 1];
5379 unsigned int i;
5381 ME_GetTextW(editor, bufferW, MAX_PREFIX_LEN, start, nChars, FALSE, FALSE);
5382 for (i = 0; i < sizeof(prefixes) / sizeof(*prefixes); i++)
5384 if (nChars < prefixes[i].length) continue;
5385 if (!memcmp(prefixes[i].text, bufferW, prefixes[i].length * sizeof(WCHAR)))
5386 return TRUE;
5388 return FALSE;
5389 #undef MAX_PREFIX_LEN
5393 * This proc walks through the indicated selection and evaluates whether each
5394 * section identified by ME_FindNextURLCandidate and in-between sections have
5395 * their proper CFE_LINK attributes set or unset. If the CFE_LINK attribute is
5396 * not what it is supposed to be, this proc sets or unsets it as appropriate.
5398 * Since this function can cause runs to be split, do not depend on the value
5399 * of the start cursor at the end of the function.
5401 * nChars may be set to INT_MAX to update to the end of the text.
5403 * Returns TRUE if at least one section was modified.
5405 static BOOL ME_UpdateLinkAttribute(ME_TextEditor *editor, ME_Cursor *start, int nChars)
5407 BOOL modified = FALSE;
5408 ME_Cursor startCur = *start;
5410 if (!editor->AutoURLDetect_bEnable) return FALSE;
5414 CHARFORMAT2W link;
5415 ME_Cursor candidateStart, candidateEnd;
5417 if (ME_FindNextURLCandidate(editor, &startCur, nChars,
5418 &candidateStart, &candidateEnd))
5420 /* Section before candidate is not an URL */
5421 int cMin = ME_GetCursorOfs(&candidateStart);
5422 int cMax = ME_GetCursorOfs(&candidateEnd);
5424 if (!ME_IsCandidateAnURL(editor, &candidateStart, cMax - cMin))
5425 candidateStart = candidateEnd;
5426 nChars -= cMax - ME_GetCursorOfs(&startCur);
5428 else
5430 /* No more candidates until end of selection */
5431 nChars = 0;
5434 if (startCur.pRun != candidateStart.pRun ||
5435 startCur.nOffset != candidateStart.nOffset)
5437 /* CFE_LINK effect should be consistently unset */
5438 link.cbSize = sizeof(link);
5439 ME_GetCharFormat(editor, &startCur, &candidateStart, &link);
5440 if (!(link.dwMask & CFM_LINK) || (link.dwEffects & CFE_LINK))
5442 /* CFE_LINK must be unset from this range */
5443 memset(&link, 0, sizeof(CHARFORMAT2W));
5444 link.cbSize = sizeof(link);
5445 link.dwMask = CFM_LINK;
5446 link.dwEffects = 0;
5447 ME_SetCharFormat(editor, &startCur, &candidateStart, &link);
5448 /* Update candidateEnd since setting character formats may split
5449 * runs, which can cause a cursor to be at an invalid offset within
5450 * a split run. */
5451 while (candidateEnd.nOffset >= candidateEnd.pRun->member.run.len)
5453 candidateEnd.nOffset -= candidateEnd.pRun->member.run.len;
5454 candidateEnd.pRun = ME_FindItemFwd(candidateEnd.pRun, diRun);
5456 modified = TRUE;
5459 if (candidateStart.pRun != candidateEnd.pRun ||
5460 candidateStart.nOffset != candidateEnd.nOffset)
5462 /* CFE_LINK effect should be consistently set */
5463 link.cbSize = sizeof(link);
5464 ME_GetCharFormat(editor, &candidateStart, &candidateEnd, &link);
5465 if (!(link.dwMask & CFM_LINK) || !(link.dwEffects & CFE_LINK))
5467 /* CFE_LINK must be set on this range */
5468 memset(&link, 0, sizeof(CHARFORMAT2W));
5469 link.cbSize = sizeof(link);
5470 link.dwMask = CFM_LINK;
5471 link.dwEffects = CFE_LINK;
5472 ME_SetCharFormat(editor, &candidateStart, &candidateEnd, &link);
5473 modified = TRUE;
5476 startCur = candidateEnd;
5477 } while (nChars > 0);
5478 return modified;