riched20: Use wide-char string literals in a struct initialization.
[wine.git] / dlls / riched20 / editor.c
blobee392f6fa6a2d32b539dcab1005bcb99b841efb3
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);
247 static HCURSOR hLeft;
249 BOOL me_debug = FALSE;
250 HANDLE me_heap = NULL;
252 static BOOL ME_ListBoxRegistered = FALSE;
253 static BOOL ME_ComboBoxRegistered = FALSE;
255 static inline BOOL is_version_nt(void)
257 return !(GetVersion() & 0x80000000);
260 static ME_TextBuffer *ME_MakeText(void) {
261 ME_TextBuffer *buf = heap_alloc(sizeof(*buf));
262 ME_DisplayItem *p1 = ME_MakeDI(diTextStart);
263 ME_DisplayItem *p2 = ME_MakeDI(diTextEnd);
265 p1->prev = NULL;
266 p1->next = p2;
267 p2->prev = p1;
268 p2->next = NULL;
269 p1->member.para.next_para = p2;
270 p2->member.para.prev_para = p1;
271 p2->member.para.nCharOfs = 0;
273 buf->pFirst = p1;
274 buf->pLast = p2;
275 buf->pCharStyle = NULL;
277 return buf;
280 ME_Paragraph *editor_first_para( ME_TextEditor *editor )
282 return para_next( &editor->pBuffer->pFirst->member.para );
285 /* Note, returns the diTextEnd sentinel paragraph */
286 ME_Paragraph *editor_end_para( ME_TextEditor *editor )
288 return &editor->pBuffer->pLast->member.para;
291 static LRESULT ME_StreamInText(ME_TextEditor *editor, DWORD dwFormat, ME_InStream *stream, ME_Style *style)
293 WCHAR *pText;
294 LRESULT total_bytes_read = 0;
295 BOOL is_read = FALSE;
296 DWORD cp = CP_ACP, copy = 0;
297 char conv_buf[4 + STREAMIN_BUFFER_SIZE]; /* up to 4 additional UTF-8 bytes */
299 static const char bom_utf8[] = {0xEF, 0xBB, 0xBF};
301 TRACE("%08x %p\n", dwFormat, stream);
303 do {
304 LONG nWideChars = 0;
305 WCHAR wszText[STREAMIN_BUFFER_SIZE+1];
307 if (!stream->dwSize)
309 ME_StreamInFill(stream);
310 if (stream->editstream->dwError)
311 break;
312 if (!stream->dwSize)
313 break;
314 total_bytes_read += stream->dwSize;
317 if (!(dwFormat & SF_UNICODE))
319 char * buf = stream->buffer;
320 DWORD size = stream->dwSize, end;
322 if (!is_read)
324 is_read = TRUE;
325 if (stream->dwSize >= 3 && !memcmp(stream->buffer, bom_utf8, 3))
327 cp = CP_UTF8;
328 buf += 3;
329 size -= 3;
333 if (cp == CP_UTF8)
335 if (copy)
337 memcpy(conv_buf + copy, buf, size);
338 buf = conv_buf;
339 size += copy;
341 end = size;
342 while ((buf[end-1] & 0xC0) == 0x80)
344 --end;
345 --total_bytes_read; /* strange, but seems to match windows */
347 if (buf[end-1] & 0x80)
349 DWORD need = 0;
350 if ((buf[end-1] & 0xE0) == 0xC0)
351 need = 1;
352 if ((buf[end-1] & 0xF0) == 0xE0)
353 need = 2;
354 if ((buf[end-1] & 0xF8) == 0xF0)
355 need = 3;
357 if (size - end >= need)
359 /* we have enough bytes for this sequence */
360 end = size;
362 else
364 /* need more bytes, so don't transcode this sequence */
365 --end;
369 else
370 end = size;
372 nWideChars = MultiByteToWideChar(cp, 0, buf, end, wszText, STREAMIN_BUFFER_SIZE);
373 pText = wszText;
375 if (cp == CP_UTF8)
377 if (end != size)
379 memcpy(conv_buf, buf + end, size - end);
380 copy = size - end;
384 else
386 nWideChars = stream->dwSize >> 1;
387 pText = (WCHAR *)stream->buffer;
390 ME_InsertTextFromCursor(editor, 0, pText, nWideChars, style);
391 if (stream->dwSize == 0)
392 break;
393 stream->dwSize = 0;
394 } while(1);
395 return total_bytes_read;
398 static void ME_ApplyBorderProperties(RTF_Info *info,
399 ME_BorderRect *borderRect,
400 RTFBorder *borderDef)
402 int i, colorNum;
403 ME_Border *pBorders[] = {&borderRect->top,
404 &borderRect->left,
405 &borderRect->bottom,
406 &borderRect->right};
407 for (i = 0; i < 4; i++)
409 RTFColor *colorDef = info->colorList;
410 pBorders[i]->width = borderDef[i].width;
411 colorNum = borderDef[i].color;
412 while (colorDef && colorDef->rtfCNum != colorNum)
413 colorDef = colorDef->rtfNextColor;
414 if (colorDef)
415 pBorders[i]->colorRef = RGB(
416 colorDef->rtfCRed >= 0 ? colorDef->rtfCRed : 0,
417 colorDef->rtfCGreen >= 0 ? colorDef->rtfCGreen : 0,
418 colorDef->rtfCBlue >= 0 ? colorDef->rtfCBlue : 0);
419 else
420 pBorders[i]->colorRef = RGB(0, 0, 0);
424 void ME_RTFCharAttrHook(RTF_Info *info)
426 CHARFORMAT2W fmt;
427 fmt.cbSize = sizeof(fmt);
428 fmt.dwMask = 0;
429 fmt.dwEffects = 0;
431 switch(info->rtfMinor)
433 case rtfPlain:
434 /* FIXME add more flags once they're implemented */
435 fmt.dwMask = CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE | CFM_UNDERLINETYPE | CFM_STRIKEOUT |
436 CFM_COLOR | CFM_BACKCOLOR | CFM_SIZE | CFM_WEIGHT;
437 fmt.dwEffects = CFE_AUTOCOLOR | CFE_AUTOBACKCOLOR;
438 fmt.yHeight = 12*20; /* 12pt */
439 fmt.wWeight = FW_NORMAL;
440 fmt.bUnderlineType = CFU_UNDERLINE;
441 break;
442 case rtfBold:
443 fmt.dwMask = CFM_BOLD | CFM_WEIGHT;
444 fmt.dwEffects = info->rtfParam ? CFE_BOLD : 0;
445 fmt.wWeight = info->rtfParam ? FW_BOLD : FW_NORMAL;
446 break;
447 case rtfItalic:
448 fmt.dwMask = CFM_ITALIC;
449 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
450 break;
451 case rtfUnderline:
452 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
453 fmt.bUnderlineType = CFU_UNDERLINE;
454 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
455 break;
456 case rtfDotUnderline:
457 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
458 fmt.bUnderlineType = CFU_UNDERLINEDOTTED;
459 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
460 break;
461 case rtfDbUnderline:
462 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
463 fmt.bUnderlineType = CFU_UNDERLINEDOUBLE;
464 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
465 break;
466 case rtfWordUnderline:
467 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
468 fmt.bUnderlineType = CFU_UNDERLINEWORD;
469 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
470 break;
471 case rtfNoUnderline:
472 fmt.dwMask = CFM_UNDERLINE;
473 fmt.dwEffects = 0;
474 break;
475 case rtfStrikeThru:
476 fmt.dwMask = CFM_STRIKEOUT;
477 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
478 break;
479 case rtfSubScript:
480 case rtfSuperScript:
481 case rtfSubScrShrink:
482 case rtfSuperScrShrink:
483 case rtfNoSuperSub:
484 fmt.dwMask = CFM_SUBSCRIPT|CFM_SUPERSCRIPT;
485 if (info->rtfMinor == rtfSubScrShrink) fmt.dwEffects = CFE_SUBSCRIPT;
486 if (info->rtfMinor == rtfSuperScrShrink) fmt.dwEffects = CFE_SUPERSCRIPT;
487 if (info->rtfMinor == rtfNoSuperSub) fmt.dwEffects = 0;
488 break;
489 case rtfInvisible:
490 fmt.dwMask = CFM_HIDDEN;
491 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
492 break;
493 case rtfBackColor:
494 fmt.dwMask = CFM_BACKCOLOR;
495 fmt.dwEffects = 0;
496 if (info->rtfParam == 0)
497 fmt.dwEffects = CFE_AUTOBACKCOLOR;
498 else if (info->rtfParam != rtfNoParam)
500 RTFColor *c = RTFGetColor(info, info->rtfParam);
501 if (c && c->rtfCBlue >= 0)
502 fmt.crBackColor = (c->rtfCBlue<<16)|(c->rtfCGreen<<8)|(c->rtfCRed);
503 else
504 fmt.dwEffects = CFE_AUTOBACKCOLOR;
506 break;
507 case rtfForeColor:
508 fmt.dwMask = CFM_COLOR;
509 fmt.dwEffects = 0;
510 if (info->rtfParam == 0)
511 fmt.dwEffects = CFE_AUTOCOLOR;
512 else if (info->rtfParam != rtfNoParam)
514 RTFColor *c = RTFGetColor(info, info->rtfParam);
515 if (c && c->rtfCBlue >= 0)
516 fmt.crTextColor = (c->rtfCBlue<<16)|(c->rtfCGreen<<8)|(c->rtfCRed);
517 else {
518 fmt.dwEffects = CFE_AUTOCOLOR;
521 break;
522 case rtfFontNum:
523 if (info->rtfParam != rtfNoParam)
525 RTFFont *f = RTFGetFont(info, info->rtfParam);
526 if (f)
528 MultiByteToWideChar(CP_ACP, 0, f->rtfFName, -1, fmt.szFaceName, ARRAY_SIZE(fmt.szFaceName));
529 fmt.szFaceName[ARRAY_SIZE(fmt.szFaceName)-1] = '\0';
530 fmt.bCharSet = f->rtfFCharSet;
531 fmt.dwMask = CFM_FACE | CFM_CHARSET;
532 fmt.bPitchAndFamily = f->rtfFPitch | (f->rtfFFamily << 4);
535 break;
536 case rtfFontSize:
537 fmt.dwMask = CFM_SIZE;
538 if (info->rtfParam != rtfNoParam)
539 fmt.yHeight = info->rtfParam*10;
540 break;
542 if (fmt.dwMask) {
543 ME_Style *style2;
544 RTFFlushOutputBuffer(info);
545 /* FIXME too slow ? how come ? */
546 style2 = ME_ApplyStyle(info->editor, info->style, &fmt);
547 ME_ReleaseStyle(info->style);
548 info->style = style2;
549 info->styleChanged = TRUE;
553 /* FIXME this function doesn't get any information about context of the RTF tag, which is very bad,
554 the same tags mean different things in different contexts */
555 void ME_RTFParAttrHook(RTF_Info *info)
557 switch(info->rtfMinor)
559 case rtfParDef: /* restores default paragraph attributes */
560 if (!info->editor->bEmulateVersion10) /* v4.1 */
561 info->borderType = RTFBorderParaLeft;
562 else /* v1.0 - 3.0 */
563 info->borderType = RTFBorderParaTop;
564 info->fmt.dwMask = PFM_ALIGNMENT | PFM_BORDER | PFM_LINESPACING | PFM_TABSTOPS |
565 PFM_OFFSET | PFM_RIGHTINDENT | PFM_SPACEAFTER | PFM_SPACEBEFORE |
566 PFM_STARTINDENT | PFM_RTLPARA | PFM_NUMBERING | PFM_NUMBERINGSTART |
567 PFM_NUMBERINGSTYLE | PFM_NUMBERINGTAB;
568 /* TODO: shading */
569 info->fmt.wAlignment = PFA_LEFT;
570 info->fmt.cTabCount = 0;
571 info->fmt.dxOffset = info->fmt.dxStartIndent = info->fmt.dxRightIndent = 0;
572 info->fmt.wBorderWidth = info->fmt.wBorders = 0;
573 info->fmt.wBorderSpace = 0;
574 info->fmt.bLineSpacingRule = 0;
575 info->fmt.dySpaceBefore = info->fmt.dySpaceAfter = 0;
576 info->fmt.dyLineSpacing = 0;
577 info->fmt.wEffects &= ~PFE_RTLPARA;
578 info->fmt.wNumbering = 0;
579 info->fmt.wNumberingStart = 0;
580 info->fmt.wNumberingStyle = 0;
581 info->fmt.wNumberingTab = 0;
583 if (!info->editor->bEmulateVersion10) /* v4.1 */
585 if (info->tableDef && info->tableDef->row_start &&
586 info->tableDef->row_start->nFlags & MEPF_ROWEND)
588 ME_Cursor cursor;
589 ME_Paragraph *para;
590 /* We are just after a table row. */
591 RTFFlushOutputBuffer(info);
592 cursor = info->editor->pCursors[0];
593 para = cursor.para;
594 if (para == para_next( info->tableDef->row_start )
595 && !cursor.nOffset && !cursor.run->nCharOfs)
597 /* Since the table row end, no text has been inserted, and the \intbl
598 * control word has not be used. We can confirm that we are not in a
599 * table anymore.
601 info->tableDef->row_start = NULL;
602 info->canInheritInTbl = FALSE;
606 else /* v1.0 - v3.0 */
608 info->fmt.dwMask |= PFM_TABLE;
609 info->fmt.wEffects &= ~PFE_TABLE;
611 break;
612 case rtfNestLevel:
613 if (!info->editor->bEmulateVersion10) /* v4.1 */
615 while (info->rtfParam > info->nestingLevel)
617 RTFTable *tableDef = heap_alloc_zero(sizeof(*tableDef));
618 tableDef->parent = info->tableDef;
619 info->tableDef = tableDef;
621 RTFFlushOutputBuffer(info);
622 if (tableDef->row_start && tableDef->row_start->nFlags & MEPF_ROWEND)
624 ME_Paragraph *para = para_next( tableDef->row_start );
625 tableDef->row_start = table_insert_row_start_at_para( info->editor, para );
627 else
629 ME_Cursor cursor;
630 WCHAR endl = '\r';
631 cursor = info->editor->pCursors[0];
632 if (cursor.nOffset || cursor.run->nCharOfs)
633 ME_InsertTextFromCursor(info->editor, 0, &endl, 1, info->style);
634 tableDef->row_start = table_insert_row_start( info->editor, info->editor->pCursors );
637 info->nestingLevel++;
639 info->canInheritInTbl = FALSE;
641 break;
642 case rtfInTable:
644 if (!info->editor->bEmulateVersion10) /* v4.1 */
646 if (info->nestingLevel < 1)
648 RTFTable *tableDef;
649 ME_Paragraph *para;
651 if (!info->tableDef)
652 info->tableDef = heap_alloc_zero(sizeof(*info->tableDef));
653 tableDef = info->tableDef;
654 RTFFlushOutputBuffer(info);
655 if (tableDef->row_start && tableDef->row_start->nFlags & MEPF_ROWEND)
656 para = para_next( tableDef->row_start );
657 else
658 para = info->editor->pCursors[0].para;
660 tableDef->row_start = table_insert_row_start_at_para( info->editor, para );
662 info->nestingLevel = 1;
663 info->canInheritInTbl = TRUE;
665 return;
666 } else { /* v1.0 - v3.0 */
667 info->fmt.dwMask |= PFM_TABLE;
668 info->fmt.wEffects |= PFE_TABLE;
670 break;
672 case rtfFirstIndent:
673 case rtfLeftIndent:
674 if ((info->fmt.dwMask & (PFM_STARTINDENT | PFM_OFFSET)) != (PFM_STARTINDENT | PFM_OFFSET))
676 PARAFORMAT2 fmt;
677 fmt.cbSize = sizeof(fmt);
678 editor_get_selection_para_fmt( info->editor, &fmt );
679 info->fmt.dwMask |= PFM_STARTINDENT | PFM_OFFSET;
680 info->fmt.dxStartIndent = fmt.dxStartIndent;
681 info->fmt.dxOffset = fmt.dxOffset;
683 if (info->rtfMinor == rtfFirstIndent)
685 info->fmt.dxStartIndent += info->fmt.dxOffset + info->rtfParam;
686 info->fmt.dxOffset = -info->rtfParam;
688 else
689 info->fmt.dxStartIndent = info->rtfParam - info->fmt.dxOffset;
690 break;
691 case rtfRightIndent:
692 info->fmt.dwMask |= PFM_RIGHTINDENT;
693 info->fmt.dxRightIndent = info->rtfParam;
694 break;
695 case rtfQuadLeft:
696 case rtfQuadJust:
697 info->fmt.dwMask |= PFM_ALIGNMENT;
698 info->fmt.wAlignment = PFA_LEFT;
699 break;
700 case rtfQuadRight:
701 info->fmt.dwMask |= PFM_ALIGNMENT;
702 info->fmt.wAlignment = PFA_RIGHT;
703 break;
704 case rtfQuadCenter:
705 info->fmt.dwMask |= PFM_ALIGNMENT;
706 info->fmt.wAlignment = PFA_CENTER;
707 break;
708 case rtfTabPos:
709 if (!(info->fmt.dwMask & PFM_TABSTOPS))
711 PARAFORMAT2 fmt;
712 fmt.cbSize = sizeof(fmt);
713 editor_get_selection_para_fmt( info->editor, &fmt );
714 memcpy(info->fmt.rgxTabs, fmt.rgxTabs,
715 fmt.cTabCount * sizeof(fmt.rgxTabs[0]));
716 info->fmt.cTabCount = fmt.cTabCount;
717 info->fmt.dwMask |= PFM_TABSTOPS;
719 if (info->fmt.cTabCount < MAX_TAB_STOPS && info->rtfParam < 0x1000000)
720 info->fmt.rgxTabs[info->fmt.cTabCount++] = info->rtfParam;
721 break;
722 case rtfKeep:
723 info->fmt.dwMask |= PFM_KEEP;
724 info->fmt.wEffects |= PFE_KEEP;
725 break;
726 case rtfNoWidowControl:
727 info->fmt.dwMask |= PFM_NOWIDOWCONTROL;
728 info->fmt.wEffects |= PFE_NOWIDOWCONTROL;
729 break;
730 case rtfKeepNext:
731 info->fmt.dwMask |= PFM_KEEPNEXT;
732 info->fmt.wEffects |= PFE_KEEPNEXT;
733 break;
734 case rtfSpaceAfter:
735 info->fmt.dwMask |= PFM_SPACEAFTER;
736 info->fmt.dySpaceAfter = info->rtfParam;
737 break;
738 case rtfSpaceBefore:
739 info->fmt.dwMask |= PFM_SPACEBEFORE;
740 info->fmt.dySpaceBefore = info->rtfParam;
741 break;
742 case rtfSpaceBetween:
743 info->fmt.dwMask |= PFM_LINESPACING;
744 if ((int)info->rtfParam > 0)
746 info->fmt.dyLineSpacing = info->rtfParam;
747 info->fmt.bLineSpacingRule = 3;
749 else
751 info->fmt.dyLineSpacing = info->rtfParam;
752 info->fmt.bLineSpacingRule = 4;
754 break;
755 case rtfSpaceMultiply:
756 info->fmt.dwMask |= PFM_LINESPACING;
757 info->fmt.dyLineSpacing = info->rtfParam * 20;
758 info->fmt.bLineSpacingRule = 5;
759 break;
760 case rtfParBullet:
761 info->fmt.dwMask |= PFM_NUMBERING;
762 info->fmt.wNumbering = PFN_BULLET;
763 break;
764 case rtfParSimple:
765 info->fmt.dwMask |= PFM_NUMBERING;
766 info->fmt.wNumbering = 2; /* FIXME: MSDN says it's not used ?? */
767 break;
768 case rtfBorderLeft:
769 info->borderType = RTFBorderParaLeft;
770 info->fmt.wBorders |= 1;
771 info->fmt.dwMask |= PFM_BORDER;
772 break;
773 case rtfBorderRight:
774 info->borderType = RTFBorderParaRight;
775 info->fmt.wBorders |= 2;
776 info->fmt.dwMask |= PFM_BORDER;
777 break;
778 case rtfBorderTop:
779 info->borderType = RTFBorderParaTop;
780 info->fmt.wBorders |= 4;
781 info->fmt.dwMask |= PFM_BORDER;
782 break;
783 case rtfBorderBottom:
784 info->borderType = RTFBorderParaBottom;
785 info->fmt.wBorders |= 8;
786 info->fmt.dwMask |= PFM_BORDER;
787 break;
788 case rtfBorderSingle:
789 info->fmt.wBorders &= ~0x700;
790 info->fmt.wBorders |= 1 << 8;
791 info->fmt.dwMask |= PFM_BORDER;
792 break;
793 case rtfBorderThick:
794 info->fmt.wBorders &= ~0x700;
795 info->fmt.wBorders |= 2 << 8;
796 info->fmt.dwMask |= PFM_BORDER;
797 break;
798 case rtfBorderShadow:
799 info->fmt.wBorders &= ~0x700;
800 info->fmt.wBorders |= 10 << 8;
801 info->fmt.dwMask |= PFM_BORDER;
802 break;
803 case rtfBorderDouble:
804 info->fmt.wBorders &= ~0x700;
805 info->fmt.wBorders |= 7 << 8;
806 info->fmt.dwMask |= PFM_BORDER;
807 break;
808 case rtfBorderDot:
809 info->fmt.wBorders &= ~0x700;
810 info->fmt.wBorders |= 11 << 8;
811 info->fmt.dwMask |= PFM_BORDER;
812 break;
813 case rtfBorderWidth:
815 int borderSide = info->borderType & RTFBorderSideMask;
816 RTFTable *tableDef = info->tableDef;
817 if ((info->borderType & RTFBorderTypeMask) == RTFBorderTypeCell)
819 RTFBorder *border;
820 if (!tableDef || tableDef->numCellsDefined >= MAX_TABLE_CELLS)
821 break;
822 border = &tableDef->cells[tableDef->numCellsDefined].border[borderSide];
823 border->width = info->rtfParam;
824 break;
826 info->fmt.wBorderWidth = info->rtfParam;
827 info->fmt.dwMask |= PFM_BORDER;
828 break;
830 case rtfBorderSpace:
831 info->fmt.wBorderSpace = info->rtfParam;
832 info->fmt.dwMask |= PFM_BORDER;
833 break;
834 case rtfBorderColor:
836 RTFTable *tableDef = info->tableDef;
837 int borderSide = info->borderType & RTFBorderSideMask;
838 int borderType = info->borderType & RTFBorderTypeMask;
839 switch(borderType)
841 case RTFBorderTypePara:
842 if (!info->editor->bEmulateVersion10) /* v4.1 */
843 break;
844 /* v1.0 - 3.0 treat paragraph and row borders the same. */
845 case RTFBorderTypeRow:
846 if (tableDef) {
847 tableDef->border[borderSide].color = info->rtfParam;
849 break;
850 case RTFBorderTypeCell:
851 if (tableDef && tableDef->numCellsDefined < MAX_TABLE_CELLS) {
852 tableDef->cells[tableDef->numCellsDefined].border[borderSide].color = info->rtfParam;
854 break;
856 break;
858 case rtfRTLPar:
859 info->fmt.dwMask |= PFM_RTLPARA;
860 info->fmt.wEffects |= PFE_RTLPARA;
861 break;
862 case rtfLTRPar:
863 info->fmt.dwMask |= PFM_RTLPARA;
864 info->fmt.wEffects &= ~PFE_RTLPARA;
865 break;
869 void ME_RTFTblAttrHook(RTF_Info *info)
871 switch (info->rtfMinor)
873 case rtfRowDef:
875 if (!info->editor->bEmulateVersion10) /* v4.1 */
876 info->borderType = 0; /* Not sure */
877 else /* v1.0 - 3.0 */
878 info->borderType = RTFBorderRowTop;
879 if (!info->tableDef) {
880 info->tableDef = ME_MakeTableDef(info->editor);
881 } else {
882 ME_InitTableDef(info->editor, info->tableDef);
884 break;
886 case rtfCellPos:
888 int cellNum;
889 if (!info->tableDef)
891 info->tableDef = ME_MakeTableDef(info->editor);
893 cellNum = info->tableDef->numCellsDefined;
894 if (cellNum >= MAX_TABLE_CELLS)
895 break;
896 info->tableDef->cells[cellNum].rightBoundary = info->rtfParam;
897 if (cellNum < MAX_TAB_STOPS)
899 /* Tab stops were used to store cell positions before v4.1 but v4.1
900 * still seems to set the tabstops without using them. */
901 PARAFORMAT2 *fmt = &info->editor->pCursors[0].para->fmt;
902 fmt->rgxTabs[cellNum] &= ~0x00FFFFFF;
903 fmt->rgxTabs[cellNum] |= 0x00FFFFFF & info->rtfParam;
905 info->tableDef->numCellsDefined++;
906 break;
908 case rtfRowBordTop:
909 info->borderType = RTFBorderRowTop;
910 break;
911 case rtfRowBordLeft:
912 info->borderType = RTFBorderRowLeft;
913 break;
914 case rtfRowBordBottom:
915 info->borderType = RTFBorderRowBottom;
916 break;
917 case rtfRowBordRight:
918 info->borderType = RTFBorderRowRight;
919 break;
920 case rtfCellBordTop:
921 info->borderType = RTFBorderCellTop;
922 break;
923 case rtfCellBordLeft:
924 info->borderType = RTFBorderCellLeft;
925 break;
926 case rtfCellBordBottom:
927 info->borderType = RTFBorderCellBottom;
928 break;
929 case rtfCellBordRight:
930 info->borderType = RTFBorderCellRight;
931 break;
932 case rtfRowGapH:
933 if (info->tableDef)
934 info->tableDef->gapH = info->rtfParam;
935 break;
936 case rtfRowLeftEdge:
937 if (info->tableDef)
938 info->tableDef->leftEdge = info->rtfParam;
939 break;
943 void ME_RTFSpecialCharHook(RTF_Info *info)
945 RTFTable *tableDef = info->tableDef;
946 switch (info->rtfMinor)
948 case rtfNestCell:
949 if (info->editor->bEmulateVersion10) /* v1.0 - v3.0 */
950 break;
951 /* else fall through since v4.1 treats rtfNestCell and rtfCell the same */
952 case rtfCell:
953 if (!tableDef)
954 break;
955 RTFFlushOutputBuffer(info);
956 if (!info->editor->bEmulateVersion10) /* v4.1 */
958 if (tableDef->row_start)
960 if (!info->nestingLevel && tableDef->row_start->nFlags & MEPF_ROWEND)
962 ME_Paragraph *para = para_next( tableDef->row_start );
963 tableDef->row_start = table_insert_row_start_at_para( info->editor, para );
964 info->nestingLevel = 1;
966 table_insert_cell( info->editor, info->editor->pCursors );
969 else /* v1.0 - v3.0 */
971 ME_Paragraph *para = info->editor->pCursors[0].para;
973 if (para_in_table( para ) && tableDef->numCellsInserted < tableDef->numCellsDefined)
975 WCHAR tab = '\t';
976 ME_InsertTextFromCursor(info->editor, 0, &tab, 1, info->style);
977 tableDef->numCellsInserted++;
980 break;
981 case rtfNestRow:
982 if (info->editor->bEmulateVersion10) /* v1.0 - v3.0 */
983 break;
984 /* else fall through since v4.1 treats rtfNestRow and rtfRow the same */
985 case rtfRow:
987 ME_Run *run;
988 ME_Paragraph *para;
989 ME_Cell *cell;
990 int i;
992 if (!tableDef)
993 break;
994 RTFFlushOutputBuffer(info);
995 if (!info->editor->bEmulateVersion10) /* v4.1 */
997 if (!tableDef->row_start) break;
998 if (!info->nestingLevel && tableDef->row_start->nFlags & MEPF_ROWEND)
1000 para = para_next( tableDef->row_start );
1001 tableDef->row_start = table_insert_row_start_at_para( info->editor, para );
1002 info->nestingLevel++;
1004 para = tableDef->row_start;
1005 cell = table_row_first_cell( para );
1006 assert( cell && !cell_prev( cell ) );
1007 if (tableDef->numCellsDefined < 1)
1009 /* 2000 twips appears to be the cell size that native richedit uses
1010 * when no cell sizes are specified. */
1011 const int default_size = 2000;
1012 int right_boundary = default_size;
1013 cell->nRightBoundary = right_boundary;
1014 while (cell_next( cell ))
1016 cell = cell_next( cell );
1017 right_boundary += default_size;
1018 cell->nRightBoundary = right_boundary;
1020 para = table_insert_cell( info->editor, info->editor->pCursors );
1021 cell = para_cell( para );
1022 cell->nRightBoundary = right_boundary;
1024 else
1026 for (i = 0; i < tableDef->numCellsDefined; i++)
1028 RTFCell *cellDef = &tableDef->cells[i];
1029 cell->nRightBoundary = cellDef->rightBoundary;
1030 ME_ApplyBorderProperties( info, &cell->border, cellDef->border );
1031 cell = cell_next( cell );
1032 if (!cell)
1034 para = table_insert_cell( info->editor, info->editor->pCursors );
1035 cell = para_cell( para );
1038 /* Cell for table row delimiter is empty */
1039 cell->nRightBoundary = tableDef->cells[i - 1].rightBoundary;
1042 run = para_first_run( cell_first_para( cell ) );
1043 if (info->editor->pCursors[0].run != run || info->editor->pCursors[0].nOffset)
1045 int nOfs, nChars;
1046 /* Delete inserted cells that aren't defined. */
1047 info->editor->pCursors[1].run = run;
1048 info->editor->pCursors[1].para = run->para;
1049 info->editor->pCursors[1].nOffset = 0;
1050 nOfs = ME_GetCursorOfs(&info->editor->pCursors[1]);
1051 nChars = ME_GetCursorOfs(&info->editor->pCursors[0]) - nOfs;
1052 ME_InternalDeleteText(info->editor, &info->editor->pCursors[1],
1053 nChars, TRUE);
1056 para = table_insert_row_end( info->editor, info->editor->pCursors );
1057 para->fmt.dxOffset = abs(info->tableDef->gapH);
1058 para->fmt.dxStartIndent = info->tableDef->leftEdge;
1059 ME_ApplyBorderProperties( info, &para->border, tableDef->border );
1060 info->nestingLevel--;
1061 if (!info->nestingLevel)
1063 if (info->canInheritInTbl) tableDef->row_start = para;
1064 else
1066 while (info->tableDef)
1068 tableDef = info->tableDef;
1069 info->tableDef = tableDef->parent;
1070 heap_free(tableDef);
1074 else
1076 info->tableDef = tableDef->parent;
1077 heap_free(tableDef);
1080 else /* v1.0 - v3.0 */
1082 WCHAR endl = '\r';
1084 para = info->editor->pCursors[0].para;
1085 para->fmt.dxOffset = info->tableDef->gapH;
1086 para->fmt.dxStartIndent = info->tableDef->leftEdge;
1088 ME_ApplyBorderProperties( info, &para->border, tableDef->border );
1089 while (tableDef->numCellsInserted < tableDef->numCellsDefined)
1091 WCHAR tab = '\t';
1092 ME_InsertTextFromCursor(info->editor, 0, &tab, 1, info->style);
1093 tableDef->numCellsInserted++;
1095 para->fmt.cTabCount = min(tableDef->numCellsDefined, MAX_TAB_STOPS);
1096 if (!tableDef->numCellsDefined) para->fmt.wEffects &= ~PFE_TABLE;
1097 ME_InsertTextFromCursor(info->editor, 0, &endl, 1, info->style);
1098 tableDef->numCellsInserted = 0;
1100 break;
1102 case rtfTab:
1103 case rtfPar:
1104 if (info->editor->bEmulateVersion10) /* v1.0 - 3.0 */
1106 ME_Paragraph *para;
1108 RTFFlushOutputBuffer(info);
1109 para = info->editor->pCursors[0].para;
1110 if (para_in_table( para ))
1112 /* rtfPar is treated like a space within a table. */
1113 info->rtfClass = rtfText;
1114 info->rtfMajor = ' ';
1116 else if (info->rtfMinor == rtfPar && tableDef)
1117 tableDef->numCellsInserted = 0;
1119 break;
1123 static HRESULT insert_static_object(ME_TextEditor *editor, HENHMETAFILE hemf, HBITMAP hbmp,
1124 const SIZEL* sz)
1126 LPOLEOBJECT lpObject = NULL;
1127 LPSTORAGE lpStorage = NULL;
1128 LPOLECLIENTSITE lpClientSite = NULL;
1129 LPDATAOBJECT lpDataObject = NULL;
1130 LPOLECACHE lpOleCache = NULL;
1131 LPRICHEDITOLE lpReOle = NULL;
1132 STGMEDIUM stgm;
1133 FORMATETC fm;
1134 CLSID clsid;
1135 HRESULT hr = E_FAIL;
1136 DWORD conn;
1138 if (hemf)
1140 stgm.tymed = TYMED_ENHMF;
1141 stgm.u.hEnhMetaFile = hemf;
1142 fm.cfFormat = CF_ENHMETAFILE;
1144 else if (hbmp)
1146 stgm.tymed = TYMED_GDI;
1147 stgm.u.hBitmap = hbmp;
1148 fm.cfFormat = CF_BITMAP;
1150 else return E_FAIL;
1152 stgm.pUnkForRelease = NULL;
1154 fm.ptd = NULL;
1155 fm.dwAspect = DVASPECT_CONTENT;
1156 fm.lindex = -1;
1157 fm.tymed = stgm.tymed;
1159 if (!editor->reOle)
1161 if (!CreateIRichEditOle(NULL, editor, (LPVOID *)&editor->reOle))
1162 return hr;
1165 if (OleCreateDefaultHandler(&CLSID_NULL, NULL, &IID_IOleObject, (void**)&lpObject) == S_OK &&
1166 IUnknown_QueryInterface(editor->reOle, &IID_IRichEditOle, (void**)&lpReOle) == S_OK &&
1167 IRichEditOle_GetClientSite(lpReOle, &lpClientSite) == S_OK &&
1168 IOleObject_SetClientSite(lpObject, lpClientSite) == S_OK &&
1169 IOleObject_GetUserClassID(lpObject, &clsid) == S_OK &&
1170 IOleObject_QueryInterface(lpObject, &IID_IOleCache, (void**)&lpOleCache) == S_OK &&
1171 IOleCache_Cache(lpOleCache, &fm, 0, &conn) == S_OK &&
1172 IOleObject_QueryInterface(lpObject, &IID_IDataObject, (void**)&lpDataObject) == S_OK &&
1173 IDataObject_SetData(lpDataObject, &fm, &stgm, TRUE) == S_OK)
1175 REOBJECT reobject;
1177 reobject.cbStruct = sizeof(reobject);
1178 reobject.cp = REO_CP_SELECTION;
1179 reobject.clsid = clsid;
1180 reobject.poleobj = lpObject;
1181 reobject.pstg = lpStorage;
1182 reobject.polesite = lpClientSite;
1183 /* convert from twips to .01 mm */
1184 reobject.sizel.cx = MulDiv(sz->cx, 254, 144);
1185 reobject.sizel.cy = MulDiv(sz->cy, 254, 144);
1186 reobject.dvaspect = DVASPECT_CONTENT;
1187 reobject.dwFlags = 0; /* FIXME */
1188 reobject.dwUser = 0;
1190 ME_InsertOLEFromCursor(editor, &reobject, 0);
1191 hr = S_OK;
1194 if (lpObject) IOleObject_Release(lpObject);
1195 if (lpClientSite) IOleClientSite_Release(lpClientSite);
1196 if (lpStorage) IStorage_Release(lpStorage);
1197 if (lpDataObject) IDataObject_Release(lpDataObject);
1198 if (lpOleCache) IOleCache_Release(lpOleCache);
1199 if (lpReOle) IRichEditOle_Release(lpReOle);
1201 return hr;
1204 static void ME_RTFReadShpPictGroup( RTF_Info *info )
1206 int level = 1;
1208 for (;;)
1210 RTFGetToken (info);
1212 if (info->rtfClass == rtfEOF) return;
1213 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1215 if (--level == 0) break;
1217 else if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1219 level++;
1221 else
1223 RTFRouteToken( info );
1224 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1225 level--;
1229 RTFRouteToken( info ); /* feed "}" back to router */
1230 return;
1233 static DWORD read_hex_data( RTF_Info *info, BYTE **out )
1235 DWORD read = 0, size = 1024;
1236 BYTE *buf, val;
1237 BOOL flip;
1239 *out = NULL;
1241 if (info->rtfClass != rtfText)
1243 ERR("Called with incorrect token\n");
1244 return 0;
1247 buf = HeapAlloc( GetProcessHeap(), 0, size );
1248 if (!buf) return 0;
1250 val = info->rtfMajor;
1251 for (flip = TRUE;; flip = !flip)
1253 RTFGetToken( info );
1254 if (info->rtfClass == rtfEOF)
1256 HeapFree( GetProcessHeap(), 0, buf );
1257 return 0;
1259 if (info->rtfClass != rtfText) break;
1260 if (flip)
1262 if (read >= size)
1264 size *= 2;
1265 buf = HeapReAlloc( GetProcessHeap(), 0, buf, size );
1266 if (!buf) return 0;
1268 buf[read++] = RTFCharToHex(val) * 16 + RTFCharToHex(info->rtfMajor);
1270 else
1271 val = info->rtfMajor;
1273 if (flip) FIXME("wrong hex string\n");
1275 *out = buf;
1276 return read;
1279 static void ME_RTFReadPictGroup(RTF_Info *info)
1281 SIZEL sz;
1282 BYTE *buffer = NULL;
1283 DWORD size = 0;
1284 METAFILEPICT mfp;
1285 HENHMETAFILE hemf;
1286 HBITMAP hbmp;
1287 enum gfxkind {gfx_unknown = 0, gfx_enhmetafile, gfx_metafile, gfx_dib} gfx = gfx_unknown;
1288 int level = 1;
1290 mfp.mm = MM_TEXT;
1291 sz.cx = sz.cy = 0;
1293 for (;;)
1295 RTFGetToken( info );
1297 if (info->rtfClass == rtfText)
1299 if (level == 1)
1301 if (!buffer)
1302 size = read_hex_data( info, &buffer );
1304 else
1306 RTFSkipGroup( info );
1308 } /* We potentially have a new token so fall through. */
1310 if (info->rtfClass == rtfEOF) return;
1312 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1314 if (--level == 0) break;
1315 continue;
1317 if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1319 level++;
1320 continue;
1322 if (!RTFCheckCM( info, rtfControl, rtfPictAttr ))
1324 RTFRouteToken( info );
1325 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1326 level--;
1327 continue;
1330 if (RTFCheckMM( info, rtfPictAttr, rtfWinMetafile ))
1332 mfp.mm = info->rtfParam;
1333 gfx = gfx_metafile;
1335 else if (RTFCheckMM( info, rtfPictAttr, rtfDevIndBitmap ))
1337 if (info->rtfParam != 0) FIXME("dibitmap should be 0 (%d)\n", info->rtfParam);
1338 gfx = gfx_dib;
1340 else if (RTFCheckMM( info, rtfPictAttr, rtfEmfBlip ))
1341 gfx = gfx_enhmetafile;
1342 else if (RTFCheckMM( info, rtfPictAttr, rtfPicWid ))
1343 mfp.xExt = info->rtfParam;
1344 else if (RTFCheckMM( info, rtfPictAttr, rtfPicHt ))
1345 mfp.yExt = info->rtfParam;
1346 else if (RTFCheckMM( info, rtfPictAttr, rtfPicGoalWid ))
1347 sz.cx = info->rtfParam;
1348 else if (RTFCheckMM( info, rtfPictAttr, rtfPicGoalHt ))
1349 sz.cy = info->rtfParam;
1350 else
1351 FIXME("Non supported attribute: %d %d %d\n", info->rtfClass, info->rtfMajor, info->rtfMinor);
1354 if (buffer)
1356 switch (gfx)
1358 case gfx_enhmetafile:
1359 if ((hemf = SetEnhMetaFileBits( size, buffer )))
1360 insert_static_object( info->editor, hemf, NULL, &sz );
1361 break;
1362 case gfx_metafile:
1363 if ((hemf = SetWinMetaFileBits( size, buffer, NULL, &mfp )))
1364 insert_static_object( info->editor, hemf, NULL, &sz );
1365 break;
1366 case gfx_dib:
1368 BITMAPINFO *bi = (BITMAPINFO*)buffer;
1369 HDC hdc = GetDC(0);
1370 unsigned nc = bi->bmiHeader.biClrUsed;
1372 /* not quite right, especially for bitfields type of compression */
1373 if (!nc && bi->bmiHeader.biBitCount <= 8)
1374 nc = 1 << bi->bmiHeader.biBitCount;
1375 if ((hbmp = CreateDIBitmap( hdc, &bi->bmiHeader,
1376 CBM_INIT, (char*)(bi + 1) + nc * sizeof(RGBQUAD),
1377 bi, DIB_RGB_COLORS)) )
1378 insert_static_object( info->editor, NULL, hbmp, &sz );
1379 ReleaseDC( 0, hdc );
1380 break;
1382 default:
1383 break;
1386 HeapFree( GetProcessHeap(), 0, buffer );
1387 RTFRouteToken( info ); /* feed "}" back to router */
1388 return;
1391 /* for now, lookup the \result part and use it, whatever the object */
1392 static void ME_RTFReadObjectGroup(RTF_Info *info)
1394 for (;;)
1396 RTFGetToken (info);
1397 if (info->rtfClass == rtfEOF)
1398 return;
1399 if (RTFCheckCM(info, rtfGroup, rtfEndGroup))
1400 break;
1401 if (RTFCheckCM(info, rtfGroup, rtfBeginGroup))
1403 RTFGetToken (info);
1404 if (info->rtfClass == rtfEOF)
1405 return;
1406 if (RTFCheckCMM(info, rtfControl, rtfDestination, rtfObjResult))
1408 int level = 1;
1410 while (RTFGetToken (info) != rtfEOF)
1412 if (info->rtfClass == rtfGroup)
1414 if (info->rtfMajor == rtfBeginGroup) level++;
1415 else if (info->rtfMajor == rtfEndGroup && --level < 0) break;
1417 RTFRouteToken(info);
1420 else RTFSkipGroup(info);
1421 continue;
1423 if (!RTFCheckCM (info, rtfControl, rtfObjAttr))
1425 FIXME("Non supported attribute: %d %d %d\n", info->rtfClass, info->rtfMajor, info->rtfMinor);
1426 return;
1429 RTFRouteToken(info); /* feed "}" back to router */
1432 static void ME_RTFReadParnumGroup( RTF_Info *info )
1434 int level = 1, type = -1;
1435 WORD indent = 0, start = 1;
1436 WCHAR txt_before = 0, txt_after = 0;
1438 for (;;)
1440 RTFGetToken( info );
1442 if (RTFCheckCMM( info, rtfControl, rtfDestination, rtfParNumTextBefore ) ||
1443 RTFCheckCMM( info, rtfControl, rtfDestination, rtfParNumTextAfter ))
1445 int loc = info->rtfMinor;
1447 RTFGetToken( info );
1448 if (info->rtfClass == rtfText)
1450 if (loc == rtfParNumTextBefore)
1451 txt_before = info->rtfMajor;
1452 else
1453 txt_after = info->rtfMajor;
1454 continue;
1456 /* falling through to catch EOFs and group level changes */
1459 if (info->rtfClass == rtfEOF)
1460 return;
1462 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1464 if (--level == 0) break;
1465 continue;
1468 if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1470 level++;
1471 continue;
1474 /* Ignore non para-attr */
1475 if (!RTFCheckCM( info, rtfControl, rtfParAttr ))
1476 continue;
1478 switch (info->rtfMinor)
1480 case rtfParLevel: /* Para level is ignored */
1481 case rtfParSimple:
1482 break;
1483 case rtfParBullet:
1484 type = PFN_BULLET;
1485 break;
1487 case rtfParNumDecimal:
1488 type = PFN_ARABIC;
1489 break;
1490 case rtfParNumULetter:
1491 type = PFN_UCLETTER;
1492 break;
1493 case rtfParNumURoman:
1494 type = PFN_UCROMAN;
1495 break;
1496 case rtfParNumLLetter:
1497 type = PFN_LCLETTER;
1498 break;
1499 case rtfParNumLRoman:
1500 type = PFN_LCROMAN;
1501 break;
1503 case rtfParNumIndent:
1504 indent = info->rtfParam;
1505 break;
1506 case rtfParNumStartAt:
1507 start = info->rtfParam;
1508 break;
1512 if (type != -1)
1514 info->fmt.dwMask |= (PFM_NUMBERING | PFM_NUMBERINGSTART | PFM_NUMBERINGSTYLE | PFM_NUMBERINGTAB);
1515 info->fmt.wNumbering = type;
1516 info->fmt.wNumberingStart = start;
1517 info->fmt.wNumberingStyle = PFNS_PAREN;
1518 if (type != PFN_BULLET)
1520 if (txt_before == 0 && txt_after == 0)
1521 info->fmt.wNumberingStyle = PFNS_PLAIN;
1522 else if (txt_after == '.')
1523 info->fmt.wNumberingStyle = PFNS_PERIOD;
1524 else if (txt_before == '(' && txt_after == ')')
1525 info->fmt.wNumberingStyle = PFNS_PARENS;
1527 info->fmt.wNumberingTab = indent;
1530 TRACE("type %d indent %d start %d txt before %04x txt after %04x\n",
1531 type, indent, start, txt_before, txt_after);
1533 RTFRouteToken( info ); /* feed "}" back to router */
1536 static void ME_RTFReadHook(RTF_Info *info)
1538 switch(info->rtfClass)
1540 case rtfGroup:
1541 switch(info->rtfMajor)
1543 case rtfBeginGroup:
1544 if (info->stackTop < maxStack) {
1545 info->stack[info->stackTop].style = info->style;
1546 ME_AddRefStyle(info->style);
1547 info->stack[info->stackTop].codePage = info->codePage;
1548 info->stack[info->stackTop].unicodeLength = info->unicodeLength;
1550 info->stackTop++;
1551 info->styleChanged = FALSE;
1552 break;
1553 case rtfEndGroup:
1555 RTFFlushOutputBuffer(info);
1556 info->stackTop--;
1557 if (info->stackTop <= 0)
1558 info->rtfClass = rtfEOF;
1559 if (info->stackTop < 0)
1560 return;
1562 ME_ReleaseStyle(info->style);
1563 info->style = info->stack[info->stackTop].style;
1564 info->codePage = info->stack[info->stackTop].codePage;
1565 info->unicodeLength = info->stack[info->stackTop].unicodeLength;
1566 break;
1569 break;
1573 void
1574 ME_StreamInFill(ME_InStream *stream)
1576 stream->editstream->dwError = stream->editstream->pfnCallback(stream->editstream->dwCookie,
1577 (BYTE *)stream->buffer,
1578 sizeof(stream->buffer),
1579 (LONG *)&stream->dwSize);
1580 stream->dwUsed = 0;
1583 static LRESULT ME_StreamIn(ME_TextEditor *editor, DWORD format, EDITSTREAM *stream, BOOL stripLastCR)
1585 RTF_Info parser;
1586 ME_Style *style;
1587 int from, to, nUndoMode;
1588 int nEventMask = editor->nEventMask;
1589 ME_InStream inStream;
1590 BOOL invalidRTF = FALSE;
1591 ME_Cursor *selStart, *selEnd;
1592 LRESULT num_read = 0; /* bytes read for SF_TEXT, non-control chars inserted for SF_RTF */
1594 TRACE("stream==%p editor==%p format==0x%X\n", stream, editor, format);
1595 editor->nEventMask = 0;
1597 ME_GetSelectionOfs(editor, &from, &to);
1598 if (format & SFF_SELECTION && editor->mode & TM_RICHTEXT)
1600 ME_GetSelection(editor, &selStart, &selEnd);
1601 style = ME_GetSelectionInsertStyle(editor);
1603 ME_InternalDeleteText(editor, selStart, to - from, FALSE);
1605 /* Don't insert text at the end of the table row */
1606 if (!editor->bEmulateVersion10) /* v4.1 */
1608 ME_Paragraph *para = editor->pCursors->para;
1609 if (para->nFlags & (MEPF_ROWSTART | MEPF_ROWEND))
1611 para = para_next( para );
1612 editor->pCursors[0].para = para;
1613 editor->pCursors[0].run = para_first_run( para );
1614 editor->pCursors[0].nOffset = 0;
1616 editor->pCursors[1] = editor->pCursors[0];
1618 else /* v1.0 - 3.0 */
1620 if (editor->pCursors[0].run->nFlags & MERF_ENDPARA &&
1621 para_in_table( editor->pCursors[0].para ))
1622 return 0;
1625 else
1627 style = editor->pBuffer->pDefaultStyle;
1628 ME_AddRefStyle(style);
1629 set_selection_cursors(editor, 0, 0);
1630 ME_InternalDeleteText(editor, &editor->pCursors[1],
1631 ME_GetTextLength(editor), FALSE);
1632 from = to = 0;
1633 ME_ClearTempStyle(editor);
1634 editor_set_default_para_fmt( editor, &editor->pCursors[0].para->fmt );
1638 /* Back up undo mode to a local variable */
1639 nUndoMode = editor->nUndoMode;
1641 /* Only create an undo if SFF_SELECTION is set */
1642 if (!(format & SFF_SELECTION))
1643 editor->nUndoMode = umIgnore;
1645 inStream.editstream = stream;
1646 inStream.editstream->dwError = 0;
1647 inStream.dwSize = 0;
1648 inStream.dwUsed = 0;
1650 if (format & SF_RTF)
1652 /* Check if it's really RTF, and if it is not, use plain text */
1653 ME_StreamInFill(&inStream);
1654 if (!inStream.editstream->dwError)
1656 if ((!editor->bEmulateVersion10 && strncmp(inStream.buffer, "{\\rtf", 5) && strncmp(inStream.buffer, "{\\urtf", 6))
1657 || (editor->bEmulateVersion10 && *inStream.buffer != '{'))
1659 invalidRTF = TRUE;
1660 inStream.editstream->dwError = -16;
1665 if (!invalidRTF && !inStream.editstream->dwError)
1667 ME_Cursor start;
1668 from = ME_GetCursorOfs(&editor->pCursors[0]);
1669 if (format & SF_RTF) {
1671 /* setup the RTF parser */
1672 memset(&parser, 0, sizeof parser);
1673 RTFSetEditStream(&parser, &inStream);
1674 parser.rtfFormat = format&(SF_TEXT|SF_RTF);
1675 parser.editor = editor;
1676 parser.style = style;
1677 WriterInit(&parser);
1678 RTFInit(&parser);
1679 RTFSetReadHook(&parser, ME_RTFReadHook);
1680 RTFSetDestinationCallback(&parser, rtfShpPict, ME_RTFReadShpPictGroup);
1681 RTFSetDestinationCallback(&parser, rtfPict, ME_RTFReadPictGroup);
1682 RTFSetDestinationCallback(&parser, rtfObject, ME_RTFReadObjectGroup);
1683 RTFSetDestinationCallback(&parser, rtfParNumbering, ME_RTFReadParnumGroup);
1684 if (!parser.editor->bEmulateVersion10) /* v4.1 */
1686 RTFSetDestinationCallback(&parser, rtfNoNestTables, RTFSkipGroup);
1687 RTFSetDestinationCallback(&parser, rtfNestTableProps, RTFReadGroup);
1689 BeginFile(&parser);
1691 /* do the parsing */
1692 RTFRead(&parser);
1693 RTFFlushOutputBuffer(&parser);
1694 if (!editor->bEmulateVersion10) /* v4.1 */
1696 if (parser.tableDef && parser.tableDef->row_start &&
1697 (parser.nestingLevel > 0 || parser.canInheritInTbl))
1699 /* Delete any incomplete table row at the end of the rich text. */
1700 int nOfs, nChars;
1701 ME_Paragraph *para;
1703 parser.rtfMinor = rtfRow;
1704 /* Complete the table row before deleting it.
1705 * By doing it this way we will have the current paragraph format set
1706 * properly to reflect that is not in the complete table, and undo items
1707 * will be added for this change to the current paragraph format. */
1708 if (parser.nestingLevel > 0)
1710 while (parser.nestingLevel > 1)
1711 ME_RTFSpecialCharHook(&parser); /* Decrements nestingLevel */
1712 para = parser.tableDef->row_start;
1713 ME_RTFSpecialCharHook(&parser);
1715 else
1717 para = parser.tableDef->row_start;
1718 ME_RTFSpecialCharHook(&parser);
1719 assert( para->nFlags & MEPF_ROWEND );
1720 para = para_next( para );
1723 editor->pCursors[1].para = para;
1724 editor->pCursors[1].run = para_first_run( para );
1725 editor->pCursors[1].nOffset = 0;
1726 nOfs = ME_GetCursorOfs(&editor->pCursors[1]);
1727 nChars = ME_GetCursorOfs(&editor->pCursors[0]) - nOfs;
1728 ME_InternalDeleteText(editor, &editor->pCursors[1], nChars, TRUE);
1729 if (parser.tableDef) parser.tableDef->row_start = NULL;
1732 RTFDestroy(&parser);
1734 if (parser.stackTop > 0)
1736 while (--parser.stackTop >= 0)
1738 ME_ReleaseStyle(parser.style);
1739 parser.style = parser.stack[parser.stackTop].style;
1741 if (!inStream.editstream->dwError)
1742 inStream.editstream->dwError = HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
1745 /* Remove last line break, as mandated by tests. This is not affected by
1746 CR/LF counters, since RTF streaming presents only \para tokens, which
1747 are converted according to the standard rules: \r for 2.0, \r\n for 1.0
1749 if (stripLastCR && !(format & SFF_SELECTION)) {
1750 int newto;
1751 ME_GetSelection(editor, &selStart, &selEnd);
1752 newto = ME_GetCursorOfs(selEnd);
1753 if (newto > to + (editor->bEmulateVersion10 ? 1 : 0)) {
1754 WCHAR lastchar[3] = {'\0', '\0'};
1755 int linebreakSize = editor->bEmulateVersion10 ? 2 : 1;
1756 ME_Cursor linebreakCursor = *selEnd, lastcharCursor = *selEnd;
1757 CHARFORMAT2W cf;
1759 /* Set the final eop to the char fmt of the last char */
1760 cf.cbSize = sizeof(cf);
1761 cf.dwMask = CFM_ALL2;
1762 ME_MoveCursorChars(editor, &lastcharCursor, -1, FALSE);
1763 ME_GetCharFormat(editor, &lastcharCursor, &linebreakCursor, &cf);
1764 set_selection_cursors(editor, newto, -1);
1765 ME_SetSelectionCharFormat(editor, &cf);
1766 set_selection_cursors(editor, newto, newto);
1768 ME_MoveCursorChars(editor, &linebreakCursor, -linebreakSize, FALSE);
1769 ME_GetTextW(editor, lastchar, 2, &linebreakCursor, linebreakSize, FALSE, FALSE);
1770 if (lastchar[0] == '\r' && (lastchar[1] == '\n' || lastchar[1] == '\0')) {
1771 ME_InternalDeleteText(editor, &linebreakCursor, linebreakSize, FALSE);
1775 to = ME_GetCursorOfs(&editor->pCursors[0]);
1776 num_read = to - from;
1778 style = parser.style;
1780 else if (format & SF_TEXT)
1782 num_read = ME_StreamInText(editor, format, &inStream, style);
1783 to = ME_GetCursorOfs(&editor->pCursors[0]);
1785 else
1786 ERR("EM_STREAMIN without SF_TEXT or SF_RTF\n");
1787 /* put the cursor at the top */
1788 if (!(format & SFF_SELECTION))
1789 set_selection_cursors(editor, 0, 0);
1790 cursor_from_char_ofs( editor, from, &start );
1791 ME_UpdateLinkAttribute(editor, &start, to - from);
1794 /* Restore saved undo mode */
1795 editor->nUndoMode = nUndoMode;
1797 /* even if we didn't add an undo, we need to commit anything on the stack */
1798 ME_CommitUndo(editor);
1800 /* If SFF_SELECTION isn't set, delete any undos from before we started too */
1801 if (!(format & SFF_SELECTION))
1802 ME_EmptyUndoStack(editor);
1804 ME_ReleaseStyle(style);
1805 editor->nEventMask = nEventMask;
1806 ME_UpdateRepaint(editor, FALSE);
1807 if (!(format & SFF_SELECTION)) {
1808 ME_ClearTempStyle(editor);
1810 update_caret(editor);
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 cursor_from_char_ofs( editor, nMin - 1, &cursor );
1920 wLastChar = *get_text( cursor.run, cursor.nOffset );
1921 ME_MoveCursorChars(editor, &cursor, 1, FALSE);
1923 else cursor_from_char_ofs( editor, nMin, &cursor );
1925 while (cursor.run && ME_GetCursorOfs(&cursor) + nLen <= nMax)
1927 ME_Run *run = cursor.run;
1928 int nCurStart = cursor.nOffset;
1929 int nMatched = 0;
1931 while (run && ME_CharCompare( *get_text( run, nCurStart + nMatched ), text[nMatched], (flags & FR_MATCHCASE)))
1933 if ((flags & FR_WHOLEWORD) && iswalnum(wLastChar))
1934 break;
1936 nMatched++;
1937 if (nMatched == nLen)
1939 ME_Run *next_run = run;
1940 int nNextStart = nCurStart;
1941 WCHAR wNextChar;
1943 /* Check to see if next character is a whitespace */
1944 if (flags & FR_WHOLEWORD)
1946 if (nCurStart + nMatched == run->len)
1948 next_run = run_next_all_paras( run );
1949 nNextStart = -nMatched;
1952 if (next_run)
1953 wNextChar = *get_text( next_run, nNextStart + nMatched );
1954 else
1955 wNextChar = ' ';
1957 if (iswalnum(wNextChar))
1958 break;
1961 cursor.nOffset += cursor.para->nCharOfs + cursor.run->nCharOfs;
1962 if (chrgText)
1964 chrgText->cpMin = cursor.nOffset;
1965 chrgText->cpMax = cursor.nOffset + nLen;
1967 TRACE("found at %d-%d\n", cursor.nOffset, cursor.nOffset + nLen);
1968 return cursor.nOffset;
1970 if (nCurStart + nMatched == run->len)
1972 run = run_next_all_paras( run );
1973 nCurStart = -nMatched;
1976 if (run)
1977 wLastChar = *get_text( run, nCurStart + nMatched );
1978 else
1979 wLastChar = ' ';
1981 cursor.nOffset++;
1982 if (cursor.nOffset == cursor.run->len)
1984 if (run_next_all_paras( cursor.run ))
1986 cursor.run = run_next_all_paras( cursor.run );
1987 cursor.para = cursor.run->para;
1988 cursor.nOffset = 0;
1990 else
1991 cursor.run = NULL;
1995 else /* Backward search */
1997 /* If possible, find the character after where the search ends */
1998 if ((flags & FR_WHOLEWORD) && nMax < nTextLen - 1)
2000 cursor_from_char_ofs( editor, nMax + 1, &cursor );
2001 wLastChar = *get_text( cursor.run, cursor.nOffset );
2002 ME_MoveCursorChars(editor, &cursor, -1, FALSE);
2004 else cursor_from_char_ofs( editor, nMax, &cursor );
2006 while (cursor.run && ME_GetCursorOfs(&cursor) - nLen >= nMin)
2008 ME_Run *run = cursor.run;
2009 ME_Paragraph *para = cursor.para;
2010 int nCurEnd = cursor.nOffset;
2011 int nMatched = 0;
2013 if (nCurEnd == 0 && run_prev_all_paras( run ))
2015 run = run_prev_all_paras( run );
2016 para = run->para;
2017 nCurEnd = run->len;
2020 while (run && ME_CharCompare( *get_text( run, nCurEnd - nMatched - 1 ),
2021 text[nLen - nMatched - 1], (flags & FR_MATCHCASE) ))
2023 if ((flags & FR_WHOLEWORD) && iswalnum(wLastChar))
2024 break;
2026 nMatched++;
2027 if (nMatched == nLen)
2029 ME_Run *prev_run = run;
2030 int nPrevEnd = nCurEnd;
2031 WCHAR wPrevChar;
2032 int nStart;
2034 /* Check to see if previous character is a whitespace */
2035 if (flags & FR_WHOLEWORD)
2037 if (nPrevEnd - nMatched == 0)
2039 prev_run = run_prev_all_paras( run );
2040 if (prev_run) nPrevEnd = prev_run->len + nMatched;
2043 if (prev_run) wPrevChar = *get_text( prev_run, nPrevEnd - nMatched - 1 );
2044 else wPrevChar = ' ';
2046 if (iswalnum(wPrevChar))
2047 break;
2050 nStart = para->nCharOfs + run->nCharOfs + nCurEnd - nMatched;
2051 if (chrgText)
2053 chrgText->cpMin = nStart;
2054 chrgText->cpMax = nStart + nLen;
2056 TRACE("found at %d-%d\n", nStart, nStart + nLen);
2057 return nStart;
2059 if (nCurEnd - nMatched == 0)
2061 if (run_prev_all_paras( run ))
2063 run = run_prev_all_paras( run );
2064 para = run->para;
2066 /* Don't care about pCurItem becoming NULL here; it's already taken
2067 * care of in the exterior loop condition */
2068 nCurEnd = run->len + nMatched;
2071 if (run)
2072 wLastChar = *get_text( run, nCurEnd - nMatched - 1 );
2073 else
2074 wLastChar = ' ';
2076 cursor.nOffset--;
2077 if (cursor.nOffset < 0)
2079 if (run_prev_all_paras( cursor.run ) )
2081 cursor.run = run_prev_all_paras( cursor.run );
2082 cursor.para = cursor.run->para;
2083 cursor.nOffset = cursor.run->len;
2085 else
2086 cursor.run = NULL;
2090 TRACE("not found\n");
2091 if (chrgText)
2092 chrgText->cpMin = chrgText->cpMax = -1;
2093 return -1;
2096 static int ME_GetTextEx(ME_TextEditor *editor, GETTEXTEX *ex, LPARAM pText)
2098 int nChars;
2099 ME_Cursor start;
2101 if (!ex->cb || !pText) return 0;
2103 if (ex->flags & ~(GT_SELECTION | GT_USECRLF))
2104 FIXME("GETTEXTEX flags 0x%08x not supported\n", ex->flags & ~(GT_SELECTION | GT_USECRLF));
2106 if (ex->flags & GT_SELECTION)
2108 int from, to;
2109 int nStartCur = ME_GetSelectionOfs(editor, &from, &to);
2110 start = editor->pCursors[nStartCur];
2111 nChars = to - from;
2113 else
2115 ME_SetCursorToStart(editor, &start);
2116 nChars = INT_MAX;
2118 if (ex->codepage == CP_UNICODE)
2120 return ME_GetTextW(editor, (LPWSTR)pText, ex->cb / sizeof(WCHAR) - 1,
2121 &start, nChars, ex->flags & GT_USECRLF, FALSE);
2123 else
2125 /* potentially each char may be a CR, why calculate the exact value with O(N) when
2126 we can just take a bigger buffer? :)
2127 The above assumption still holds with CR/LF counters, since CR->CRLF expansion
2128 occurs only in richedit 2.0 mode, in which line breaks have only one CR
2130 int crlfmul = (ex->flags & GT_USECRLF) ? 2 : 1;
2131 DWORD buflen;
2132 LPWSTR buffer;
2133 LRESULT rc;
2135 buflen = min(crlfmul * nChars, ex->cb - 1);
2136 buffer = heap_alloc((buflen + 1) * sizeof(WCHAR));
2138 nChars = ME_GetTextW(editor, buffer, buflen, &start, nChars, ex->flags & GT_USECRLF, FALSE);
2139 rc = WideCharToMultiByte(ex->codepage, 0, buffer, nChars + 1,
2140 (LPSTR)pText, ex->cb, ex->lpDefaultChar, ex->lpUsedDefChar);
2141 if (rc) rc--; /* do not count 0 terminator */
2143 heap_free(buffer);
2144 return rc;
2148 static int ME_GetTextRange(ME_TextEditor *editor, WCHAR *strText,
2149 const ME_Cursor *start, int nLen, BOOL unicode)
2151 if (!strText) return 0;
2152 if (unicode) {
2153 return ME_GetTextW(editor, strText, INT_MAX, start, nLen, FALSE, FALSE);
2154 } else {
2155 int nChars;
2156 WCHAR *p = heap_alloc((nLen+1) * sizeof(*p));
2157 if (!p) return 0;
2158 nChars = ME_GetTextW(editor, p, nLen, start, nLen, FALSE, FALSE);
2159 WideCharToMultiByte(CP_ACP, 0, p, nChars+1, (char *)strText,
2160 nLen+1, NULL, NULL);
2161 heap_free(p);
2162 return nChars;
2166 int set_selection( ME_TextEditor *editor, int to, int from )
2168 int end;
2170 TRACE("%d - %d\n", to, from );
2172 if (!editor->bHideSelection) ME_InvalidateSelection( editor );
2173 end = set_selection_cursors( editor, to, from );
2174 if (!editor->bHideSelection) ME_InvalidateSelection( editor );
2175 update_caret( editor );
2176 ME_SendSelChange( editor );
2178 return end;
2181 typedef struct tagME_GlobalDestStruct
2183 HGLOBAL hData;
2184 int nLength;
2185 } ME_GlobalDestStruct;
2187 static DWORD CALLBACK ME_ReadFromHGLOBALUnicode(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
2189 ME_GlobalDestStruct *pData = (ME_GlobalDestStruct *)dwCookie;
2190 int i;
2191 WORD *pSrc, *pDest;
2193 cb = cb >> 1;
2194 pDest = (WORD *)lpBuff;
2195 pSrc = GlobalLock(pData->hData);
2196 for (i = 0; i<cb && pSrc[pData->nLength+i]; i++) {
2197 pDest[i] = pSrc[pData->nLength+i];
2199 pData->nLength += i;
2200 *pcb = 2*i;
2201 GlobalUnlock(pData->hData);
2202 return 0;
2205 static DWORD CALLBACK ME_ReadFromHGLOBALRTF(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
2207 ME_GlobalDestStruct *pData = (ME_GlobalDestStruct *)dwCookie;
2208 int i;
2209 BYTE *pSrc, *pDest;
2211 pDest = lpBuff;
2212 pSrc = GlobalLock(pData->hData);
2213 for (i = 0; i<cb && pSrc[pData->nLength+i]; i++) {
2214 pDest[i] = pSrc[pData->nLength+i];
2216 pData->nLength += i;
2217 *pcb = i;
2218 GlobalUnlock(pData->hData);
2219 return 0;
2222 static HRESULT paste_rtf(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2224 EDITSTREAM es;
2225 ME_GlobalDestStruct gds;
2226 HRESULT hr;
2228 gds.hData = med->u.hGlobal;
2229 gds.nLength = 0;
2230 es.dwCookie = (DWORD_PTR)&gds;
2231 es.pfnCallback = ME_ReadFromHGLOBALRTF;
2232 hr = ME_StreamIn( editor, SF_RTF | SFF_SELECTION, &es, FALSE ) == 0 ? E_FAIL : S_OK;
2233 ReleaseStgMedium( med );
2234 return hr;
2237 static HRESULT paste_text(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2239 EDITSTREAM es;
2240 ME_GlobalDestStruct gds;
2241 HRESULT hr;
2243 gds.hData = med->u.hGlobal;
2244 gds.nLength = 0;
2245 es.dwCookie = (DWORD_PTR)&gds;
2246 es.pfnCallback = ME_ReadFromHGLOBALUnicode;
2247 hr = ME_StreamIn( editor, SF_TEXT | SF_UNICODE | SFF_SELECTION, &es, FALSE ) == 0 ? E_FAIL : S_OK;
2248 ReleaseStgMedium( med );
2249 return hr;
2252 static HRESULT paste_emf(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2254 HRESULT hr;
2255 SIZEL sz = {0, 0};
2257 hr = insert_static_object( editor, med->u.hEnhMetaFile, NULL, &sz );
2258 if (SUCCEEDED(hr))
2260 ME_CommitUndo( editor );
2261 ME_UpdateRepaint( editor, FALSE );
2263 else
2264 ReleaseStgMedium( med );
2266 return hr;
2269 static struct paste_format
2271 FORMATETC fmt;
2272 HRESULT (*paste)(ME_TextEditor *, FORMATETC *, STGMEDIUM *);
2273 const WCHAR *name;
2274 } paste_formats[] =
2276 {{ -1, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, paste_rtf, L"Rich Text Format" },
2277 {{ CF_UNICODETEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, paste_text },
2278 {{ CF_ENHMETAFILE, NULL, DVASPECT_CONTENT, -1, TYMED_ENHMF }, paste_emf },
2279 {{ 0 }}
2282 static void init_paste_formats(void)
2284 struct paste_format *format;
2285 static int done;
2287 if (!done)
2289 for (format = paste_formats; format->fmt.cfFormat; format++)
2291 if (format->name)
2292 format->fmt.cfFormat = RegisterClipboardFormatW( format->name );
2294 done = 1;
2298 static BOOL paste_special(ME_TextEditor *editor, UINT cf, REPASTESPECIAL *ps, BOOL check_only)
2300 HRESULT hr;
2301 STGMEDIUM med;
2302 struct paste_format *format;
2303 IDataObject *data;
2305 /* Protect read-only edit control from modification */
2306 if (editor->styleFlags & ES_READONLY)
2308 if (!check_only)
2309 MessageBeep(MB_ICONERROR);
2310 return FALSE;
2313 init_paste_formats();
2315 if (ps && ps->dwAspect != DVASPECT_CONTENT)
2316 FIXME("Ignoring aspect %x\n", ps->dwAspect);
2318 hr = OleGetClipboard( &data );
2319 if (hr != S_OK) return FALSE;
2321 if (cf == CF_TEXT) cf = CF_UNICODETEXT;
2323 hr = S_FALSE;
2324 for (format = paste_formats; format->fmt.cfFormat; format++)
2326 if (cf && cf != format->fmt.cfFormat) continue;
2327 hr = IDataObject_QueryGetData( data, &format->fmt );
2328 if (hr == S_OK)
2330 if (!check_only)
2332 hr = IDataObject_GetData( data, &format->fmt, &med );
2333 if (hr != S_OK) goto done;
2334 hr = format->paste( editor, &format->fmt, &med );
2336 break;
2340 done:
2341 IDataObject_Release( data );
2343 return hr == S_OK;
2346 static HRESULT editor_copy( ME_TextEditor *editor, ME_Cursor *start, int chars, IDataObject **data_out )
2348 IDataObject *data = NULL;
2349 HRESULT hr = S_OK;
2351 if (editor->lpOleCallback)
2353 CHARRANGE range;
2354 range.cpMin = ME_GetCursorOfs( start );
2355 range.cpMax = range.cpMin + chars;
2356 hr = IRichEditOleCallback_GetClipboardData( editor->lpOleCallback, &range, RECO_COPY, &data );
2359 if (FAILED( hr ) || !data)
2360 hr = ME_GetDataObject( editor, start, chars, &data );
2362 if (SUCCEEDED( hr ))
2364 if (data_out)
2365 *data_out = data;
2366 else
2368 hr = OleSetClipboard( data );
2369 IDataObject_Release( data );
2373 return hr;
2376 HRESULT editor_copy_or_cut( ME_TextEditor *editor, BOOL cut, ME_Cursor *start, int count,
2377 IDataObject **data_out )
2379 HRESULT hr;
2381 if (cut && (editor->styleFlags & ES_READONLY))
2383 return E_ACCESSDENIED;
2386 hr = editor_copy( editor, start, count, data_out );
2387 if (SUCCEEDED(hr) && cut)
2389 ME_InternalDeleteText( editor, start, count, FALSE );
2390 ME_CommitUndo( editor );
2391 ME_UpdateRepaint( editor, TRUE );
2393 return hr;
2396 static BOOL copy_or_cut( ME_TextEditor *editor, BOOL cut )
2398 HRESULT hr;
2399 int offs, count;
2400 int start_cursor = ME_GetSelectionOfs( editor, &offs, &count );
2401 ME_Cursor *sel_start = &editor->pCursors[start_cursor];
2403 if (editor->cPasswordMask) return FALSE;
2405 count -= offs;
2406 hr = editor_copy_or_cut( editor, cut, sel_start, count, NULL );
2407 if (FAILED( hr )) MessageBeep( MB_ICONERROR );
2409 return SUCCEEDED( hr );
2412 /* helper to send a msg filter notification */
2413 static BOOL
2414 ME_FilterEvent(ME_TextEditor *editor, UINT msg, WPARAM* wParam, LPARAM* lParam)
2416 MSGFILTER msgf;
2418 if (!editor->hWnd || !editor->hwndParent) return FALSE;
2419 msgf.nmhdr.hwndFrom = editor->hWnd;
2420 msgf.nmhdr.idFrom = GetWindowLongW(editor->hWnd, GWLP_ID);
2421 msgf.nmhdr.code = EN_MSGFILTER;
2422 msgf.msg = msg;
2423 msgf.wParam = *wParam;
2424 msgf.lParam = *lParam;
2425 if (SendMessageW(editor->hwndParent, WM_NOTIFY, msgf.nmhdr.idFrom, (LPARAM)&msgf))
2426 return FALSE;
2427 *wParam = msgf.wParam;
2428 *lParam = msgf.lParam;
2429 msgf.wParam = *wParam;
2431 return TRUE;
2434 static void ME_UpdateSelectionLinkAttribute(ME_TextEditor *editor)
2436 ME_Paragraph *start_para, *end_para;
2437 ME_Cursor *from, *to, start;
2438 int num_chars;
2440 if (!editor->AutoURLDetect_bEnable) return;
2442 ME_GetSelection(editor, &from, &to);
2444 /* Find paragraph previous to the one that contains start cursor */
2445 start_para = from->para;
2446 if (para_prev( start_para )) start_para = para_prev( start_para );
2448 /* Find paragraph that contains end cursor */
2449 end_para = para_next( to->para );
2451 start.para = start_para;
2452 start.run = para_first_run( start_para );
2453 start.nOffset = 0;
2454 num_chars = end_para->nCharOfs - start_para->nCharOfs;
2456 ME_UpdateLinkAttribute( editor, &start, num_chars );
2459 static BOOL handle_enter(ME_TextEditor *editor)
2461 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2462 BOOL shift_is_down = GetKeyState(VK_SHIFT) & 0x8000;
2464 if (editor->bDialogMode)
2466 if (ctrl_is_down)
2467 return TRUE;
2469 if (!(editor->styleFlags & ES_WANTRETURN))
2471 if (editor->hwndParent)
2473 DWORD dw;
2474 dw = SendMessageW(editor->hwndParent, DM_GETDEFID, 0, 0);
2475 if (HIWORD(dw) == DC_HASDEFID)
2477 HWND hwDefCtrl = GetDlgItem(editor->hwndParent, LOWORD(dw));
2478 if (hwDefCtrl)
2480 SendMessageW(editor->hwndParent, WM_NEXTDLGCTL, (WPARAM)hwDefCtrl, TRUE);
2481 PostMessageW(hwDefCtrl, WM_KEYDOWN, VK_RETURN, 0);
2485 return TRUE;
2489 if (editor->styleFlags & ES_MULTILINE)
2491 static const WCHAR endl = '\r';
2492 static const WCHAR endlv10[] = {'\r','\n'};
2493 ME_Cursor cursor = editor->pCursors[0];
2494 ME_Paragraph *para = cursor.para;
2495 int from, to;
2496 ME_Style *style, *eop_style;
2498 if (editor->styleFlags & ES_READONLY)
2500 MessageBeep(MB_ICONERROR);
2501 return TRUE;
2504 ME_GetSelectionOfs(editor, &from, &to);
2505 if (editor->nTextLimit > ME_GetTextLength(editor) - (to-from))
2507 if (!editor->bEmulateVersion10) /* v4.1 */
2509 if (para->nFlags & MEPF_ROWEND)
2511 /* Add a new table row after this row. */
2512 para = table_append_row( editor, para );
2513 para = para_next( para );
2514 editor->pCursors[0].para = para;
2515 editor->pCursors[0].run = para_first_run( para );
2516 editor->pCursors[0].nOffset = 0;
2517 editor->pCursors[1] = editor->pCursors[0];
2518 ME_CommitUndo(editor);
2519 ME_UpdateRepaint(editor, FALSE);
2520 return TRUE;
2522 else if (para == editor->pCursors[1].para &&
2523 cursor.nOffset + cursor.run->nCharOfs == 0 &&
2524 para_prev( para ) && para_prev( para )->nFlags & MEPF_ROWSTART &&
2525 !para_prev( para )->nCharOfs)
2527 /* Insert a newline before the table. */
2528 para = para_prev( para );
2529 para->nFlags &= ~MEPF_ROWSTART;
2530 editor->pCursors[0].para = para;
2531 editor->pCursors[0].run = para_first_run( para );
2532 editor->pCursors[1] = editor->pCursors[0];
2533 ME_InsertTextFromCursor( editor, 0, &endl, 1, editor->pCursors[0].run->style );
2534 para = editor_first_para( editor );
2535 editor_set_default_para_fmt( editor, &para->fmt );
2536 para->nFlags = 0;
2537 para_mark_rewrap( editor, para );
2538 editor->pCursors[0].para = para;
2539 editor->pCursors[0].run = para_first_run( para );
2540 editor->pCursors[1] = editor->pCursors[0];
2541 para_next( para )->nFlags |= MEPF_ROWSTART;
2542 ME_CommitCoalescingUndo(editor);
2543 ME_UpdateRepaint(editor, FALSE);
2544 return TRUE;
2547 else /* v1.0 - 3.0 */
2549 ME_Paragraph *para = cursor.para;
2550 if (para_in_table( para ))
2552 if (cursor.run->nFlags & MERF_ENDPARA)
2554 if (from == to)
2556 ME_ContinueCoalescingTransaction(editor);
2557 para = table_append_row( editor, para );
2558 editor->pCursors[0].para = para;
2559 editor->pCursors[0].run = para_first_run( para );
2560 editor->pCursors[0].nOffset = 0;
2561 editor->pCursors[1] = editor->pCursors[0];
2562 ME_CommitCoalescingUndo(editor);
2563 ME_UpdateRepaint(editor, FALSE);
2564 return TRUE;
2567 else
2569 ME_ContinueCoalescingTransaction(editor);
2570 if (cursor.run->nCharOfs + cursor.nOffset == 0 &&
2571 para_prev( para ) && !para_in_table( para_prev( para ) ))
2573 /* Insert newline before table */
2574 cursor.run = para_end_run( para_prev( para ) );
2575 if (cursor.run)
2577 editor->pCursors[0].run = cursor.run;
2578 editor->pCursors[0].para = para_prev( para );
2580 editor->pCursors[0].nOffset = 0;
2581 editor->pCursors[1] = editor->pCursors[0];
2582 ME_InsertTextFromCursor( editor, 0, &endl, 1, editor->pCursors[0].run->style );
2584 else
2586 editor->pCursors[1] = editor->pCursors[0];
2587 para = table_append_row( editor, para );
2588 editor->pCursors[0].para = para;
2589 editor->pCursors[0].run = para_first_run( para );
2590 editor->pCursors[0].nOffset = 0;
2591 editor->pCursors[1] = editor->pCursors[0];
2593 ME_CommitCoalescingUndo(editor);
2594 ME_UpdateRepaint(editor, FALSE);
2595 return TRUE;
2600 style = style_get_insert_style( editor, editor->pCursors );
2602 /* Normally the new eop style is the insert style, however in a list it is copied from the existing
2603 eop style (this prevents the list label style changing when the new eop is inserted).
2604 No extra ref is taken here on eop_style. */
2605 if (para->fmt.wNumbering)
2606 eop_style = para->eop_run->style;
2607 else
2608 eop_style = style;
2609 ME_ContinueCoalescingTransaction(editor);
2610 if (shift_is_down)
2611 ME_InsertEndRowFromCursor(editor, 0);
2612 else
2613 if (!editor->bEmulateVersion10)
2614 ME_InsertTextFromCursor(editor, 0, &endl, 1, eop_style);
2615 else
2616 ME_InsertTextFromCursor(editor, 0, endlv10, 2, eop_style);
2617 ME_CommitCoalescingUndo(editor);
2618 SetCursor(NULL);
2620 ME_UpdateSelectionLinkAttribute(editor);
2621 ME_UpdateRepaint(editor, FALSE);
2622 ME_SaveTempStyle(editor, style); /* set the temp insert style for the new para */
2623 ME_ReleaseStyle(style);
2625 return TRUE;
2627 return FALSE;
2630 static BOOL
2631 ME_KeyDown(ME_TextEditor *editor, WORD nKey)
2633 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2634 BOOL shift_is_down = GetKeyState(VK_SHIFT) & 0x8000;
2636 if (editor->bMouseCaptured)
2637 return FALSE;
2638 if (nKey != VK_SHIFT && nKey != VK_CONTROL && nKey != VK_MENU)
2639 editor->nSelectionType = stPosition;
2641 switch (nKey)
2643 case VK_LEFT:
2644 case VK_RIGHT:
2645 case VK_HOME:
2646 case VK_END:
2647 editor->nUDArrowX = -1;
2648 /* fall through */
2649 case VK_UP:
2650 case VK_DOWN:
2651 case VK_PRIOR:
2652 case VK_NEXT:
2653 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
2654 ME_ArrowKey(editor, nKey, shift_is_down, ctrl_is_down);
2655 return TRUE;
2656 case VK_BACK:
2657 case VK_DELETE:
2658 editor->nUDArrowX = -1;
2659 /* FIXME backspace and delete aren't the same, they act different wrt paragraph style of the merged paragraph */
2660 if (editor->styleFlags & ES_READONLY)
2661 return FALSE;
2662 if (ME_IsSelection(editor))
2664 ME_DeleteSelection(editor);
2665 ME_CommitUndo(editor);
2667 else if (nKey == VK_DELETE)
2669 /* Delete stops group typing.
2670 * (See MSDN remarks on EM_STOPGROUPTYPING message) */
2671 ME_DeleteTextAtCursor(editor, 1, 1);
2672 ME_CommitUndo(editor);
2674 else if (ME_ArrowKey(editor, VK_LEFT, FALSE, FALSE))
2676 BOOL bDeletionSucceeded;
2677 /* Backspace can be grouped for a single undo */
2678 ME_ContinueCoalescingTransaction(editor);
2679 bDeletionSucceeded = ME_DeleteTextAtCursor(editor, 1, 1);
2680 if (!bDeletionSucceeded && !editor->bEmulateVersion10) { /* v4.1 */
2681 /* Deletion was prevented so the cursor is moved back to where it was.
2682 * (e.g. this happens when trying to delete cell boundaries)
2684 ME_ArrowKey(editor, VK_RIGHT, FALSE, FALSE);
2686 ME_CommitCoalescingUndo(editor);
2688 else
2689 return TRUE;
2690 table_move_from_row_start( editor );
2691 ME_UpdateSelectionLinkAttribute(editor);
2692 ME_UpdateRepaint(editor, FALSE);
2693 ME_SendRequestResize(editor, FALSE);
2694 return TRUE;
2695 case VK_RETURN:
2696 if (!editor->bEmulateVersion10)
2697 return handle_enter(editor);
2698 break;
2699 case VK_ESCAPE:
2700 if (editor->bDialogMode && editor->hwndParent)
2701 PostMessageW(editor->hwndParent, WM_CLOSE, 0, 0);
2702 return TRUE;
2703 case VK_TAB:
2704 if (editor->bDialogMode && editor->hwndParent)
2705 SendMessageW(editor->hwndParent, WM_NEXTDLGCTL, shift_is_down, 0);
2706 return TRUE;
2707 case 'A':
2708 if (ctrl_is_down)
2710 set_selection( editor, 0, -1 );
2711 return TRUE;
2713 break;
2714 case 'V':
2715 if (ctrl_is_down)
2716 return paste_special( editor, 0, NULL, FALSE );
2717 break;
2718 case 'C':
2719 case 'X':
2720 if (ctrl_is_down)
2721 return copy_or_cut(editor, nKey == 'X');
2722 break;
2723 case 'Z':
2724 if (ctrl_is_down)
2726 ME_Undo(editor);
2727 return TRUE;
2729 break;
2730 case 'Y':
2731 if (ctrl_is_down)
2733 ME_Redo(editor);
2734 return TRUE;
2736 break;
2738 default:
2739 if (nKey != VK_SHIFT && nKey != VK_CONTROL && nKey && nKey != VK_MENU)
2740 editor->nUDArrowX = -1;
2741 if (ctrl_is_down)
2743 if (nKey == 'W')
2745 CHARFORMAT2W chf;
2746 char buf[2048];
2747 chf.cbSize = sizeof(chf);
2749 ME_GetSelectionCharFormat(editor, &chf);
2750 ME_DumpStyleToBuf(&chf, buf);
2751 MessageBoxA(NULL, buf, "Style dump", MB_OK);
2753 if (nKey == 'Q')
2755 ME_CheckCharOffsets(editor);
2759 return FALSE;
2762 static LRESULT ME_Char(ME_TextEditor *editor, WPARAM charCode,
2763 LPARAM flags, BOOL unicode)
2765 WCHAR wstr;
2767 if (editor->bMouseCaptured)
2768 return 0;
2770 if (editor->styleFlags & ES_READONLY)
2772 MessageBeep(MB_ICONERROR);
2773 return 0; /* FIXME really 0 ? */
2776 if (unicode)
2777 wstr = (WCHAR)charCode;
2778 else
2780 CHAR charA = charCode;
2781 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &wstr, 1);
2784 if (editor->bEmulateVersion10 && wstr == '\r')
2785 handle_enter(editor);
2787 if ((unsigned)wstr >= ' ' || wstr == '\t')
2789 ME_Cursor cursor = editor->pCursors[0];
2790 ME_Paragraph *para = cursor.para;
2791 int from, to;
2792 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2793 ME_GetSelectionOfs(editor, &from, &to);
2794 if (wstr == '\t' &&
2795 /* v4.1 allows tabs to be inserted with ctrl key down */
2796 !(ctrl_is_down && !editor->bEmulateVersion10))
2798 BOOL selected_row = FALSE;
2800 if (ME_IsSelection(editor) &&
2801 cursor.run->nCharOfs + cursor.nOffset == 0 &&
2802 to == ME_GetCursorOfs(&editor->pCursors[0]) && para_prev( para ))
2804 para = para_prev( para );
2805 selected_row = TRUE;
2807 if (para_in_table( para ))
2809 table_handle_tab( editor, selected_row );
2810 ME_CommitUndo(editor);
2811 return 0;
2814 else if (!editor->bEmulateVersion10) /* v4.1 */
2816 if (para->nFlags & MEPF_ROWEND)
2818 if (from == to)
2820 para = para_next( para );
2821 if (para->nFlags & MEPF_ROWSTART) para = para_next( para );
2822 editor->pCursors[0].para = para;
2823 editor->pCursors[0].run = para_first_run( para );
2824 editor->pCursors[0].nOffset = 0;
2825 editor->pCursors[1] = editor->pCursors[0];
2829 else /* v1.0 - 3.0 */
2831 if (para_in_table( para ) && cursor.run->nFlags & MERF_ENDPARA && from == to)
2833 /* Text should not be inserted at the end of the table. */
2834 MessageBeep(-1);
2835 return 0;
2838 /* FIXME maybe it would make sense to call EM_REPLACESEL instead ? */
2839 /* WM_CHAR is restricted to nTextLimit */
2840 if(editor->nTextLimit > ME_GetTextLength(editor) - (to-from))
2842 ME_Style *style = style_get_insert_style( editor, editor->pCursors );
2843 ME_ContinueCoalescingTransaction(editor);
2844 ME_InsertTextFromCursor(editor, 0, &wstr, 1, style);
2845 ME_ReleaseStyle(style);
2846 ME_CommitCoalescingUndo(editor);
2847 ITextHost_TxSetCursor(editor->texthost, NULL, FALSE);
2850 ME_UpdateSelectionLinkAttribute(editor);
2851 ME_UpdateRepaint(editor, FALSE);
2853 return 0;
2856 /* Process the message and calculate the new click count.
2858 * returns: The click count if it is mouse down event, else returns 0. */
2859 static int ME_CalculateClickCount(ME_TextEditor *editor, UINT msg, WPARAM wParam,
2860 LPARAM lParam)
2862 static int clickNum = 0;
2863 if (msg < WM_MOUSEFIRST || msg > WM_MOUSELAST)
2864 return 0;
2866 if ((msg == WM_LBUTTONDBLCLK) ||
2867 (msg == WM_RBUTTONDBLCLK) ||
2868 (msg == WM_MBUTTONDBLCLK) ||
2869 (msg == WM_XBUTTONDBLCLK))
2871 msg -= (WM_LBUTTONDBLCLK - WM_LBUTTONDOWN);
2874 if ((msg == WM_LBUTTONDOWN) ||
2875 (msg == WM_RBUTTONDOWN) ||
2876 (msg == WM_MBUTTONDOWN) ||
2877 (msg == WM_XBUTTONDOWN))
2879 static MSG prevClickMsg;
2880 MSG clickMsg;
2881 /* Compare the editor instead of the hwnd so that the this
2882 * can still be done for windowless richedit controls. */
2883 clickMsg.hwnd = (HWND)editor;
2884 clickMsg.message = msg;
2885 clickMsg.wParam = wParam;
2886 clickMsg.lParam = lParam;
2887 clickMsg.time = GetMessageTime();
2888 clickMsg.pt.x = (short)LOWORD(lParam);
2889 clickMsg.pt.y = (short)HIWORD(lParam);
2890 if ((clickNum != 0) &&
2891 (clickMsg.message == prevClickMsg.message) &&
2892 (clickMsg.hwnd == prevClickMsg.hwnd) &&
2893 (clickMsg.wParam == prevClickMsg.wParam) &&
2894 (clickMsg.time - prevClickMsg.time < GetDoubleClickTime()) &&
2895 (abs(clickMsg.pt.x - prevClickMsg.pt.x) < GetSystemMetrics(SM_CXDOUBLECLK)/2) &&
2896 (abs(clickMsg.pt.y - prevClickMsg.pt.y) < GetSystemMetrics(SM_CYDOUBLECLK)/2))
2898 clickNum++;
2899 } else {
2900 clickNum = 1;
2902 prevClickMsg = clickMsg;
2903 } else {
2904 return 0;
2906 return clickNum;
2909 static BOOL is_link( ME_Run *run )
2911 return (run->style->fmt.dwMask & CFM_LINK) && (run->style->fmt.dwEffects & CFE_LINK);
2914 static BOOL ME_SetCursor(ME_TextEditor *editor)
2916 ME_Cursor cursor;
2917 POINT pt;
2918 BOOL isExact;
2919 SCROLLBARINFO sbi;
2920 DWORD messagePos = GetMessagePos();
2921 pt.x = (short)LOWORD(messagePos);
2922 pt.y = (short)HIWORD(messagePos);
2924 if (editor->hWnd)
2926 sbi.cbSize = sizeof(sbi);
2927 GetScrollBarInfo(editor->hWnd, OBJID_HSCROLL, &sbi);
2928 if (!(sbi.rgstate[0] & (STATE_SYSTEM_INVISIBLE|STATE_SYSTEM_OFFSCREEN)) &&
2929 PtInRect(&sbi.rcScrollBar, pt))
2931 ITextHost_TxSetCursor(editor->texthost,
2932 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2933 return TRUE;
2935 sbi.cbSize = sizeof(sbi);
2936 GetScrollBarInfo(editor->hWnd, OBJID_VSCROLL, &sbi);
2937 if (!(sbi.rgstate[0] & (STATE_SYSTEM_INVISIBLE|STATE_SYSTEM_OFFSCREEN)) &&
2938 PtInRect(&sbi.rcScrollBar, pt))
2940 ITextHost_TxSetCursor(editor->texthost,
2941 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2942 return TRUE;
2945 ITextHost_TxScreenToClient(editor->texthost, &pt);
2947 if (editor->nSelectionType == stLine && editor->bMouseCaptured) {
2948 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2949 return TRUE;
2951 if (!editor->bEmulateVersion10 /* v4.1 */ &&
2952 pt.y < editor->rcFormat.top &&
2953 pt.x < editor->rcFormat.left)
2955 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2956 return TRUE;
2958 if (pt.y < editor->rcFormat.top || pt.y > editor->rcFormat.bottom)
2960 if (editor->bEmulateVersion10) /* v1.0 - 3.0 */
2961 ITextHost_TxSetCursor(editor->texthost,
2962 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2963 else /* v4.1 */
2964 ITextHost_TxSetCursor(editor->texthost,
2965 LoadCursorW(NULL, (WCHAR*)IDC_IBEAM), TRUE);
2966 return TRUE;
2968 if (pt.x < editor->rcFormat.left)
2970 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2971 return TRUE;
2973 ME_CharFromPos(editor, pt.x, pt.y, &cursor, &isExact);
2974 if (isExact)
2976 ME_Run *run = cursor.run;
2978 if (is_link( run ))
2980 ITextHost_TxSetCursor(editor->texthost,
2981 LoadCursorW(NULL, (WCHAR*)IDC_HAND),
2982 FALSE);
2983 return TRUE;
2986 if (ME_IsSelection(editor))
2988 int selStart, selEnd;
2989 int offset = ME_GetCursorOfs(&cursor);
2991 ME_GetSelectionOfs(editor, &selStart, &selEnd);
2992 if (selStart <= offset && selEnd >= offset) {
2993 ITextHost_TxSetCursor(editor->texthost,
2994 LoadCursorW(NULL, (WCHAR*)IDC_ARROW),
2995 FALSE);
2996 return TRUE;
3000 ITextHost_TxSetCursor(editor->texthost,
3001 LoadCursorW(NULL, (WCHAR*)IDC_IBEAM), TRUE);
3002 return TRUE;
3005 static void ME_SetDefaultFormatRect(ME_TextEditor *editor)
3007 ITextHost_TxGetClientRect(editor->texthost, &editor->rcFormat);
3008 editor->rcFormat.top += editor->exStyleFlags & WS_EX_CLIENTEDGE ? 1 : 0;
3009 editor->rcFormat.left += 1 + editor->selofs;
3010 editor->rcFormat.right -= 1;
3013 static LONG ME_GetSelectionType(ME_TextEditor *editor)
3015 LONG sel_type = SEL_EMPTY;
3016 LONG start, end;
3018 ME_GetSelectionOfs(editor, &start, &end);
3019 if (start == end)
3020 sel_type = SEL_EMPTY;
3021 else
3023 LONG object_count = 0, character_count = 0;
3024 int i;
3026 for (i = 0; i < end - start; i++)
3028 ME_Cursor cursor;
3030 cursor_from_char_ofs( editor, start + i, &cursor );
3031 if (cursor.run->reobj) object_count++;
3032 else character_count++;
3033 if (character_count >= 2 && object_count >= 2)
3034 return (SEL_TEXT | SEL_MULTICHAR | SEL_OBJECT | SEL_MULTIOBJECT);
3036 if (character_count)
3038 sel_type |= SEL_TEXT;
3039 if (character_count >= 2)
3040 sel_type |= SEL_MULTICHAR;
3042 if (object_count)
3044 sel_type |= SEL_OBJECT;
3045 if (object_count >= 2)
3046 sel_type |= SEL_MULTIOBJECT;
3049 return sel_type;
3052 static BOOL ME_ShowContextMenu(ME_TextEditor *editor, int x, int y)
3054 CHARRANGE selrange;
3055 HMENU menu;
3056 int seltype;
3058 if(!editor->lpOleCallback || !editor->hWnd)
3059 return FALSE;
3060 ME_GetSelectionOfs(editor, &selrange.cpMin, &selrange.cpMax);
3061 seltype = ME_GetSelectionType(editor);
3062 if(SUCCEEDED(IRichEditOleCallback_GetContextMenu(editor->lpOleCallback, seltype, NULL, &selrange, &menu)))
3064 TrackPopupMenu(menu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, x, y, 0, editor->hwndParent, NULL);
3065 DestroyMenu(menu);
3067 return TRUE;
3070 ME_TextEditor *ME_MakeEditor(ITextHost *texthost, BOOL bEmulateVersion10)
3072 ME_TextEditor *ed = heap_alloc(sizeof(*ed));
3073 int i;
3074 DWORD props;
3075 LONG selbarwidth;
3077 ed->hWnd = NULL;
3078 ed->hwndParent = NULL;
3079 ed->sizeWindow.cx = ed->sizeWindow.cy = 0;
3080 ed->texthost = texthost;
3081 ed->reOle = NULL;
3082 ed->bEmulateVersion10 = bEmulateVersion10;
3083 ed->styleFlags = 0;
3084 ed->exStyleFlags = 0;
3085 ed->total_rows = 0;
3086 ITextHost_TxGetPropertyBits(texthost,
3087 (TXTBIT_RICHTEXT|TXTBIT_MULTILINE|
3088 TXTBIT_READONLY|TXTBIT_USEPASSWORD|
3089 TXTBIT_HIDESELECTION|TXTBIT_SAVESELECTION|
3090 TXTBIT_AUTOWORDSEL|TXTBIT_VERTICAL|
3091 TXTBIT_WORDWRAP|TXTBIT_DISABLEDRAG),
3092 &props);
3093 ITextHost_TxGetScrollBars(texthost, &ed->styleFlags);
3094 ed->styleFlags &= (WS_VSCROLL|WS_HSCROLL|ES_AUTOVSCROLL|
3095 ES_AUTOHSCROLL|ES_DISABLENOSCROLL);
3096 ed->pBuffer = ME_MakeText();
3097 ed->nZoomNumerator = ed->nZoomDenominator = 0;
3098 ed->nAvailWidth = 0; /* wrap to client area */
3099 list_init( &ed->style_list );
3100 ME_MakeFirstParagraph(ed);
3101 /* The four cursors are for:
3102 * 0 - The position where the caret is shown
3103 * 1 - The anchored end of the selection (for normal selection)
3104 * 2 & 3 - The anchored start and end respectively for word, line,
3105 * or paragraph selection.
3107 ed->nCursors = 4;
3108 ed->pCursors = heap_alloc(ed->nCursors * sizeof(*ed->pCursors));
3109 ME_SetCursorToStart(ed, &ed->pCursors[0]);
3110 ed->pCursors[1] = ed->pCursors[0];
3111 ed->pCursors[2] = ed->pCursors[0];
3112 ed->pCursors[3] = ed->pCursors[1];
3113 ed->nLastTotalLength = ed->nTotalLength = 0;
3114 ed->nLastTotalWidth = ed->nTotalWidth = 0;
3115 ed->nUDArrowX = -1;
3116 ed->rgbBackColor = -1;
3117 ed->hbrBackground = GetSysColorBrush(COLOR_WINDOW);
3118 ed->nEventMask = 0;
3119 ed->nModifyStep = 0;
3120 ed->nTextLimit = TEXT_LIMIT_DEFAULT;
3121 list_init( &ed->undo_stack );
3122 list_init( &ed->redo_stack );
3123 ed->nUndoStackSize = 0;
3124 ed->nUndoLimit = STACK_SIZE_DEFAULT;
3125 ed->nUndoMode = umAddToUndo;
3126 ed->nParagraphs = 1;
3127 ed->nLastSelStart = ed->nLastSelEnd = 0;
3128 ed->last_sel_start_para = ed->last_sel_end_para = ed->pCursors[0].para;
3129 ed->bHideSelection = FALSE;
3130 ed->pfnWordBreak = NULL;
3131 ed->lpOleCallback = NULL;
3132 ed->mode = TM_MULTILEVELUNDO | TM_MULTICODEPAGE;
3133 ed->mode |= (props & TXTBIT_RICHTEXT) ? TM_RICHTEXT : TM_PLAINTEXT;
3134 ed->AutoURLDetect_bEnable = FALSE;
3135 ed->bHaveFocus = FALSE;
3136 ed->bDialogMode = FALSE;
3137 ed->bMouseCaptured = FALSE;
3138 ed->caret_hidden = FALSE;
3139 ed->caret_height = 0;
3140 for (i=0; i<HFONT_CACHE_SIZE; i++)
3142 ed->pFontCache[i].nRefs = 0;
3143 ed->pFontCache[i].nAge = 0;
3144 ed->pFontCache[i].hFont = NULL;
3147 ME_CheckCharOffsets(ed);
3148 SetRectEmpty(&ed->rcFormat);
3149 ed->bDefaultFormatRect = TRUE;
3150 ITextHost_TxGetSelectionBarWidth(ed->texthost, &selbarwidth);
3151 if (selbarwidth) {
3152 /* FIXME: Convert selbarwidth from HIMETRIC to pixels */
3153 ed->selofs = SELECTIONBAR_WIDTH;
3154 ed->styleFlags |= ES_SELECTIONBAR;
3155 } else {
3156 ed->selofs = 0;
3158 ed->nSelectionType = stPosition;
3160 ed->cPasswordMask = 0;
3161 if (props & TXTBIT_USEPASSWORD)
3162 ITextHost_TxGetPasswordChar(texthost, &ed->cPasswordMask);
3164 if (props & TXTBIT_AUTOWORDSEL)
3165 ed->styleFlags |= ECO_AUTOWORDSELECTION;
3166 if (props & TXTBIT_MULTILINE) {
3167 ed->styleFlags |= ES_MULTILINE;
3168 ed->bWordWrap = (props & TXTBIT_WORDWRAP) != 0;
3169 } else {
3170 ed->bWordWrap = FALSE;
3172 if (props & TXTBIT_READONLY)
3173 ed->styleFlags |= ES_READONLY;
3174 if (!(props & TXTBIT_HIDESELECTION))
3175 ed->styleFlags |= ES_NOHIDESEL;
3176 if (props & TXTBIT_SAVESELECTION)
3177 ed->styleFlags |= ES_SAVESEL;
3178 if (props & TXTBIT_VERTICAL)
3179 ed->styleFlags |= ES_VERTICAL;
3180 if (props & TXTBIT_DISABLEDRAG)
3181 ed->styleFlags |= ES_NOOLEDRAGDROP;
3183 ed->notified_cr.cpMin = ed->notified_cr.cpMax = 0;
3185 /* Default scrollbar information */
3186 ed->vert_si.cbSize = sizeof(SCROLLINFO);
3187 ed->vert_si.nMin = 0;
3188 ed->vert_si.nMax = 0;
3189 ed->vert_si.nPage = 0;
3190 ed->vert_si.nPos = 0;
3192 ed->horz_si.cbSize = sizeof(SCROLLINFO);
3193 ed->horz_si.nMin = 0;
3194 ed->horz_si.nMax = 0;
3195 ed->horz_si.nPage = 0;
3196 ed->horz_si.nPos = 0;
3198 ed->wheel_remain = 0;
3200 list_init( &ed->reobj_list );
3201 OleInitialize(NULL);
3203 return ed;
3206 void ME_DestroyEditor(ME_TextEditor *editor)
3208 ME_DisplayItem *p = editor->pBuffer->pFirst, *pNext = NULL;
3209 ME_Style *s, *cursor2;
3210 int i;
3212 ME_ClearTempStyle(editor);
3213 ME_EmptyUndoStack(editor);
3214 editor->pBuffer->pFirst = NULL;
3215 while(p)
3217 pNext = p->next;
3218 if (p->type == diParagraph)
3219 para_destroy( editor, &p->member.para );
3220 else
3221 ME_DestroyDisplayItem(p);
3222 p = pNext;
3225 LIST_FOR_EACH_ENTRY_SAFE( s, cursor2, &editor->style_list, ME_Style, entry )
3226 ME_DestroyStyle( s );
3228 ME_ReleaseStyle(editor->pBuffer->pDefaultStyle);
3229 for (i=0; i<HFONT_CACHE_SIZE; i++)
3231 if (editor->pFontCache[i].hFont)
3232 DeleteObject(editor->pFontCache[i].hFont);
3234 if (editor->rgbBackColor != -1)
3235 DeleteObject(editor->hbrBackground);
3236 if(editor->lpOleCallback)
3237 IRichEditOleCallback_Release(editor->lpOleCallback);
3238 ITextHost_Release(editor->texthost);
3239 if (editor->reOle)
3241 IUnknown_Release(editor->reOle);
3242 editor->reOle = NULL;
3244 OleUninitialize();
3246 heap_free(editor->pBuffer);
3247 heap_free(editor->pCursors);
3248 heap_free(editor);
3251 BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
3253 TRACE("\n");
3254 switch (fdwReason)
3256 case DLL_PROCESS_ATTACH:
3257 DisableThreadLibraryCalls(hinstDLL);
3258 me_heap = HeapCreate (0, 0x10000, 0);
3259 if (!ME_RegisterEditorClass(hinstDLL)) return FALSE;
3260 hLeft = LoadCursorW(hinstDLL, MAKEINTRESOURCEW(OCR_REVERSE));
3261 LookupInit();
3262 break;
3264 case DLL_PROCESS_DETACH:
3265 if (lpvReserved) break;
3266 UnregisterClassW(RICHEDIT_CLASS20W, 0);
3267 UnregisterClassW(MSFTEDIT_CLASS, 0);
3268 UnregisterClassA(RICHEDIT_CLASS20A, 0);
3269 UnregisterClassA("RichEdit50A", 0);
3270 if (ME_ListBoxRegistered)
3271 UnregisterClassW(L"REListBox20W", 0);
3272 if (ME_ComboBoxRegistered)
3273 UnregisterClassW(L"REComboBox20W", 0);
3274 LookupCleanup();
3275 HeapDestroy (me_heap);
3276 release_typelib();
3277 break;
3279 return TRUE;
3282 static inline int get_default_line_height( ME_TextEditor *editor )
3284 int height = 0;
3286 if (editor->pBuffer && editor->pBuffer->pDefaultStyle)
3287 height = editor->pBuffer->pDefaultStyle->tm.tmHeight;
3288 if (height <= 0) height = 24;
3290 return height;
3293 static inline int calc_wheel_change( int *remain, int amount_per_click )
3295 int change = amount_per_click * (float)*remain / WHEEL_DELTA;
3296 *remain -= WHEEL_DELTA * change / amount_per_click;
3297 return change;
3300 static const char * const edit_messages[] = {
3301 "EM_GETSEL",
3302 "EM_SETSEL",
3303 "EM_GETRECT",
3304 "EM_SETRECT",
3305 "EM_SETRECTNP",
3306 "EM_SCROLL",
3307 "EM_LINESCROLL",
3308 "EM_SCROLLCARET",
3309 "EM_GETMODIFY",
3310 "EM_SETMODIFY",
3311 "EM_GETLINECOUNT",
3312 "EM_LINEINDEX",
3313 "EM_SETHANDLE",
3314 "EM_GETHANDLE",
3315 "EM_GETTHUMB",
3316 "EM_UNKNOWN_BF",
3317 "EM_UNKNOWN_C0",
3318 "EM_LINELENGTH",
3319 "EM_REPLACESEL",
3320 "EM_UNKNOWN_C3",
3321 "EM_GETLINE",
3322 "EM_LIMITTEXT",
3323 "EM_CANUNDO",
3324 "EM_UNDO",
3325 "EM_FMTLINES",
3326 "EM_LINEFROMCHAR",
3327 "EM_UNKNOWN_CA",
3328 "EM_SETTABSTOPS",
3329 "EM_SETPASSWORDCHAR",
3330 "EM_EMPTYUNDOBUFFER",
3331 "EM_GETFIRSTVISIBLELINE",
3332 "EM_SETREADONLY",
3333 "EM_SETWORDBREAKPROC",
3334 "EM_GETWORDBREAKPROC",
3335 "EM_GETPASSWORDCHAR",
3336 "EM_SETMARGINS",
3337 "EM_GETMARGINS",
3338 "EM_GETLIMITTEXT",
3339 "EM_POSFROMCHAR",
3340 "EM_CHARFROMPOS",
3341 "EM_SETIMESTATUS",
3342 "EM_GETIMESTATUS"
3345 static const char * const richedit_messages[] = {
3346 "EM_CANPASTE",
3347 "EM_DISPLAYBAND",
3348 "EM_EXGETSEL",
3349 "EM_EXLIMITTEXT",
3350 "EM_EXLINEFROMCHAR",
3351 "EM_EXSETSEL",
3352 "EM_FINDTEXT",
3353 "EM_FORMATRANGE",
3354 "EM_GETCHARFORMAT",
3355 "EM_GETEVENTMASK",
3356 "EM_GETOLEINTERFACE",
3357 "EM_GETPARAFORMAT",
3358 "EM_GETSELTEXT",
3359 "EM_HIDESELECTION",
3360 "EM_PASTESPECIAL",
3361 "EM_REQUESTRESIZE",
3362 "EM_SELECTIONTYPE",
3363 "EM_SETBKGNDCOLOR",
3364 "EM_SETCHARFORMAT",
3365 "EM_SETEVENTMASK",
3366 "EM_SETOLECALLBACK",
3367 "EM_SETPARAFORMAT",
3368 "EM_SETTARGETDEVICE",
3369 "EM_STREAMIN",
3370 "EM_STREAMOUT",
3371 "EM_GETTEXTRANGE",
3372 "EM_FINDWORDBREAK",
3373 "EM_SETOPTIONS",
3374 "EM_GETOPTIONS",
3375 "EM_FINDTEXTEX",
3376 "EM_GETWORDBREAKPROCEX",
3377 "EM_SETWORDBREAKPROCEX",
3378 "EM_SETUNDOLIMIT",
3379 "EM_UNKNOWN_USER_83",
3380 "EM_REDO",
3381 "EM_CANREDO",
3382 "EM_GETUNDONAME",
3383 "EM_GETREDONAME",
3384 "EM_STOPGROUPTYPING",
3385 "EM_SETTEXTMODE",
3386 "EM_GETTEXTMODE",
3387 "EM_AUTOURLDETECT",
3388 "EM_GETAUTOURLDETECT",
3389 "EM_SETPALETTE",
3390 "EM_GETTEXTEX",
3391 "EM_GETTEXTLENGTHEX",
3392 "EM_SHOWSCROLLBAR",
3393 "EM_SETTEXTEX",
3394 "EM_UNKNOWN_USER_98",
3395 "EM_UNKNOWN_USER_99",
3396 "EM_SETPUNCTUATION",
3397 "EM_GETPUNCTUATION",
3398 "EM_SETWORDWRAPMODE",
3399 "EM_GETWORDWRAPMODE",
3400 "EM_SETIMECOLOR",
3401 "EM_GETIMECOLOR",
3402 "EM_SETIMEOPTIONS",
3403 "EM_GETIMEOPTIONS",
3404 "EM_CONVPOSITION",
3405 "EM_UNKNOWN_USER_109",
3406 "EM_UNKNOWN_USER_110",
3407 "EM_UNKNOWN_USER_111",
3408 "EM_UNKNOWN_USER_112",
3409 "EM_UNKNOWN_USER_113",
3410 "EM_UNKNOWN_USER_114",
3411 "EM_UNKNOWN_USER_115",
3412 "EM_UNKNOWN_USER_116",
3413 "EM_UNKNOWN_USER_117",
3414 "EM_UNKNOWN_USER_118",
3415 "EM_UNKNOWN_USER_119",
3416 "EM_SETLANGOPTIONS",
3417 "EM_GETLANGOPTIONS",
3418 "EM_GETIMECOMPMODE",
3419 "EM_FINDTEXTW",
3420 "EM_FINDTEXTEXW",
3421 "EM_RECONVERSION",
3422 "EM_SETIMEMODEBIAS",
3423 "EM_GETIMEMODEBIAS"
3426 static const char *
3427 get_msg_name(UINT msg)
3429 if (msg >= EM_GETSEL && msg <= EM_CHARFROMPOS)
3430 return edit_messages[msg - EM_GETSEL];
3431 if (msg >= EM_CANPASTE && msg <= EM_GETIMEMODEBIAS)
3432 return richedit_messages[msg - EM_CANPASTE];
3433 return "";
3436 static void ME_LinkNotify(ME_TextEditor *editor, UINT msg, WPARAM wParam, LPARAM lParam)
3438 int x,y;
3439 BOOL isExact;
3440 ME_Cursor cursor; /* The start of the clicked text. */
3441 ME_Run *run;
3442 ENLINK info;
3444 x = (short)LOWORD(lParam);
3445 y = (short)HIWORD(lParam);
3446 ME_CharFromPos(editor, x, y, &cursor, &isExact);
3447 if (!isExact) return;
3449 if (is_link( cursor.run ))
3450 { /* The clicked run has CFE_LINK set */
3451 info.nmhdr.hwndFrom = NULL;
3452 info.nmhdr.idFrom = 0;
3453 info.nmhdr.code = EN_LINK;
3454 info.msg = msg;
3455 info.wParam = wParam;
3456 info.lParam = lParam;
3457 cursor.nOffset = 0;
3459 /* find the first contiguous run with CFE_LINK set */
3460 info.chrg.cpMin = ME_GetCursorOfs(&cursor);
3461 run = cursor.run;
3462 while ((run = run_prev( run )) && is_link( run ))
3463 info.chrg.cpMin -= run->len;
3465 /* find the last contiguous run with CFE_LINK set */
3466 info.chrg.cpMax = ME_GetCursorOfs(&cursor) + cursor.run->len;
3467 run = cursor.run;
3468 while ((run = run_next( run )) && is_link( run ))
3469 info.chrg.cpMax += run->len;
3471 ITextHost_TxNotify(editor->texthost, info.nmhdr.code, &info);
3475 void ME_ReplaceSel(ME_TextEditor *editor, BOOL can_undo, const WCHAR *str, int len)
3477 int from, to, nStartCursor;
3478 ME_Style *style;
3480 nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3481 style = ME_GetSelectionInsertStyle(editor);
3482 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to-from, FALSE);
3483 ME_InsertTextFromCursor(editor, 0, str, len, style);
3484 ME_ReleaseStyle(style);
3485 /* drop temporary style if line end */
3487 * FIXME question: does abc\n mean: put abc,
3488 * clear temp style, put \n? (would require a change)
3490 if (len>0 && str[len-1] == '\n')
3491 ME_ClearTempStyle(editor);
3492 ME_CommitUndo(editor);
3493 ME_UpdateSelectionLinkAttribute(editor);
3494 if (!can_undo)
3495 ME_EmptyUndoStack(editor);
3496 ME_UpdateRepaint(editor, FALSE);
3499 static void ME_SetText(ME_TextEditor *editor, void *text, BOOL unicode)
3501 LONG codepage = unicode ? CP_UNICODE : CP_ACP;
3502 int textLen;
3504 LPWSTR wszText = ME_ToUnicode(codepage, text, &textLen);
3505 ME_InsertTextFromCursor(editor, 0, wszText, textLen, editor->pBuffer->pDefaultStyle);
3506 ME_EndToUnicode(codepage, wszText);
3509 static LRESULT ME_WmCreate(ME_TextEditor *editor, LPARAM lParam, BOOL unicode)
3511 CREATESTRUCTW *createW = (CREATESTRUCTW*)lParam;
3512 CREATESTRUCTA *createA = (CREATESTRUCTA*)lParam;
3513 void *text = NULL;
3514 INT max;
3516 if (lParam)
3517 text = unicode ? (void*)createW->lpszName : (void*)createA->lpszName;
3519 ME_SetDefaultFormatRect(editor);
3521 max = (editor->styleFlags & ES_DISABLENOSCROLL) ? 1 : 0;
3522 if (~editor->styleFlags & ES_DISABLENOSCROLL || editor->styleFlags & WS_VSCROLL)
3523 ITextHost_TxSetScrollRange(editor->texthost, SB_VERT, 0, max, TRUE);
3525 if (~editor->styleFlags & ES_DISABLENOSCROLL || editor->styleFlags & WS_HSCROLL)
3526 ITextHost_TxSetScrollRange(editor->texthost, SB_HORZ, 0, max, TRUE);
3528 if (editor->styleFlags & ES_DISABLENOSCROLL)
3530 if (editor->styleFlags & WS_VSCROLL)
3532 ITextHost_TxEnableScrollBar(editor->texthost, SB_VERT, ESB_DISABLE_BOTH);
3533 ITextHost_TxShowScrollBar(editor->texthost, SB_VERT, TRUE);
3535 if (editor->styleFlags & WS_HSCROLL)
3537 ITextHost_TxEnableScrollBar(editor->texthost, SB_HORZ, ESB_DISABLE_BOTH);
3538 ITextHost_TxShowScrollBar(editor->texthost, SB_HORZ, TRUE);
3542 if (text)
3544 ME_SetText(editor, text, unicode);
3545 ME_SetCursorToStart(editor, &editor->pCursors[0]);
3546 ME_SetCursorToStart(editor, &editor->pCursors[1]);
3549 ME_CommitUndo(editor);
3550 ME_WrapMarkedParagraphs(editor);
3551 update_caret(editor);
3552 return 0;
3555 static LRESULT handle_EM_SETCHARFORMAT( ME_TextEditor *editor, WPARAM flags, const CHARFORMAT2W *fmt_in )
3557 CHARFORMAT2W fmt;
3558 BOOL changed = TRUE;
3559 ME_Cursor start, end;
3561 if (!cfany_to_cf2w( &fmt, fmt_in )) return 0;
3563 if (flags & SCF_ALL)
3565 if (editor->mode & TM_PLAINTEXT)
3567 ME_SetDefaultCharFormat( editor, &fmt );
3569 else
3571 ME_SetCursorToStart( editor, &start );
3572 ME_SetCharFormat( editor, &start, NULL, &fmt );
3573 editor->nModifyStep = 1;
3576 else if (flags & SCF_SELECTION)
3578 if (editor->mode & TM_PLAINTEXT) return 0;
3579 if (flags & SCF_WORD)
3581 end = editor->pCursors[0];
3582 ME_MoveCursorWords( editor, &end, +1 );
3583 start = end;
3584 ME_MoveCursorWords( editor, &start, -1 );
3585 ME_SetCharFormat( editor, &start, &end, &fmt );
3587 changed = ME_IsSelection( editor );
3588 ME_SetSelectionCharFormat( editor, &fmt );
3589 if (changed) editor->nModifyStep = 1;
3591 else /* SCF_DEFAULT */
3593 ME_SetDefaultCharFormat( editor, &fmt );
3596 ME_CommitUndo( editor );
3597 if (changed)
3599 ME_WrapMarkedParagraphs( editor );
3600 ME_UpdateScrollBar( editor );
3602 return 1;
3605 #define UNSUPPORTED_MSG(e) \
3606 case e: \
3607 FIXME(#e ": stub\n"); \
3608 *phresult = S_FALSE; \
3609 return 0;
3611 /* Handle messages for windowless and windowed richedit controls.
3613 * The LRESULT that is returned is a return value for window procs,
3614 * and the phresult parameter is the COM return code needed by the
3615 * text services interface. */
3616 LRESULT ME_HandleMessage(ME_TextEditor *editor, UINT msg, WPARAM wParam,
3617 LPARAM lParam, BOOL unicode, HRESULT* phresult)
3619 *phresult = S_OK;
3621 switch(msg) {
3623 UNSUPPORTED_MSG(EM_DISPLAYBAND)
3624 UNSUPPORTED_MSG(EM_FINDWORDBREAK)
3625 UNSUPPORTED_MSG(EM_FMTLINES)
3626 UNSUPPORTED_MSG(EM_FORMATRANGE)
3627 UNSUPPORTED_MSG(EM_GETBIDIOPTIONS)
3628 UNSUPPORTED_MSG(EM_GETEDITSTYLE)
3629 UNSUPPORTED_MSG(EM_GETIMECOMPMODE)
3630 UNSUPPORTED_MSG(EM_GETIMESTATUS)
3631 UNSUPPORTED_MSG(EM_SETIMESTATUS)
3632 UNSUPPORTED_MSG(EM_GETLANGOPTIONS)
3633 UNSUPPORTED_MSG(EM_GETREDONAME)
3634 UNSUPPORTED_MSG(EM_GETTYPOGRAPHYOPTIONS)
3635 UNSUPPORTED_MSG(EM_GETUNDONAME)
3636 UNSUPPORTED_MSG(EM_GETWORDBREAKPROCEX)
3637 UNSUPPORTED_MSG(EM_SETBIDIOPTIONS)
3638 UNSUPPORTED_MSG(EM_SETEDITSTYLE)
3639 UNSUPPORTED_MSG(EM_SETLANGOPTIONS)
3640 UNSUPPORTED_MSG(EM_SETMARGINS)
3641 UNSUPPORTED_MSG(EM_SETPALETTE)
3642 UNSUPPORTED_MSG(EM_SETTABSTOPS)
3643 UNSUPPORTED_MSG(EM_SETTYPOGRAPHYOPTIONS)
3644 UNSUPPORTED_MSG(EM_SETWORDBREAKPROCEX)
3646 /* Messages specific to Richedit controls */
3648 case EM_STREAMIN:
3649 return ME_StreamIn(editor, wParam, (EDITSTREAM*)lParam, TRUE);
3650 case EM_STREAMOUT:
3651 return ME_StreamOut(editor, wParam, (EDITSTREAM *)lParam);
3652 case WM_GETDLGCODE:
3654 UINT code = DLGC_WANTCHARS|DLGC_WANTTAB|DLGC_WANTARROWS;
3656 if (lParam)
3657 editor->bDialogMode = TRUE;
3658 if (editor->styleFlags & ES_MULTILINE)
3659 code |= DLGC_WANTMESSAGE;
3660 if (!(editor->styleFlags & ES_SAVESEL))
3661 code |= DLGC_HASSETSEL;
3662 return code;
3664 case EM_EMPTYUNDOBUFFER:
3665 ME_EmptyUndoStack(editor);
3666 return 0;
3667 case EM_GETSEL:
3669 /* Note: wParam/lParam can be NULL */
3670 UINT from, to;
3671 PUINT pfrom = wParam ? (PUINT)wParam : &from;
3672 PUINT pto = lParam ? (PUINT)lParam : &to;
3673 ME_GetSelectionOfs(editor, (int *)pfrom, (int *)pto);
3674 if ((*pfrom|*pto) & 0xFFFF0000)
3675 return -1;
3676 return MAKELONG(*pfrom,*pto);
3678 case EM_EXGETSEL:
3680 CHARRANGE *pRange = (CHARRANGE *)lParam;
3681 ME_GetSelectionOfs(editor, &pRange->cpMin, &pRange->cpMax);
3682 TRACE("EM_EXGETSEL = (%d,%d)\n", pRange->cpMin, pRange->cpMax);
3683 return 0;
3685 case EM_SETUNDOLIMIT:
3687 if ((int)wParam < 0)
3688 editor->nUndoLimit = STACK_SIZE_DEFAULT;
3689 else
3690 editor->nUndoLimit = min(wParam, STACK_SIZE_MAX);
3691 /* Setting a max stack size keeps wine from getting killed
3692 for hogging memory. Windows allocates all this memory at once, so
3693 no program would realistically set a value above our maximum. */
3694 return editor->nUndoLimit;
3696 case EM_CANUNDO:
3697 return !list_empty( &editor->undo_stack );
3698 case EM_CANREDO:
3699 return !list_empty( &editor->redo_stack );
3700 case WM_UNDO: /* FIXME: actually not the same */
3701 case EM_UNDO:
3702 return ME_Undo(editor);
3703 case EM_REDO:
3704 return ME_Redo(editor);
3705 case EM_GETOPTIONS:
3707 /* these flags are equivalent to the ES_* counterparts */
3708 DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
3709 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN | ECO_SELECTIONBAR;
3710 DWORD settings = editor->styleFlags & mask;
3712 return settings;
3714 case EM_SETFONTSIZE:
3716 CHARFORMAT2W cf;
3717 LONG tmp_size, size;
3718 BOOL is_increase = ((LONG)wParam > 0);
3720 if (editor->mode & TM_PLAINTEXT)
3721 return FALSE;
3723 cf.cbSize = sizeof(cf);
3724 cf.dwMask = CFM_SIZE;
3725 ME_GetSelectionCharFormat(editor, &cf);
3726 tmp_size = (cf.yHeight / 20) + wParam;
3728 if (tmp_size <= 1)
3729 size = 1;
3730 else if (tmp_size > 12 && tmp_size < 28 && tmp_size % 2)
3731 size = tmp_size + (is_increase ? 1 : -1);
3732 else if (tmp_size > 28 && tmp_size < 36)
3733 size = is_increase ? 36 : 28;
3734 else if (tmp_size > 36 && tmp_size < 48)
3735 size = is_increase ? 48 : 36;
3736 else if (tmp_size > 48 && tmp_size < 72)
3737 size = is_increase ? 72 : 48;
3738 else if (tmp_size > 72 && tmp_size < 80)
3739 size = is_increase ? 80 : 72;
3740 else if (tmp_size > 80 && tmp_size < 1638)
3741 size = 10 * (is_increase ? (tmp_size / 10 + 1) : (tmp_size / 10));
3742 else if (tmp_size >= 1638)
3743 size = 1638;
3744 else
3745 size = tmp_size;
3747 cf.yHeight = size * 20; /* convert twips to points */
3748 ME_SetSelectionCharFormat(editor, &cf);
3749 ME_CommitUndo(editor);
3750 ME_WrapMarkedParagraphs(editor);
3751 ME_UpdateScrollBar(editor);
3753 return TRUE;
3755 case EM_SETOPTIONS:
3757 /* these flags are equivalent to ES_* counterparts, except for
3758 * ECO_AUTOWORDSELECTION that doesn't have an ES_* counterpart,
3759 * but is still stored in editor->styleFlags. */
3760 const DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
3761 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN |
3762 ECO_SELECTIONBAR | ECO_AUTOWORDSELECTION;
3763 DWORD settings = mask & editor->styleFlags;
3764 DWORD oldSettings = settings;
3765 DWORD changedSettings;
3767 switch(wParam)
3769 case ECOOP_SET:
3770 settings = lParam;
3771 break;
3772 case ECOOP_OR:
3773 settings |= lParam;
3774 break;
3775 case ECOOP_AND:
3776 settings &= lParam;
3777 break;
3778 case ECOOP_XOR:
3779 settings ^= lParam;
3781 changedSettings = oldSettings ^ settings;
3783 if (changedSettings) {
3784 editor->styleFlags = (editor->styleFlags & ~mask) | (settings & mask);
3786 if (changedSettings & ECO_SELECTIONBAR)
3788 ITextHost_TxInvalidateRect(editor->texthost, &editor->rcFormat, TRUE);
3789 if (settings & ECO_SELECTIONBAR) {
3790 assert(!editor->selofs);
3791 editor->selofs = SELECTIONBAR_WIDTH;
3792 editor->rcFormat.left += editor->selofs;
3793 } else {
3794 editor->rcFormat.left -= editor->selofs;
3795 editor->selofs = 0;
3797 ME_RewrapRepaint(editor);
3800 if ((changedSettings & settings & ES_NOHIDESEL) && !editor->bHaveFocus)
3801 ME_InvalidateSelection( editor );
3803 if (changedSettings & settings & ECO_VERTICAL)
3804 FIXME("ECO_VERTICAL not implemented yet!\n");
3805 if (changedSettings & settings & ECO_AUTOHSCROLL)
3806 FIXME("ECO_AUTOHSCROLL not implemented yet!\n");
3807 if (changedSettings & settings & ECO_AUTOVSCROLL)
3808 FIXME("ECO_AUTOVSCROLL not implemented yet!\n");
3809 if (changedSettings & settings & ECO_WANTRETURN)
3810 FIXME("ECO_WANTRETURN not implemented yet!\n");
3811 if (changedSettings & settings & ECO_AUTOWORDSELECTION)
3812 FIXME("ECO_AUTOWORDSELECTION not implemented yet!\n");
3815 return settings;
3817 case EM_SETSEL:
3819 return set_selection( editor, wParam, lParam );
3821 case EM_SETSCROLLPOS:
3823 POINT *point = (POINT *)lParam;
3824 ME_ScrollAbs(editor, point->x, point->y);
3825 return 0;
3827 case EM_AUTOURLDETECT:
3829 if (wParam==1 || wParam ==0)
3831 editor->AutoURLDetect_bEnable = (BOOL)wParam;
3832 return 0;
3834 return E_INVALIDARG;
3836 case EM_GETAUTOURLDETECT:
3838 return editor->AutoURLDetect_bEnable;
3840 case EM_EXSETSEL:
3842 CHARRANGE range = *(CHARRANGE *)lParam;
3844 return set_selection( editor, range.cpMin, range.cpMax );
3846 case EM_SHOWSCROLLBAR:
3848 DWORD flags;
3850 switch (wParam)
3852 case SB_HORZ:
3853 flags = WS_HSCROLL;
3854 break;
3855 case SB_VERT:
3856 flags = WS_VSCROLL;
3857 break;
3858 case SB_BOTH:
3859 flags = WS_HSCROLL|WS_VSCROLL;
3860 break;
3861 default:
3862 return 0;
3865 if (lParam) {
3866 editor->styleFlags |= flags;
3867 if (flags & WS_HSCROLL)
3868 ITextHost_TxShowScrollBar(editor->texthost, SB_HORZ,
3869 editor->nTotalWidth > editor->sizeWindow.cx);
3870 if (flags & WS_VSCROLL)
3871 ITextHost_TxShowScrollBar(editor->texthost, SB_VERT,
3872 editor->nTotalLength > editor->sizeWindow.cy);
3873 } else {
3874 editor->styleFlags &= ~flags;
3875 ITextHost_TxShowScrollBar(editor->texthost, wParam, FALSE);
3877 return 0;
3879 case EM_SETTEXTEX:
3881 LPWSTR wszText;
3882 SETTEXTEX *pStruct = (SETTEXTEX *)wParam;
3883 int from, to, len;
3884 ME_Style *style;
3885 BOOL bRtf, bUnicode, bSelection, bUTF8;
3886 int oldModify = editor->nModifyStep;
3887 static const char utf8_bom[] = {0xef, 0xbb, 0xbf};
3889 if (!pStruct) return 0;
3891 /* If we detect ascii rtf at the start of the string,
3892 * we know it isn't unicode. */
3893 bRtf = (lParam && (!strncmp((char *)lParam, "{\\rtf", 5) ||
3894 !strncmp((char *)lParam, "{\\urtf", 6)));
3895 bUnicode = !bRtf && pStruct->codepage == CP_UNICODE;
3896 bUTF8 = (lParam && (!strncmp((char *)lParam, utf8_bom, 3)));
3898 TRACE("EM_SETTEXTEX - %s, flags %d, cp %d\n",
3899 bUnicode ? debugstr_w((LPCWSTR)lParam) : debugstr_a((LPCSTR)lParam),
3900 pStruct->flags, pStruct->codepage);
3902 bSelection = (pStruct->flags & ST_SELECTION) != 0;
3903 if (bSelection) {
3904 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3905 style = ME_GetSelectionInsertStyle(editor);
3906 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to - from, FALSE);
3907 } else {
3908 ME_Cursor start;
3909 ME_SetCursorToStart(editor, &start);
3910 ME_InternalDeleteText(editor, &start, ME_GetTextLength(editor), FALSE);
3911 style = editor->pBuffer->pDefaultStyle;
3914 if (bRtf) {
3915 ME_StreamInRTFString(editor, bSelection, (char *)lParam);
3916 if (bSelection) {
3917 /* FIXME: The length returned doesn't include the rtf control
3918 * characters, only the actual text. */
3919 len = lParam ? strlen((char *)lParam) : 0;
3921 } else {
3922 if (bUTF8 && !bUnicode) {
3923 wszText = ME_ToUnicode(CP_UTF8, (void *)(lParam+3), &len);
3924 ME_InsertTextFromCursor(editor, 0, wszText, len, style);
3925 ME_EndToUnicode(CP_UTF8, wszText);
3926 } else {
3927 wszText = ME_ToUnicode(pStruct->codepage, (void *)lParam, &len);
3928 ME_InsertTextFromCursor(editor, 0, wszText, len, style);
3929 ME_EndToUnicode(pStruct->codepage, wszText);
3933 if (bSelection) {
3934 ME_ReleaseStyle(style);
3935 ME_UpdateSelectionLinkAttribute(editor);
3936 } else {
3937 ME_Cursor cursor;
3938 len = 1;
3939 ME_SetCursorToStart(editor, &cursor);
3940 ME_UpdateLinkAttribute(editor, &cursor, INT_MAX);
3942 ME_CommitUndo(editor);
3943 if (!(pStruct->flags & ST_KEEPUNDO))
3945 editor->nModifyStep = oldModify;
3946 ME_EmptyUndoStack(editor);
3948 ME_UpdateRepaint(editor, FALSE);
3949 return len;
3951 case EM_SELECTIONTYPE:
3952 return ME_GetSelectionType(editor);
3953 case EM_SETBKGNDCOLOR:
3955 LRESULT lColor;
3956 if (editor->rgbBackColor != -1) {
3957 DeleteObject(editor->hbrBackground);
3958 lColor = editor->rgbBackColor;
3960 else lColor = ITextHost_TxGetSysColor(editor->texthost, COLOR_WINDOW);
3962 if (wParam)
3964 editor->rgbBackColor = -1;
3965 editor->hbrBackground = GetSysColorBrush(COLOR_WINDOW);
3967 else
3969 editor->rgbBackColor = lParam;
3970 editor->hbrBackground = CreateSolidBrush(editor->rgbBackColor);
3972 ITextHost_TxInvalidateRect(editor->texthost, NULL, TRUE);
3973 return lColor;
3975 case EM_GETMODIFY:
3976 return editor->nModifyStep == 0 ? 0 : -1;
3977 case EM_SETMODIFY:
3979 if (wParam)
3980 editor->nModifyStep = 1;
3981 else
3982 editor->nModifyStep = 0;
3984 return 0;
3986 case EM_SETREADONLY:
3988 if (wParam)
3989 editor->styleFlags |= ES_READONLY;
3990 else
3991 editor->styleFlags &= ~ES_READONLY;
3992 return 1;
3994 case EM_SETEVENTMASK:
3996 DWORD nOldMask = editor->nEventMask;
3998 editor->nEventMask = lParam;
3999 return nOldMask;
4001 case EM_GETEVENTMASK:
4002 return editor->nEventMask;
4003 case EM_SETCHARFORMAT:
4004 return handle_EM_SETCHARFORMAT( editor, wParam, (CHARFORMAT2W *)lParam );
4005 case EM_GETCHARFORMAT:
4007 CHARFORMAT2W tmp, *dst = (CHARFORMAT2W *)lParam;
4008 if (dst->cbSize != sizeof(CHARFORMATA) &&
4009 dst->cbSize != sizeof(CHARFORMATW) &&
4010 dst->cbSize != sizeof(CHARFORMAT2A) &&
4011 dst->cbSize != sizeof(CHARFORMAT2W))
4012 return 0;
4013 tmp.cbSize = sizeof(tmp);
4014 if (!wParam)
4015 ME_GetDefaultCharFormat(editor, &tmp);
4016 else
4017 ME_GetSelectionCharFormat(editor, &tmp);
4018 cf2w_to_cfany(dst, &tmp);
4019 return tmp.dwMask;
4021 case EM_SETPARAFORMAT:
4023 BOOL result = editor_set_selection_para_fmt( editor, (PARAFORMAT2 *)lParam );
4024 ME_WrapMarkedParagraphs(editor);
4025 ME_UpdateScrollBar(editor);
4026 ME_CommitUndo(editor);
4027 return result;
4029 case EM_GETPARAFORMAT:
4030 editor_get_selection_para_fmt( editor, (PARAFORMAT2 *)lParam );
4031 return ((PARAFORMAT2 *)lParam)->dwMask;
4032 case EM_GETFIRSTVISIBLELINE:
4034 ME_Paragraph *para = editor_first_para( editor );
4035 ME_Row *row;
4036 int y = editor->vert_si.nPos;
4037 int count = 0;
4039 while (para_next( para ))
4041 if (y < para->pt.y + para->nHeight) break;
4042 count += para->nRows;
4043 para = para_next( para );
4046 row = para_first_row( para );
4047 while (row)
4049 if (y < para->pt.y + row->pt.y + row->nHeight) break;
4050 count++;
4051 row = row_next( row );
4053 return count;
4055 case EM_HIDESELECTION:
4057 editor->bHideSelection = (wParam != 0);
4058 ME_InvalidateSelection(editor);
4059 return 0;
4061 case EM_LINESCROLL:
4063 if (!(editor->styleFlags & ES_MULTILINE))
4064 return FALSE;
4065 ME_ScrollDown( editor, lParam * get_default_line_height( editor ) );
4066 return TRUE;
4068 case WM_CLEAR:
4070 int from, to;
4071 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
4072 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to-from, FALSE);
4073 ME_CommitUndo(editor);
4074 ME_UpdateRepaint(editor, TRUE);
4075 return 0;
4077 case EM_REPLACESEL:
4079 int len = 0;
4080 LONG codepage = unicode ? CP_UNICODE : CP_ACP;
4081 LPWSTR wszText = ME_ToUnicode(codepage, (void *)lParam, &len);
4083 TRACE("EM_REPLACESEL - %s\n", debugstr_w(wszText));
4085 ME_ReplaceSel(editor, !!wParam, wszText, len);
4086 ME_EndToUnicode(codepage, wszText);
4087 return len;
4089 case EM_SCROLLCARET:
4090 editor_ensure_visible( editor, &editor->pCursors[0] );
4091 return 0;
4092 case WM_SETFONT:
4094 LOGFONTW lf;
4095 CHARFORMAT2W fmt;
4096 HDC hDC;
4097 BOOL bRepaint = LOWORD(lParam);
4099 if (!wParam)
4100 wParam = (WPARAM)GetStockObject(SYSTEM_FONT);
4102 if (!GetObjectW((HGDIOBJ)wParam, sizeof(LOGFONTW), &lf))
4103 return 0;
4105 hDC = ITextHost_TxGetDC(editor->texthost);
4106 ME_CharFormatFromLogFont(hDC, &lf, &fmt);
4107 ITextHost_TxReleaseDC(editor->texthost, hDC);
4108 if (editor->mode & TM_RICHTEXT) {
4109 ME_Cursor start;
4110 ME_SetCursorToStart(editor, &start);
4111 ME_SetCharFormat(editor, &start, NULL, &fmt);
4113 ME_SetDefaultCharFormat(editor, &fmt);
4115 ME_CommitUndo(editor);
4116 editor_mark_rewrap_all( editor );
4117 ME_WrapMarkedParagraphs(editor);
4118 ME_UpdateScrollBar(editor);
4119 if (bRepaint)
4120 ME_Repaint(editor);
4121 return 0;
4123 case WM_SETTEXT:
4125 ME_Cursor cursor;
4126 ME_SetCursorToStart(editor, &cursor);
4127 ME_InternalDeleteText(editor, &cursor, ME_GetTextLength(editor), FALSE);
4128 if (lParam)
4130 TRACE("WM_SETTEXT lParam==%lx\n",lParam);
4131 if (!strncmp((char *)lParam, "{\\rtf", 5) ||
4132 !strncmp((char *)lParam, "{\\urtf", 6))
4134 /* Undocumented: WM_SETTEXT supports RTF text */
4135 ME_StreamInRTFString(editor, 0, (char *)lParam);
4137 else
4138 ME_SetText(editor, (void*)lParam, unicode);
4140 else
4141 TRACE("WM_SETTEXT - NULL\n");
4142 ME_SetCursorToStart(editor, &cursor);
4143 ME_UpdateLinkAttribute(editor, &cursor, INT_MAX);
4144 set_selection_cursors(editor, 0, 0);
4145 editor->nModifyStep = 0;
4146 ME_CommitUndo(editor);
4147 ME_EmptyUndoStack(editor);
4148 ME_UpdateRepaint(editor, FALSE);
4149 return 1;
4151 case EM_CANPASTE:
4152 return paste_special( editor, 0, NULL, TRUE );
4153 case WM_PASTE:
4154 case WM_MBUTTONDOWN:
4155 wParam = 0;
4156 lParam = 0;
4157 /* fall through */
4158 case EM_PASTESPECIAL:
4159 paste_special( editor, wParam, (REPASTESPECIAL *)lParam, FALSE );
4160 return 0;
4161 case WM_CUT:
4162 case WM_COPY:
4163 copy_or_cut(editor, msg == WM_CUT);
4164 return 0;
4165 case WM_GETTEXTLENGTH:
4167 GETTEXTLENGTHEX how;
4169 /* CR/LF conversion required in 2.0 mode, verbatim in 1.0 mode */
4170 how.flags = GTL_CLOSE | (editor->bEmulateVersion10 ? 0 : GTL_USECRLF) | GTL_NUMCHARS;
4171 how.codepage = unicode ? CP_UNICODE : CP_ACP;
4172 return ME_GetTextLengthEx(editor, &how);
4174 case EM_GETTEXTLENGTHEX:
4175 return ME_GetTextLengthEx(editor, (GETTEXTLENGTHEX *)wParam);
4176 case WM_GETTEXT:
4178 GETTEXTEX ex;
4179 ex.cb = wParam * (unicode ? sizeof(WCHAR) : sizeof(CHAR));
4180 ex.flags = GT_USECRLF;
4181 ex.codepage = unicode ? CP_UNICODE : CP_ACP;
4182 ex.lpDefaultChar = NULL;
4183 ex.lpUsedDefChar = NULL;
4184 return ME_GetTextEx(editor, &ex, lParam);
4186 case EM_GETTEXTEX:
4187 return ME_GetTextEx(editor, (GETTEXTEX*)wParam, lParam);
4188 case EM_GETSELTEXT:
4190 int nFrom, nTo, nStartCur = ME_GetSelectionOfs(editor, &nFrom, &nTo);
4191 ME_Cursor *from = &editor->pCursors[nStartCur];
4192 return ME_GetTextRange(editor, (WCHAR *)lParam, from,
4193 nTo - nFrom, unicode);
4195 case EM_GETSCROLLPOS:
4197 POINT *point = (POINT *)lParam;
4198 point->x = editor->horz_si.nPos;
4199 point->y = editor->vert_si.nPos;
4200 /* 16-bit scaled value is returned as stored in scrollinfo */
4201 if (editor->horz_si.nMax > 0xffff)
4202 point->x = MulDiv(point->x, 0xffff, editor->horz_si.nMax);
4203 if (editor->vert_si.nMax > 0xffff)
4204 point->y = MulDiv(point->y, 0xffff, editor->vert_si.nMax);
4205 return 1;
4207 case EM_GETTEXTRANGE:
4209 TEXTRANGEW *rng = (TEXTRANGEW *)lParam;
4210 ME_Cursor start;
4211 int nStart = rng->chrg.cpMin;
4212 int nEnd = rng->chrg.cpMax;
4213 int textlength = ME_GetTextLength(editor);
4215 TRACE("EM_GETTEXTRANGE min=%d max=%d unicode=%d textlength=%d\n",
4216 rng->chrg.cpMin, rng->chrg.cpMax, unicode, textlength);
4217 if (nStart < 0) return 0;
4218 if ((nStart == 0 && nEnd == -1) || nEnd > textlength)
4219 nEnd = textlength;
4220 if (nStart >= nEnd) return 0;
4222 cursor_from_char_ofs( editor, nStart, &start );
4223 return ME_GetTextRange(editor, rng->lpstrText, &start, nEnd - nStart, unicode);
4225 case EM_GETLINE:
4227 ME_Row *row;
4228 ME_Run *run;
4229 const unsigned int nMaxChars = *(WORD *) lParam;
4230 unsigned int nCharsLeft = nMaxChars;
4231 char *dest = (char *) lParam;
4232 BOOL wroteNull = FALSE;
4233 ME_Cursor start, end;
4235 TRACE("EM_GETLINE: row=%d, nMaxChars=%d (%s)\n", (int) wParam, nMaxChars,
4236 unicode ? "Unicode" : "Ansi");
4238 row = row_from_row_number( editor, wParam );
4239 if (row == NULL) return 0;
4241 row_first_cursor( row, &start );
4242 row_end_cursor( row, &end, TRUE );
4243 run = start.run;
4244 while (nCharsLeft)
4246 WCHAR *str;
4247 unsigned int nCopy;
4248 int ofs = (run == start.run) ? start.nOffset : 0;
4249 int len = (run == end.run) ? end.nOffset : run->len;
4251 str = get_text( run, ofs );
4252 nCopy = min( nCharsLeft, len );
4254 if (unicode)
4255 memcpy(dest, str, nCopy * sizeof(WCHAR));
4256 else
4257 nCopy = WideCharToMultiByte(CP_ACP, 0, str, nCopy, dest,
4258 nCharsLeft, NULL, NULL);
4259 dest += nCopy * (unicode ? sizeof(WCHAR) : 1);
4260 nCharsLeft -= nCopy;
4261 if (run == end.run) break;
4262 run = row_next_run( row, run );
4265 /* append line termination, space allowing */
4266 if (nCharsLeft > 0)
4268 if (unicode)
4269 *((WCHAR *)dest) = '\0';
4270 else
4271 *dest = '\0';
4272 nCharsLeft--;
4273 wroteNull = TRUE;
4276 TRACE("EM_GETLINE: got %u characters\n", nMaxChars - nCharsLeft);
4277 return nMaxChars - nCharsLeft - (wroteNull ? 1 : 0);
4279 case EM_GETLINECOUNT:
4281 int count = editor->total_rows;
4282 ME_Run *prev_run, *last_run;
4284 last_run = para_end_run( para_prev( editor_end_para( editor ) ) );
4285 prev_run = run_prev_all_paras( last_run );
4287 if (editor->bEmulateVersion10 && prev_run && last_run->nCharOfs == 0 &&
4288 prev_run->len == 1 && *get_text( prev_run, 0 ) == '\r')
4290 /* In 1.0 emulation, the last solitary \r at the very end of the text
4291 (if one exists) is NOT a line break.
4292 FIXME: this is an ugly hack. This should have a more regular model. */
4293 count--;
4296 count = max(1, count);
4297 TRACE("EM_GETLINECOUNT: count==%d\n", count);
4298 return count;
4300 case EM_LINEFROMCHAR:
4302 if (wParam == -1) wParam = ME_GetCursorOfs( editor->pCursors + 1 );
4303 return row_number_from_char_ofs( editor, wParam );
4305 case EM_EXLINEFROMCHAR:
4307 if (lParam == -1) lParam = ME_GetCursorOfs( editor->pCursors + 1 );
4308 return row_number_from_char_ofs( editor, lParam );
4310 case EM_LINEINDEX:
4312 ME_Row *row;
4313 ME_Cursor cursor;
4314 int ofs;
4316 if (wParam == -1) row = row_from_cursor( editor->pCursors );
4317 else row = row_from_row_number( editor, wParam );
4318 if (!row) return -1;
4320 row_first_cursor( row, &cursor );
4321 ofs = ME_GetCursorOfs( &cursor );
4322 TRACE( "EM_LINEINDEX: nCharOfs==%d\n", ofs );
4323 return ofs;
4325 case EM_LINELENGTH:
4327 ME_Row *row;
4328 int start_ofs, end_ofs;
4329 ME_Cursor cursor;
4331 if (wParam > ME_GetTextLength(editor))
4332 return 0;
4333 if (wParam == -1)
4335 FIXME("EM_LINELENGTH: returning number of unselected characters on lines with selection unsupported.\n");
4336 return 0;
4338 cursor_from_char_ofs( editor, wParam, &cursor );
4339 row = row_from_cursor( &cursor );
4340 row_first_cursor( row, &cursor );
4341 start_ofs = ME_GetCursorOfs( &cursor );
4342 row_end_cursor( row, &cursor, FALSE );
4343 end_ofs = ME_GetCursorOfs( &cursor );
4344 TRACE( "EM_LINELENGTH(%ld)==%d\n", wParam, end_ofs - start_ofs );
4345 return end_ofs - start_ofs;
4347 case EM_EXLIMITTEXT:
4349 if ((int)lParam < 0)
4350 return 0;
4351 if (lParam == 0)
4352 editor->nTextLimit = 65536;
4353 else
4354 editor->nTextLimit = (int) lParam;
4355 return 0;
4357 case EM_LIMITTEXT:
4359 if (wParam == 0)
4360 editor->nTextLimit = 65536;
4361 else
4362 editor->nTextLimit = (int) wParam;
4363 return 0;
4365 case EM_GETLIMITTEXT:
4367 return editor->nTextLimit;
4369 case EM_FINDTEXT:
4371 LRESULT r;
4372 if(!unicode){
4373 FINDTEXTA *ft = (FINDTEXTA *)lParam;
4374 int nChars = MultiByteToWideChar(CP_ACP, 0, ft->lpstrText, -1, NULL, 0);
4375 WCHAR *tmp;
4377 if ((tmp = heap_alloc(nChars * sizeof(*tmp))) != NULL)
4378 MultiByteToWideChar(CP_ACP, 0, ft->lpstrText, -1, tmp, nChars);
4379 r = ME_FindText(editor, wParam, &ft->chrg, tmp, NULL);
4380 heap_free(tmp);
4381 }else{
4382 FINDTEXTW *ft = (FINDTEXTW *)lParam;
4383 r = ME_FindText(editor, wParam, &ft->chrg, ft->lpstrText, NULL);
4385 return r;
4387 case EM_FINDTEXTEX:
4389 LRESULT r;
4390 if(!unicode){
4391 FINDTEXTEXA *ex = (FINDTEXTEXA *)lParam;
4392 int nChars = MultiByteToWideChar(CP_ACP, 0, ex->lpstrText, -1, NULL, 0);
4393 WCHAR *tmp;
4395 if ((tmp = heap_alloc(nChars * sizeof(*tmp))) != NULL)
4396 MultiByteToWideChar(CP_ACP, 0, ex->lpstrText, -1, tmp, nChars);
4397 r = ME_FindText(editor, wParam, &ex->chrg, tmp, &ex->chrgText);
4398 heap_free(tmp);
4399 }else{
4400 FINDTEXTEXW *ex = (FINDTEXTEXW *)lParam;
4401 r = ME_FindText(editor, wParam, &ex->chrg, ex->lpstrText, &ex->chrgText);
4403 return r;
4405 case EM_FINDTEXTW:
4407 FINDTEXTW *ft = (FINDTEXTW *)lParam;
4408 return ME_FindText(editor, wParam, &ft->chrg, ft->lpstrText, NULL);
4410 case EM_FINDTEXTEXW:
4412 FINDTEXTEXW *ex = (FINDTEXTEXW *)lParam;
4413 return ME_FindText(editor, wParam, &ex->chrg, ex->lpstrText, &ex->chrgText);
4415 case EM_GETZOOM:
4416 if (!wParam || !lParam)
4417 return FALSE;
4418 *(int *)wParam = editor->nZoomNumerator;
4419 *(int *)lParam = editor->nZoomDenominator;
4420 return TRUE;
4421 case EM_SETZOOM:
4422 return ME_SetZoom(editor, wParam, lParam);
4423 case EM_CHARFROMPOS:
4425 ME_Cursor cursor;
4426 if (ME_CharFromPos(editor, ((POINTL *)lParam)->x, ((POINTL *)lParam)->y,
4427 &cursor, NULL))
4428 return ME_GetCursorOfs(&cursor);
4429 else
4430 return -1;
4432 case EM_POSFROMCHAR:
4434 ME_Cursor cursor;
4435 int nCharOfs, nLength;
4436 POINTL pt = {0,0};
4438 nCharOfs = wParam;
4439 /* detect which API version we're dealing with */
4440 if (wParam >= 0x40000)
4441 nCharOfs = lParam;
4442 nLength = ME_GetTextLength(editor);
4443 nCharOfs = min(nCharOfs, nLength);
4444 nCharOfs = max(nCharOfs, 0);
4446 cursor_from_char_ofs( editor, nCharOfs, &cursor );
4447 pt.y = cursor.run->pt.y;
4448 pt.x = cursor.run->pt.x +
4449 ME_PointFromChar( editor, cursor.run, cursor.nOffset, TRUE );
4450 pt.y += cursor.para->pt.y + editor->rcFormat.top;
4451 pt.x += editor->rcFormat.left;
4453 pt.x -= editor->horz_si.nPos;
4454 pt.y -= editor->vert_si.nPos;
4456 if (wParam >= 0x40000) *(POINTL *)wParam = pt;
4458 return (wParam >= 0x40000) ? 0 : MAKELONG( pt.x, pt.y );
4460 case WM_CREATE:
4461 return ME_WmCreate(editor, lParam, unicode);
4462 case WM_DESTROY:
4463 ME_DestroyEditor(editor);
4464 return 0;
4465 case WM_SETCURSOR:
4467 POINT cursor_pos;
4468 if (wParam == (WPARAM)editor->hWnd && GetCursorPos(&cursor_pos) &&
4469 ScreenToClient(editor->hWnd, &cursor_pos))
4470 ME_LinkNotify(editor, msg, 0, MAKELPARAM(cursor_pos.x, cursor_pos.y));
4471 return ME_SetCursor(editor);
4473 case WM_LBUTTONDBLCLK:
4474 case WM_LBUTTONDOWN:
4476 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4477 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4478 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4479 return 0;
4480 ITextHost_TxSetFocus(editor->texthost);
4481 ME_LButtonDown(editor, (short)LOWORD(lParam), (short)HIWORD(lParam),
4482 ME_CalculateClickCount(editor, msg, wParam, lParam));
4483 ITextHost_TxSetCapture(editor->texthost, TRUE);
4484 editor->bMouseCaptured = TRUE;
4485 ME_LinkNotify(editor, msg, wParam, lParam);
4486 if (!ME_SetCursor(editor)) goto do_default;
4487 break;
4489 case WM_MOUSEMOVE:
4490 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4491 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4492 return 0;
4493 if (editor->bMouseCaptured)
4494 ME_MouseMove(editor, (short)LOWORD(lParam), (short)HIWORD(lParam));
4495 else
4496 ME_LinkNotify(editor, msg, wParam, lParam);
4497 /* Set cursor if mouse is captured, since WM_SETCURSOR won't be received. */
4498 if (editor->bMouseCaptured)
4499 ME_SetCursor(editor);
4500 break;
4501 case WM_LBUTTONUP:
4502 if (editor->bMouseCaptured) {
4503 ITextHost_TxSetCapture(editor->texthost, FALSE);
4504 editor->bMouseCaptured = FALSE;
4506 if (editor->nSelectionType == stDocument)
4507 editor->nSelectionType = stPosition;
4508 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4509 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4510 return 0;
4511 else
4513 ME_SetCursor(editor);
4514 ME_LinkNotify(editor, msg, wParam, lParam);
4516 break;
4517 case WM_RBUTTONUP:
4518 case WM_RBUTTONDOWN:
4519 case WM_RBUTTONDBLCLK:
4520 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4521 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4522 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4523 return 0;
4524 ME_LinkNotify(editor, msg, wParam, lParam);
4525 goto do_default;
4526 case WM_CONTEXTMENU:
4527 if (!ME_ShowContextMenu(editor, (short)LOWORD(lParam), (short)HIWORD(lParam)))
4528 goto do_default;
4529 break;
4530 case WM_SETFOCUS:
4531 editor->bHaveFocus = TRUE;
4532 create_caret(editor);
4533 update_caret(editor);
4534 ME_SendOldNotify(editor, EN_SETFOCUS);
4535 if (!editor->bHideSelection && !(editor->styleFlags & ES_NOHIDESEL))
4536 ME_InvalidateSelection( editor );
4537 return 0;
4538 case WM_KILLFOCUS:
4539 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4540 editor->bHaveFocus = FALSE;
4541 editor->wheel_remain = 0;
4542 hide_caret(editor);
4543 DestroyCaret();
4544 ME_SendOldNotify(editor, EN_KILLFOCUS);
4545 if (!editor->bHideSelection && !(editor->styleFlags & ES_NOHIDESEL))
4546 ME_InvalidateSelection( editor );
4547 return 0;
4548 case WM_COMMAND:
4549 TRACE("editor wnd command = %d\n", LOWORD(wParam));
4550 return 0;
4551 case WM_KEYUP:
4552 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4553 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4554 return 0;
4555 goto do_default;
4556 case WM_KEYDOWN:
4557 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4558 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4559 return 0;
4560 if (ME_KeyDown(editor, LOWORD(wParam)))
4561 return 0;
4562 goto do_default;
4563 case WM_CHAR:
4564 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4565 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4566 return 0;
4567 return ME_Char(editor, wParam, lParam, unicode);
4568 case WM_UNICHAR:
4569 if (unicode)
4571 if(wParam == UNICODE_NOCHAR) return TRUE;
4572 if(wParam <= 0x000fffff)
4574 if(wParam > 0xffff) /* convert to surrogates */
4576 wParam -= 0x10000;
4577 ME_Char(editor, (wParam >> 10) + 0xd800, 0, TRUE);
4578 ME_Char(editor, (wParam & 0x03ff) + 0xdc00, 0, TRUE);
4579 } else {
4580 ME_Char(editor, wParam, 0, TRUE);
4583 return 0;
4585 break;
4586 case EM_STOPGROUPTYPING:
4587 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4588 return 0;
4589 case WM_HSCROLL:
4591 const int scrollUnit = 7;
4593 switch(LOWORD(wParam))
4595 case SB_LEFT:
4596 ME_ScrollAbs(editor, 0, 0);
4597 break;
4598 case SB_RIGHT:
4599 ME_ScrollAbs(editor,
4600 editor->horz_si.nMax - (int)editor->horz_si.nPage,
4601 editor->vert_si.nMax - (int)editor->vert_si.nPage);
4602 break;
4603 case SB_LINELEFT:
4604 ME_ScrollLeft(editor, scrollUnit);
4605 break;
4606 case SB_LINERIGHT:
4607 ME_ScrollRight(editor, scrollUnit);
4608 break;
4609 case SB_PAGELEFT:
4610 ME_ScrollLeft(editor, editor->sizeWindow.cx);
4611 break;
4612 case SB_PAGERIGHT:
4613 ME_ScrollRight(editor, editor->sizeWindow.cx);
4614 break;
4615 case SB_THUMBTRACK:
4616 case SB_THUMBPOSITION:
4618 int pos = HIWORD(wParam);
4619 if (editor->horz_si.nMax > 0xffff)
4620 pos = MulDiv(pos, editor->horz_si.nMax, 0xffff);
4621 ME_HScrollAbs(editor, pos);
4622 break;
4625 break;
4627 case EM_SCROLL: /* fall through */
4628 case WM_VSCROLL:
4630 int origNPos;
4631 int lineHeight = get_default_line_height( editor );
4633 origNPos = editor->vert_si.nPos;
4635 switch(LOWORD(wParam))
4637 case SB_TOP:
4638 ME_ScrollAbs(editor, 0, 0);
4639 break;
4640 case SB_BOTTOM:
4641 ME_ScrollAbs(editor,
4642 editor->horz_si.nMax - (int)editor->horz_si.nPage,
4643 editor->vert_si.nMax - (int)editor->vert_si.nPage);
4644 break;
4645 case SB_LINEUP:
4646 ME_ScrollUp(editor,lineHeight);
4647 break;
4648 case SB_LINEDOWN:
4649 ME_ScrollDown(editor,lineHeight);
4650 break;
4651 case SB_PAGEUP:
4652 ME_ScrollUp(editor,editor->sizeWindow.cy);
4653 break;
4654 case SB_PAGEDOWN:
4655 ME_ScrollDown(editor,editor->sizeWindow.cy);
4656 break;
4657 case SB_THUMBTRACK:
4658 case SB_THUMBPOSITION:
4660 int pos = HIWORD(wParam);
4661 if (editor->vert_si.nMax > 0xffff)
4662 pos = MulDiv(pos, editor->vert_si.nMax, 0xffff);
4663 ME_VScrollAbs(editor, pos);
4664 break;
4667 if (msg == EM_SCROLL)
4668 return 0x00010000 | (((editor->vert_si.nPos - origNPos)/lineHeight) & 0xffff);
4669 break;
4671 case WM_MOUSEWHEEL:
4673 int delta;
4674 BOOL ctrl_is_down;
4676 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4677 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4678 return 0;
4680 ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
4682 delta = GET_WHEEL_DELTA_WPARAM(wParam);
4684 /* if scrolling changes direction, ignore left overs */
4685 if ((delta < 0 && editor->wheel_remain < 0) ||
4686 (delta > 0 && editor->wheel_remain > 0))
4687 editor->wheel_remain += delta;
4688 else
4689 editor->wheel_remain = delta;
4691 if (editor->wheel_remain)
4693 if (ctrl_is_down) {
4694 int numerator;
4695 if (!editor->nZoomNumerator || !editor->nZoomDenominator)
4697 numerator = 100;
4698 } else {
4699 numerator = editor->nZoomNumerator * 100 / editor->nZoomDenominator;
4701 numerator += calc_wheel_change( &editor->wheel_remain, 10 );
4702 if (numerator >= 10 && numerator <= 500)
4703 ME_SetZoom(editor, numerator, 100);
4704 } else {
4705 UINT max_lines = 3;
4706 int lines = 0;
4708 SystemParametersInfoW( SPI_GETWHEELSCROLLLINES, 0, &max_lines, 0 );
4709 if (max_lines)
4710 lines = calc_wheel_change( &editor->wheel_remain, (int)max_lines );
4711 if (lines)
4712 ME_ScrollDown( editor, -lines * get_default_line_height( editor ) );
4715 break;
4717 case EM_GETRECT:
4719 *((RECT *)lParam) = editor->rcFormat;
4720 if (editor->bDefaultFormatRect)
4721 ((RECT *)lParam)->left -= editor->selofs;
4722 return 0;
4724 case EM_SETRECT:
4725 case EM_SETRECTNP:
4727 if (lParam)
4729 int border = 0;
4730 RECT clientRect;
4731 RECT *rc = (RECT *)lParam;
4733 border = editor->exStyleFlags & WS_EX_CLIENTEDGE ? 1 : 0;
4734 ITextHost_TxGetClientRect(editor->texthost, &clientRect);
4735 if (wParam == 0)
4737 editor->rcFormat.top = max(0, rc->top - border);
4738 editor->rcFormat.left = max(0, rc->left - border);
4739 editor->rcFormat.bottom = min(clientRect.bottom, rc->bottom);
4740 editor->rcFormat.right = min(clientRect.right, rc->right + border);
4741 } else if (wParam == 1) {
4742 /* MSDN incorrectly says a wParam value of 1 causes the
4743 * lParam rect to be used as a relative offset,
4744 * however, the tests show it just prevents min/max bound
4745 * checking. */
4746 editor->rcFormat.top = rc->top - border;
4747 editor->rcFormat.left = rc->left - border;
4748 editor->rcFormat.bottom = rc->bottom;
4749 editor->rcFormat.right = rc->right + border;
4750 } else {
4751 return 0;
4753 editor->bDefaultFormatRect = FALSE;
4755 else
4757 ME_SetDefaultFormatRect(editor);
4758 editor->bDefaultFormatRect = TRUE;
4760 editor_mark_rewrap_all( editor );
4761 ME_WrapMarkedParagraphs(editor);
4762 ME_UpdateScrollBar(editor);
4763 if (msg != EM_SETRECTNP)
4764 ME_Repaint(editor);
4765 return 0;
4767 case EM_REQUESTRESIZE:
4768 ME_SendRequestResize(editor, TRUE);
4769 return 0;
4770 case WM_SETREDRAW:
4771 goto do_default;
4772 case WM_WINDOWPOSCHANGED:
4774 RECT clientRect;
4775 WINDOWPOS *winpos = (WINDOWPOS *)lParam;
4777 if (winpos->flags & SWP_NOCLIENTSIZE) goto do_default;
4778 ITextHost_TxGetClientRect(editor->texthost, &clientRect);
4779 if (editor->bDefaultFormatRect) {
4780 ME_SetDefaultFormatRect(editor);
4781 } else {
4782 editor->rcFormat.right += clientRect.right - editor->prevClientRect.right;
4783 editor->rcFormat.bottom += clientRect.bottom - editor->prevClientRect.bottom;
4785 editor->prevClientRect = clientRect;
4786 ME_RewrapRepaint(editor);
4787 goto do_default;
4789 /* IME messages to make richedit controls IME aware */
4790 case WM_IME_SETCONTEXT:
4791 case WM_IME_CONTROL:
4792 case WM_IME_SELECT:
4793 case WM_IME_COMPOSITIONFULL:
4794 return 0;
4795 case WM_IME_STARTCOMPOSITION:
4797 editor->imeStartIndex=ME_GetCursorOfs(&editor->pCursors[0]);
4798 ME_DeleteSelection(editor);
4799 ME_CommitUndo(editor);
4800 ME_UpdateRepaint(editor, FALSE);
4801 return 0;
4803 case WM_IME_COMPOSITION:
4805 HIMC hIMC;
4807 ME_Style *style = style_get_insert_style( editor, editor->pCursors );
4808 hIMC = ITextHost_TxImmGetContext(editor->texthost);
4809 ME_DeleteSelection(editor);
4810 ME_SaveTempStyle(editor, style);
4811 if (lParam & (GCS_RESULTSTR|GCS_COMPSTR))
4813 LPWSTR lpCompStr = NULL;
4814 DWORD dwBufLen;
4815 DWORD dwIndex = lParam & GCS_RESULTSTR;
4816 if (!dwIndex)
4817 dwIndex = GCS_COMPSTR;
4819 dwBufLen = ImmGetCompositionStringW(hIMC, dwIndex, NULL, 0);
4820 lpCompStr = HeapAlloc(GetProcessHeap(),0,dwBufLen + sizeof(WCHAR));
4821 ImmGetCompositionStringW(hIMC, dwIndex, lpCompStr, dwBufLen);
4822 lpCompStr[dwBufLen/sizeof(WCHAR)] = 0;
4823 ME_InsertTextFromCursor(editor,0,lpCompStr,dwBufLen/sizeof(WCHAR),style);
4824 HeapFree(GetProcessHeap(), 0, lpCompStr);
4826 if (dwIndex == GCS_COMPSTR)
4827 set_selection_cursors(editor,editor->imeStartIndex,
4828 editor->imeStartIndex + dwBufLen/sizeof(WCHAR));
4830 ME_ReleaseStyle(style);
4831 ME_CommitUndo(editor);
4832 ME_UpdateRepaint(editor, FALSE);
4833 return 0;
4835 case WM_IME_ENDCOMPOSITION:
4837 ME_DeleteSelection(editor);
4838 editor->imeStartIndex=-1;
4839 return 0;
4841 case EM_GETOLEINTERFACE:
4843 if (!editor->reOle)
4844 if (!CreateIRichEditOle(NULL, editor, (LPVOID *)&editor->reOle))
4845 return 0;
4846 if (IUnknown_QueryInterface(editor->reOle, &IID_IRichEditOle, (LPVOID *)lParam) == S_OK)
4847 return 1;
4848 return 0;
4850 case EM_GETPASSWORDCHAR:
4852 return editor->cPasswordMask;
4854 case EM_SETOLECALLBACK:
4855 if(editor->lpOleCallback)
4856 IRichEditOleCallback_Release(editor->lpOleCallback);
4857 editor->lpOleCallback = (IRichEditOleCallback*)lParam;
4858 if(editor->lpOleCallback)
4859 IRichEditOleCallback_AddRef(editor->lpOleCallback);
4860 return TRUE;
4861 case EM_GETWORDBREAKPROC:
4862 return (LRESULT)editor->pfnWordBreak;
4863 case EM_SETWORDBREAKPROC:
4865 EDITWORDBREAKPROCW pfnOld = editor->pfnWordBreak;
4867 editor->pfnWordBreak = (EDITWORDBREAKPROCW)lParam;
4868 return (LRESULT)pfnOld;
4870 case EM_GETTEXTMODE:
4871 return editor->mode;
4872 case EM_SETTEXTMODE:
4874 int mask = 0;
4875 int changes = 0;
4877 if (ME_GetTextLength(editor) ||
4878 !list_empty( &editor->undo_stack ) || !list_empty( &editor->redo_stack ))
4879 return E_UNEXPECTED;
4881 /* Check for mutually exclusive flags in adjacent bits of wParam */
4882 if ((wParam & (TM_RICHTEXT | TM_MULTILEVELUNDO | TM_MULTICODEPAGE)) &
4883 (wParam & (TM_PLAINTEXT | TM_SINGLELEVELUNDO | TM_SINGLECODEPAGE)) << 1)
4884 return E_INVALIDARG;
4886 if (wParam & (TM_RICHTEXT | TM_PLAINTEXT))
4888 mask |= TM_RICHTEXT | TM_PLAINTEXT;
4889 changes |= wParam & (TM_RICHTEXT | TM_PLAINTEXT);
4890 if (wParam & TM_PLAINTEXT) {
4891 /* Clear selection since it should be possible to select the
4892 * end of text run for rich text */
4893 ME_InvalidateSelection(editor);
4894 ME_SetCursorToStart(editor, &editor->pCursors[0]);
4895 editor->pCursors[1] = editor->pCursors[0];
4896 /* plain text can only have the default style. */
4897 ME_ClearTempStyle(editor);
4898 ME_AddRefStyle(editor->pBuffer->pDefaultStyle);
4899 ME_ReleaseStyle( editor->pCursors[0].run->style );
4900 editor->pCursors[0].run->style = editor->pBuffer->pDefaultStyle;
4903 /* FIXME: Currently no support for undo level and code page options */
4904 editor->mode = (editor->mode & ~mask) | changes;
4905 return 0;
4907 case EM_SETPASSWORDCHAR:
4909 editor->cPasswordMask = wParam;
4910 ME_RewrapRepaint(editor);
4911 return 0;
4913 case EM_SETTARGETDEVICE:
4914 if (wParam == 0)
4916 BOOL new = (lParam == 0 && (editor->styleFlags & ES_MULTILINE));
4917 if (editor->nAvailWidth || editor->bWordWrap != new)
4919 editor->bWordWrap = new;
4920 editor->nAvailWidth = 0; /* wrap to client area */
4921 ME_RewrapRepaint(editor);
4923 } else {
4924 int width = max(0, lParam);
4925 if ((editor->styleFlags & ES_MULTILINE) &&
4926 (!editor->bWordWrap || editor->nAvailWidth != width))
4928 editor->nAvailWidth = width;
4929 editor->bWordWrap = TRUE;
4930 ME_RewrapRepaint(editor);
4932 FIXME("EM_SETTARGETDEVICE doesn't use non-NULL target devices\n");
4934 return TRUE;
4935 default:
4936 do_default:
4937 *phresult = S_FALSE;
4938 break;
4940 return 0L;
4943 static BOOL create_windowed_editor(HWND hwnd, CREATESTRUCTW *create, BOOL emulate_10)
4945 ITextHost *host = ME_CreateTextHost( hwnd, create, emulate_10 );
4946 ME_TextEditor *editor;
4948 if (!host) return FALSE;
4950 editor = ME_MakeEditor( host, emulate_10 );
4951 if (!editor)
4953 ITextHost_Release( host );
4954 return FALSE;
4957 editor->exStyleFlags = GetWindowLongW( hwnd, GWL_EXSTYLE );
4958 editor->styleFlags |= GetWindowLongW( hwnd, GWL_STYLE ) & ES_WANTRETURN;
4959 editor->hWnd = hwnd; /* FIXME: Remove editor's dependence on hWnd */
4960 editor->hwndParent = create->hwndParent;
4962 SetWindowLongPtrW( hwnd, 0, (LONG_PTR)editor );
4964 return TRUE;
4967 static LRESULT RichEditWndProc_common(HWND hWnd, UINT msg, WPARAM wParam,
4968 LPARAM lParam, BOOL unicode)
4970 ME_TextEditor *editor;
4971 HRESULT hresult;
4972 LRESULT lresult = 0;
4974 TRACE("enter hwnd %p msg %04x (%s) %lx %lx, unicode %d\n",
4975 hWnd, msg, get_msg_name(msg), wParam, lParam, unicode);
4977 editor = (ME_TextEditor *)GetWindowLongPtrW(hWnd, 0);
4978 if (!editor)
4980 if (msg == WM_NCCREATE)
4982 CREATESTRUCTW *pcs = (CREATESTRUCTW *)lParam;
4984 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd, pcs->style);
4985 return create_windowed_editor( hWnd, pcs, FALSE );
4987 else
4989 return DefWindowProcW(hWnd, msg, wParam, lParam);
4993 switch (msg)
4995 case WM_PAINT:
4997 HDC hdc;
4998 RECT rc;
4999 PAINTSTRUCT ps;
5000 HBRUSH old_brush;
5002 update_caret(editor);
5003 hdc = BeginPaint(editor->hWnd, &ps);
5004 if (!editor->bEmulateVersion10 || (editor->nEventMask & ENM_UPDATE))
5005 ME_SendOldNotify(editor, EN_UPDATE);
5006 old_brush = SelectObject(hdc, editor->hbrBackground);
5008 /* Erase area outside of the formatting rectangle */
5009 if (ps.rcPaint.top < editor->rcFormat.top)
5011 rc = ps.rcPaint;
5012 rc.bottom = editor->rcFormat.top;
5013 PatBlt(hdc, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, PATCOPY);
5014 ps.rcPaint.top = editor->rcFormat.top;
5016 if (ps.rcPaint.bottom > editor->rcFormat.bottom) {
5017 rc = ps.rcPaint;
5018 rc.top = editor->rcFormat.bottom;
5019 PatBlt(hdc, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, PATCOPY);
5020 ps.rcPaint.bottom = editor->rcFormat.bottom;
5022 if (ps.rcPaint.left < editor->rcFormat.left) {
5023 rc = ps.rcPaint;
5024 rc.right = editor->rcFormat.left;
5025 PatBlt(hdc, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, PATCOPY);
5026 ps.rcPaint.left = editor->rcFormat.left;
5028 if (ps.rcPaint.right > editor->rcFormat.right) {
5029 rc = ps.rcPaint;
5030 rc.left = editor->rcFormat.right;
5031 PatBlt(hdc, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, PATCOPY);
5032 ps.rcPaint.right = editor->rcFormat.right;
5035 ME_PaintContent(editor, hdc, &ps.rcPaint);
5036 SelectObject(hdc, old_brush);
5037 EndPaint(editor->hWnd, &ps);
5038 return 0;
5040 case WM_ERASEBKGND:
5042 HDC hDC = (HDC)wParam;
5043 RECT rc;
5045 if (GetUpdateRect(editor->hWnd, &rc, TRUE))
5046 FillRect(hDC, &rc, editor->hbrBackground);
5047 return 1;
5049 case EM_SETOPTIONS:
5051 DWORD dwStyle;
5052 const DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
5053 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN |
5054 ECO_SELECTIONBAR;
5055 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
5056 dwStyle = GetWindowLongW(hWnd, GWL_STYLE);
5057 dwStyle = (dwStyle & ~mask) | (lresult & mask);
5058 SetWindowLongW(hWnd, GWL_STYLE, dwStyle);
5059 return lresult;
5061 case EM_SETREADONLY:
5063 DWORD dwStyle;
5064 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
5065 dwStyle = GetWindowLongW(hWnd, GWL_STYLE);
5066 dwStyle &= ~ES_READONLY;
5067 if (wParam)
5068 dwStyle |= ES_READONLY;
5069 SetWindowLongW(hWnd, GWL_STYLE, dwStyle);
5070 return lresult;
5072 default:
5073 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
5076 if (hresult == S_FALSE)
5077 lresult = DefWindowProcW(hWnd, msg, wParam, lParam);
5079 TRACE("exit hwnd %p msg %04x (%s) %lx %lx, unicode %d -> %lu\n",
5080 hWnd, msg, get_msg_name(msg), wParam, lParam, unicode, lresult);
5082 return lresult;
5085 static LRESULT WINAPI RichEditWndProcW(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5087 BOOL unicode = TRUE;
5089 /* Under Win9x RichEdit20W returns ANSI strings, see the tests. */
5090 if (msg == WM_GETTEXT && (GetVersion() & 0x80000000))
5091 unicode = FALSE;
5093 return RichEditWndProc_common(hWnd, msg, wParam, lParam, unicode);
5096 static LRESULT WINAPI RichEditWndProcA(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5098 return RichEditWndProc_common(hWnd, msg, wParam, lParam, FALSE);
5101 /******************************************************************
5102 * RichEditANSIWndProc (RICHED20.10)
5104 LRESULT WINAPI RichEditANSIWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5106 return RichEditWndProcA(hWnd, msg, wParam, lParam);
5109 /******************************************************************
5110 * RichEdit10ANSIWndProc (RICHED20.9)
5112 LRESULT WINAPI RichEdit10ANSIWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5114 if (msg == WM_NCCREATE && !GetWindowLongPtrW(hWnd, 0))
5116 CREATESTRUCTW *pcs = (CREATESTRUCTW *)lParam;
5118 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd, pcs->style);
5119 return create_windowed_editor( hWnd, pcs, TRUE );
5121 return RichEditANSIWndProc(hWnd, msg, wParam, lParam);
5124 void ME_SendOldNotify(ME_TextEditor *editor, int nCode)
5126 ITextHost_TxNotify(editor->texthost, nCode, NULL);
5129 /* Fill buffer with srcChars unicode characters from the start cursor.
5131 * buffer: destination buffer
5132 * buflen: length of buffer in characters excluding the NULL terminator.
5133 * start: start of editor text to copy into buffer.
5134 * srcChars: Number of characters to use from the editor text.
5135 * bCRLF: if true, replaces all end of lines with \r\n pairs.
5137 * returns the number of characters written excluding the NULL terminator.
5139 * The written text is always NULL terminated.
5141 int ME_GetTextW(ME_TextEditor *editor, WCHAR *buffer, int buflen,
5142 const ME_Cursor *start, int srcChars, BOOL bCRLF,
5143 BOOL bEOP)
5145 ME_Run *run, *next_run;
5146 const WCHAR *pStart = buffer;
5147 const WCHAR *str;
5148 int nLen;
5150 /* bCRLF flag is only honored in 2.0 and up. 1.0 must always return text verbatim */
5151 if (editor->bEmulateVersion10) bCRLF = FALSE;
5153 run = start->run;
5154 next_run = run_next_all_paras( run );
5156 nLen = run->len - start->nOffset;
5157 str = get_text( run, start->nOffset );
5159 while (srcChars && buflen && next_run)
5161 if (bCRLF && run->nFlags & MERF_ENDPARA && ~run->nFlags & MERF_ENDCELL)
5163 if (buflen == 1) break;
5164 /* FIXME: native fails to reduce srcChars here for WM_GETTEXT or
5165 * EM_GETTEXTEX, however, this is done for copying text which
5166 * also uses this function. */
5167 srcChars -= min(nLen, srcChars);
5168 nLen = 2;
5169 str = L"\r\n";
5171 else
5173 nLen = min(nLen, srcChars);
5174 srcChars -= nLen;
5177 nLen = min(nLen, buflen);
5178 buflen -= nLen;
5180 CopyMemory(buffer, str, sizeof(WCHAR) * nLen);
5182 buffer += nLen;
5184 run = next_run;
5185 next_run = run_next_all_paras( run );
5187 nLen = run->len;
5188 str = get_text( run, 0 );
5190 /* append '\r' to the last paragraph. */
5191 if (run == para_end_run( para_prev( editor_end_para( editor ) ) ) && bEOP)
5193 *buffer = '\r';
5194 buffer ++;
5196 *buffer = 0;
5197 return buffer - pStart;
5200 static BOOL ME_RegisterEditorClass(HINSTANCE hInstance)
5202 WNDCLASSW wcW;
5203 WNDCLASSA wcA;
5205 wcW.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS;
5206 wcW.lpfnWndProc = RichEditWndProcW;
5207 wcW.cbClsExtra = 0;
5208 wcW.cbWndExtra = sizeof(ME_TextEditor *);
5209 wcW.hInstance = NULL; /* hInstance would register DLL-local class */
5210 wcW.hIcon = NULL;
5211 wcW.hCursor = LoadCursorW(NULL, (LPWSTR)IDC_IBEAM);
5212 wcW.hbrBackground = GetStockObject(NULL_BRUSH);
5213 wcW.lpszMenuName = NULL;
5215 if (is_version_nt())
5217 wcW.lpszClassName = RICHEDIT_CLASS20W;
5218 if (!RegisterClassW(&wcW)) return FALSE;
5219 wcW.lpszClassName = MSFTEDIT_CLASS;
5220 if (!RegisterClassW(&wcW)) return FALSE;
5222 else
5224 /* WNDCLASSA/W have the same layout */
5225 wcW.lpszClassName = (LPCWSTR)"RichEdit20W";
5226 if (!RegisterClassA((WNDCLASSA *)&wcW)) return FALSE;
5227 wcW.lpszClassName = (LPCWSTR)"RichEdit50W";
5228 if (!RegisterClassA((WNDCLASSA *)&wcW)) return FALSE;
5231 wcA.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS;
5232 wcA.lpfnWndProc = RichEditWndProcA;
5233 wcA.cbClsExtra = 0;
5234 wcA.cbWndExtra = sizeof(ME_TextEditor *);
5235 wcA.hInstance = NULL; /* hInstance would register DLL-local class */
5236 wcA.hIcon = NULL;
5237 wcA.hCursor = LoadCursorW(NULL, (LPWSTR)IDC_IBEAM);
5238 wcA.hbrBackground = GetStockObject(NULL_BRUSH);
5239 wcA.lpszMenuName = NULL;
5240 wcA.lpszClassName = RICHEDIT_CLASS20A;
5241 if (!RegisterClassA(&wcA)) return FALSE;
5242 wcA.lpszClassName = "RichEdit50A";
5243 if (!RegisterClassA(&wcA)) return FALSE;
5245 return TRUE;
5248 static LRESULT WINAPI REComboWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
5249 /* FIXME: Not implemented */
5250 TRACE("hWnd %p msg %04x (%s) %08lx %08lx\n",
5251 hWnd, msg, get_msg_name(msg), wParam, lParam);
5252 return DefWindowProcW(hWnd, msg, wParam, lParam);
5255 static LRESULT WINAPI REListWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
5256 /* FIXME: Not implemented */
5257 TRACE("hWnd %p msg %04x (%s) %08lx %08lx\n",
5258 hWnd, msg, get_msg_name(msg), wParam, lParam);
5259 return DefWindowProcW(hWnd, msg, wParam, lParam);
5262 /******************************************************************
5263 * REExtendedRegisterClass (RICHED20.8)
5265 * FIXME undocumented
5266 * Need to check for errors and implement controls and callbacks
5268 LRESULT WINAPI REExtendedRegisterClass(void)
5270 WNDCLASSW wcW;
5271 UINT result;
5273 FIXME("semi stub\n");
5275 wcW.cbClsExtra = 0;
5276 wcW.cbWndExtra = 4;
5277 wcW.hInstance = NULL;
5278 wcW.hIcon = NULL;
5279 wcW.hCursor = NULL;
5280 wcW.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
5281 wcW.lpszMenuName = NULL;
5283 if (!ME_ListBoxRegistered)
5285 wcW.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS;
5286 wcW.lpfnWndProc = REListWndProc;
5287 wcW.lpszClassName = L"REListBox20W";
5288 if (RegisterClassW(&wcW)) ME_ListBoxRegistered = TRUE;
5291 if (!ME_ComboBoxRegistered)
5293 wcW.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS | CS_VREDRAW | CS_HREDRAW;
5294 wcW.lpfnWndProc = REComboWndProc;
5295 wcW.lpszClassName = L"REComboBox20W";
5296 if (RegisterClassW(&wcW)) ME_ComboBoxRegistered = TRUE;
5299 result = 0;
5300 if (ME_ListBoxRegistered)
5301 result += 1;
5302 if (ME_ComboBoxRegistered)
5303 result += 2;
5305 return result;
5308 static int __cdecl wchar_comp( const void *key, const void *elem )
5310 return *(const WCHAR *)key - *(const WCHAR *)elem;
5313 /* neutral characters end the url if the next non-neutral character is a space character,
5314 otherwise they are included in the url. */
5315 static BOOL isurlneutral( WCHAR c )
5317 /* NB this list is sorted */
5318 static const WCHAR neutral_chars[] = {'!','\"','\'','(',')',',','-','.',':',';','<','>','?','[',']','{','}'};
5320 /* Some shortcuts */
5321 if (isalnum( c )) return FALSE;
5322 if (c > neutral_chars[ARRAY_SIZE( neutral_chars ) - 1]) return FALSE;
5324 return !!bsearch( &c, neutral_chars, ARRAY_SIZE( neutral_chars ), sizeof(c), wchar_comp );
5328 * This proc takes a selection, and scans it forward in order to select the span
5329 * of a possible URL candidate. A possible URL candidate must start with isalnum
5330 * or one of the following special characters: *|/\+%#@ and must consist entirely
5331 * of the characters allowed to start the URL, plus : (colon) which may occur
5332 * at most once, and not at either end.
5334 static BOOL ME_FindNextURLCandidate(ME_TextEditor *editor,
5335 const ME_Cursor *start,
5336 int nChars,
5337 ME_Cursor *candidate_min,
5338 ME_Cursor *candidate_max)
5340 ME_Cursor cursor = *start, neutral_end, space_end;
5341 BOOL candidateStarted = FALSE, quoted = FALSE;
5342 WCHAR c;
5344 while (nChars > 0)
5346 WCHAR *str = get_text( cursor.run, 0 );
5347 int run_len = cursor.run->len;
5349 nChars -= run_len - cursor.nOffset;
5351 /* Find start of candidate */
5352 if (!candidateStarted)
5354 while (cursor.nOffset < run_len)
5356 c = str[cursor.nOffset];
5357 if (!iswspace( c ) && !isurlneutral( c ))
5359 *candidate_min = cursor;
5360 candidateStarted = TRUE;
5361 neutral_end.para = NULL;
5362 space_end.para = NULL;
5363 cursor.nOffset++;
5364 break;
5366 quoted = (c == '<');
5367 cursor.nOffset++;
5371 /* Find end of candidate */
5372 if (candidateStarted)
5374 while (cursor.nOffset < run_len)
5376 c = str[cursor.nOffset];
5377 if (iswspace( c ))
5379 if (quoted && c != '\r')
5381 if (!space_end.para)
5383 if (neutral_end.para)
5384 space_end = neutral_end;
5385 else
5386 space_end = cursor;
5389 else
5390 goto done;
5392 else if (isurlneutral( c ))
5394 if (quoted && c == '>')
5396 neutral_end.para = NULL;
5397 space_end.para = NULL;
5398 goto done;
5400 if (!neutral_end.para)
5401 neutral_end = cursor;
5403 else
5404 neutral_end.para = NULL;
5406 cursor.nOffset++;
5410 cursor.nOffset = 0;
5411 if (!cursor_next_run( &cursor, TRUE ))
5412 goto done;
5415 done:
5416 if (candidateStarted)
5418 if (space_end.para)
5419 *candidate_max = space_end;
5420 else if (neutral_end.para)
5421 *candidate_max = neutral_end;
5422 else
5423 *candidate_max = cursor;
5424 return TRUE;
5426 *candidate_max = *candidate_min = cursor;
5427 return FALSE;
5431 * This proc evaluates the selection and returns TRUE if it can be considered an URL
5433 static BOOL ME_IsCandidateAnURL(ME_TextEditor *editor, const ME_Cursor *start, int nChars)
5435 #define MAX_PREFIX_LEN 9
5436 #define X(str) str, ARRAY_SIZE(str) - 1
5437 struct prefix_s {
5438 const WCHAR text[MAX_PREFIX_LEN];
5439 int length;
5440 }prefixes[] = {
5441 {X(L"prospero:")},
5442 {X(L"telnet:")},
5443 {X(L"gopher:")},
5444 {X(L"mailto:")},
5445 {X(L"https:")},
5446 {X(L"file:")},
5447 {X(L"news:")},
5448 {X(L"wais:")},
5449 {X(L"nntp:")},
5450 {X(L"http:")},
5451 {X(L"www.")},
5452 {X(L"ftp:")},
5454 #undef X
5455 WCHAR bufferW[MAX_PREFIX_LEN + 1];
5456 unsigned int i;
5458 ME_GetTextW(editor, bufferW, MAX_PREFIX_LEN, start, nChars, FALSE, FALSE);
5459 for (i = 0; i < ARRAY_SIZE(prefixes); i++)
5461 if (nChars < prefixes[i].length) continue;
5462 if (!memcmp(prefixes[i].text, bufferW, prefixes[i].length * sizeof(WCHAR)))
5463 return TRUE;
5465 return FALSE;
5466 #undef MAX_PREFIX_LEN
5470 * This proc walks through the indicated selection and evaluates whether each
5471 * section identified by ME_FindNextURLCandidate and in-between sections have
5472 * their proper CFE_LINK attributes set or unset. If the CFE_LINK attribute is
5473 * not what it is supposed to be, this proc sets or unsets it as appropriate.
5475 * Since this function can cause runs to be split, do not depend on the value
5476 * of the start cursor at the end of the function.
5478 * nChars may be set to INT_MAX to update to the end of the text.
5480 * Returns TRUE if at least one section was modified.
5482 static BOOL ME_UpdateLinkAttribute(ME_TextEditor *editor, ME_Cursor *start, int nChars)
5484 BOOL modified = FALSE;
5485 ME_Cursor startCur = *start;
5487 if (!editor->AutoURLDetect_bEnable) return FALSE;
5491 CHARFORMAT2W link;
5492 ME_Cursor candidateStart, candidateEnd;
5494 if (ME_FindNextURLCandidate(editor, &startCur, nChars,
5495 &candidateStart, &candidateEnd))
5497 /* Section before candidate is not an URL */
5498 int cMin = ME_GetCursorOfs(&candidateStart);
5499 int cMax = ME_GetCursorOfs(&candidateEnd);
5501 if (!ME_IsCandidateAnURL(editor, &candidateStart, cMax - cMin))
5502 candidateStart = candidateEnd;
5503 nChars -= cMax - ME_GetCursorOfs(&startCur);
5505 else
5507 /* No more candidates until end of selection */
5508 nChars = 0;
5511 if (startCur.run != candidateStart.run ||
5512 startCur.nOffset != candidateStart.nOffset)
5514 /* CFE_LINK effect should be consistently unset */
5515 link.cbSize = sizeof(link);
5516 ME_GetCharFormat(editor, &startCur, &candidateStart, &link);
5517 if (!(link.dwMask & CFM_LINK) || (link.dwEffects & CFE_LINK))
5519 /* CFE_LINK must be unset from this range */
5520 memset(&link, 0, sizeof(CHARFORMAT2W));
5521 link.cbSize = sizeof(link);
5522 link.dwMask = CFM_LINK;
5523 link.dwEffects = 0;
5524 ME_SetCharFormat(editor, &startCur, &candidateStart, &link);
5525 /* Update candidateEnd since setting character formats may split
5526 * runs, which can cause a cursor to be at an invalid offset within
5527 * a split run. */
5528 while (candidateEnd.nOffset >= candidateEnd.run->len)
5530 candidateEnd.nOffset -= candidateEnd.run->len;
5531 candidateEnd.run = run_next_all_paras( candidateEnd.run );
5533 modified = TRUE;
5536 if (candidateStart.run != candidateEnd.run ||
5537 candidateStart.nOffset != candidateEnd.nOffset)
5539 /* CFE_LINK effect should be consistently set */
5540 link.cbSize = sizeof(link);
5541 ME_GetCharFormat(editor, &candidateStart, &candidateEnd, &link);
5542 if (!(link.dwMask & CFM_LINK) || !(link.dwEffects & CFE_LINK))
5544 /* CFE_LINK must be set on this range */
5545 memset(&link, 0, sizeof(CHARFORMAT2W));
5546 link.cbSize = sizeof(link);
5547 link.dwMask = CFM_LINK;
5548 link.dwEffects = CFE_LINK;
5549 ME_SetCharFormat(editor, &candidateStart, &candidateEnd, &link);
5550 modified = TRUE;
5553 startCur = candidateEnd;
5554 } while (nChars > 0);
5555 return modified;