makefiles: Explicitly create destination dirs when installing symlinks.
[wine/zf.git] / dlls / riched20 / editor.c
blob6a2e96a3c7c644541792960e8c4526d9fca3a5ef
1 /*
2 * RichEdit - functions dealing with editor object
4 * Copyright 2004 by Krzysztof Foltman
5 * Copyright 2005 by Cihan Altinay
6 * Copyright 2005 by Phil Krylov
7 * Copyright 2008 Eric Pouech
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24 /*
25 API implementation status:
27 Messages (ANSI versions not done yet)
28 + EM_AUTOURLDETECT 2.0
29 + EM_CANPASTE
30 + EM_CANREDO 2.0
31 + EM_CANUNDO
32 + EM_CHARFROMPOS
33 - EM_DISPLAYBAND
34 + EM_EMPTYUNDOBUFFER
35 + EM_EXGETSEL
36 + EM_EXLIMITTEXT
37 + EM_EXLINEFROMCHAR
38 + EM_EXSETSEL
39 + EM_FINDTEXT (only FR_DOWN flag implemented)
40 + EM_FINDTEXTEX (only FR_DOWN flag implemented)
41 - EM_FINDWORDBREAK
42 - EM_FMTLINES
43 - EM_FORMATRANGE
44 + EM_GETAUTOURLDETECT 2.0
45 - EM_GETBIDIOPTIONS 3.0
46 - EM_GETCHARFORMAT (partly done)
47 - EM_GETEDITSTYLE
48 + EM_GETEVENTMASK
49 + EM_GETFIRSTVISIBLELINE (can be optimized if needed)
50 - EM_GETIMECOLOR 1.0asian
51 - EM_GETIMECOMPMODE 2.0
52 - EM_GETIMEOPTIONS 1.0asian
53 - EM_GETIMESTATUS
54 - EM_GETLANGOPTIONS 2.0
55 + EM_GETLIMITTEXT
56 + EM_GETLINE
57 + EM_GETLINECOUNT returns number of rows, not of paragraphs
58 + EM_GETMODIFY
59 + EM_GETOLEINTERFACE
60 + EM_GETOPTIONS
61 + EM_GETPARAFORMAT
62 + EM_GETPASSWORDCHAR 2.0
63 - EM_GETPUNCTUATION 1.0asian
64 + EM_GETRECT
65 - EM_GETREDONAME 2.0
66 + EM_GETSEL
67 + EM_GETSELTEXT (ANSI&Unicode)
68 + EM_GETSCROLLPOS 3.0
69 ! - EM_GETTHUMB
70 + EM_GETTEXTEX 2.0
71 + EM_GETTEXTLENGTHEX (GTL_PRECISE unimplemented)
72 + EM_GETTEXTMODE 2.0
73 ? + EM_GETTEXTRANGE (ANSI&Unicode)
74 - EM_GETTYPOGRAPHYOPTIONS 3.0
75 - EM_GETUNDONAME
76 + EM_GETWORDBREAKPROC
77 - EM_GETWORDBREAKPROCEX
78 - EM_GETWORDWRAPMODE 1.0asian
79 + EM_GETZOOM 3.0
80 + EM_HIDESELECTION
81 + EM_LIMITTEXT (Also called EM_SETLIMITTEXT)
82 + EM_LINEFROMCHAR
83 + EM_LINEINDEX
84 + EM_LINELENGTH
85 + EM_LINESCROLL
86 - EM_PASTESPECIAL
87 + EM_POSFROMCHAR
88 + EM_REDO 2.0
89 + EM_REQUESTRESIZE
90 + EM_REPLACESEL (proper style?) ANSI&Unicode
91 + EM_SCROLL
92 + EM_SCROLLCARET
93 + EM_SELECTIONTYPE
94 - EM_SETBIDIOPTIONS 3.0
95 + EM_SETBKGNDCOLOR
96 + EM_SETCHARFORMAT (partly done, no ANSI)
97 - EM_SETEDITSTYLE
98 + EM_SETEVENTMASK (few notifications supported)
99 + EM_SETFONTSIZE
100 - EM_SETIMECOLOR 1.0asian
101 - EM_SETIMEOPTIONS 1.0asian
102 - EM_SETIMESTATUS
103 - EM_SETLANGOPTIONS 2.0
104 - EM_SETLIMITTEXT
105 - EM_SETMARGINS
106 + EM_SETMODIFY (not sure if implementation is correct)
107 - EM_SETOLECALLBACK
108 + EM_SETOPTIONS (partially implemented)
109 - EM_SETPALETTE 2.0
110 + EM_SETPARAFORMAT
111 + EM_SETPASSWORDCHAR 2.0
112 - EM_SETPUNCTUATION 1.0asian
113 + EM_SETREADONLY no beep on modification attempt
114 + EM_SETRECT
115 + EM_SETRECTNP (EM_SETRECT without repainting)
116 + EM_SETSEL
117 + EM_SETSCROLLPOS 3.0
118 - EM_SETTABSTOPS 3.0
119 - EM_SETTARGETDEVICE (partial)
120 + EM_SETTEXTEX 3.0 (proper style?)
121 - EM_SETTEXTMODE 2.0
122 - EM_SETTYPOGRAPHYOPTIONS 3.0
123 + EM_SETUNDOLIMIT 2.0
124 + EM_SETWORDBREAKPROC (used only for word movement at the moment)
125 - EM_SETWORDBREAKPROCEX
126 - EM_SETWORDWRAPMODE 1.0asian
127 + EM_SETZOOM 3.0
128 + EM_SHOWSCROLLBAR 2.0
129 + EM_STOPGROUPTYPING 2.0
130 + EM_STREAMIN
131 + EM_STREAMOUT
132 + EM_UNDO
133 + WM_CHAR
134 + WM_CLEAR
135 + WM_COPY
136 + WM_CUT
137 + WM_GETDLGCODE (the current implementation is incomplete)
138 + WM_GETTEXT (ANSI&Unicode)
139 + WM_GETTEXTLENGTH (ANSI version sucks)
140 + WM_HSCROLL
141 + WM_PASTE
142 + WM_SETFONT
143 + WM_SETTEXT (resets undo stack !) (proper style?) ANSI&Unicode
144 + WM_STYLECHANGING (seems to do nothing)
145 + WM_STYLECHANGED (seems to do nothing)
146 + WM_UNICHAR
147 + WM_VSCROLL
149 Notifications
151 * EN_CHANGE (sent from the wrong place)
152 - EN_CORRECTTEXT
153 - EN_DROPFILES
154 - EN_ERRSPACE
155 - EN_HSCROLL
156 - EN_IMECHANGE
157 + EN_KILLFOCUS
158 - EN_LINK
159 - EN_MAXTEXT
160 - EN_MSGFILTER
161 - EN_OLEOPFAILED
162 - EN_PROTECTED
163 + EN_REQUESTRESIZE
164 - EN_SAVECLIPBOARD
165 + EN_SELCHANGE
166 + EN_SETFOCUS
167 - EN_STOPNOUNDO
168 * EN_UPDATE (sent from the wrong place)
169 - EN_VSCROLL
171 Styles
173 - ES_AUTOHSCROLL
174 - ES_AUTOVSCROLL
175 + ES_CENTER
176 + ES_DISABLENOSCROLL (scrollbar is always visible)
177 - ES_EX_NOCALLOLEINIT
178 + ES_LEFT
179 + ES_MULTILINE
180 - ES_NOIME
181 - ES_READONLY (I'm not sure if beeping is the proper behaviour)
182 + ES_RIGHT
183 - ES_SAVESEL
184 - ES_SELFIME
185 - ES_SUNKEN
186 - ES_VERTICAL
187 - ES_WANTRETURN (don't know how to do WM_GETDLGCODE part)
188 - WS_SETFONT
189 + WS_HSCROLL
190 + WS_VSCROLL
194 * RICHED20 TODO (incomplete):
196 * - messages/styles/notifications listed above
197 * - add remaining CHARFORMAT/PARAFORMAT fields
198 * - right/center align should strip spaces from the beginning
199 * - pictures/OLE objects (not just smiling faces that lack API support ;-) )
200 * - COM interface (looks like a major pain in the TODO list)
201 * - calculate heights of pictures (half-done)
202 * - hysteresis during wrapping (related to scrollbars appearing/disappearing)
203 * - find/replace
204 * - how to implement EM_FORMATRANGE and EM_DISPLAYBAND ? (Mission Impossible)
205 * - italic caret with italic fonts
206 * - IME
207 * - most notifications aren't sent at all (the most important ones are)
208 * - when should EN_SELCHANGE be sent after text change ? (before/after EN_UPDATE?)
209 * - WM_SETTEXT may use wrong style (but I'm 80% sure it's OK)
210 * - EM_GETCHARFORMAT with SCF_SELECTION may not behave 100% like in original (but very close)
211 * - full justification
212 * - hyphenation
213 * - tables
214 * - ListBox & ComboBox not implemented
216 * Bugs that are probably fixed, but not so easy to verify:
217 * - EN_UPDATE/EN_CHANGE are handled very incorrectly (should be OK now)
218 * - undo for ME_JoinParagraphs doesn't store paragraph format ? (it does)
219 * - check/fix artificial EOL logic (bCursorAtEnd, hardly logical)
220 * - caret shouldn't be displayed when selection isn't empty
221 * - check refcounting in style management functions (looks perfect now, but no bugs is suspicious)
222 * - undo for setting default format (done, might be buggy)
223 * - styles might be not released properly (looks like they work like charm, but who knows?
227 #define NONAMELESSUNION
229 #include "editor.h"
230 #include "commdlg.h"
231 #include "winreg.h"
232 #define NO_SHLWAPI_STREAM
233 #include "shlwapi.h"
234 #include "rtf.h"
235 #include "imm.h"
236 #include "res.h"
238 #define STACK_SIZE_DEFAULT 100
239 #define STACK_SIZE_MAX 1000
241 #define TEXT_LIMIT_DEFAULT 32767
243 WINE_DEFAULT_DEBUG_CHANNEL(richedit);
245 static BOOL ME_RegisterEditorClass(HINSTANCE);
246 static BOOL ME_UpdateLinkAttribute(ME_TextEditor *editor, ME_Cursor *start, int nChars);
248 static const WCHAR REListBox20W[] = {'R','E','L','i','s','t','B','o','x','2','0','W', 0};
249 static const WCHAR REComboBox20W[] = {'R','E','C','o','m','b','o','B','o','x','2','0','W', 0};
250 static HCURSOR hLeft;
252 BOOL me_debug = FALSE;
253 HANDLE me_heap = NULL;
255 static BOOL ME_ListBoxRegistered = FALSE;
256 static BOOL ME_ComboBoxRegistered = FALSE;
258 static inline BOOL is_version_nt(void)
260 return !(GetVersion() & 0x80000000);
263 static ME_TextBuffer *ME_MakeText(void) {
264 ME_TextBuffer *buf = heap_alloc(sizeof(*buf));
265 ME_DisplayItem *p1 = ME_MakeDI(diTextStart);
266 ME_DisplayItem *p2 = ME_MakeDI(diTextEnd);
268 p1->prev = NULL;
269 p1->next = p2;
270 p2->prev = p1;
271 p2->next = NULL;
272 p1->member.para.next_para = p2;
273 p2->member.para.prev_para = p1;
274 p2->member.para.nCharOfs = 0;
276 buf->pFirst = p1;
277 buf->pLast = p2;
278 buf->pCharStyle = NULL;
280 return buf;
283 ME_Paragraph *editor_first_para( ME_TextEditor *editor )
285 return para_next( &editor->pBuffer->pFirst->member.para );
288 /* Note, returns the diTextEnd sentinel paragraph */
289 ME_Paragraph *editor_end_para( ME_TextEditor *editor )
291 return &editor->pBuffer->pLast->member.para;
294 static LRESULT ME_StreamInText(ME_TextEditor *editor, DWORD dwFormat, ME_InStream *stream, ME_Style *style)
296 WCHAR *pText;
297 LRESULT total_bytes_read = 0;
298 BOOL is_read = FALSE;
299 DWORD cp = CP_ACP, copy = 0;
300 char conv_buf[4 + STREAMIN_BUFFER_SIZE]; /* up to 4 additional UTF-8 bytes */
302 static const char bom_utf8[] = {0xEF, 0xBB, 0xBF};
304 TRACE("%08x %p\n", dwFormat, stream);
306 do {
307 LONG nWideChars = 0;
308 WCHAR wszText[STREAMIN_BUFFER_SIZE+1];
310 if (!stream->dwSize)
312 ME_StreamInFill(stream);
313 if (stream->editstream->dwError)
314 break;
315 if (!stream->dwSize)
316 break;
317 total_bytes_read += stream->dwSize;
320 if (!(dwFormat & SF_UNICODE))
322 char * buf = stream->buffer;
323 DWORD size = stream->dwSize, end;
325 if (!is_read)
327 is_read = TRUE;
328 if (stream->dwSize >= 3 && !memcmp(stream->buffer, bom_utf8, 3))
330 cp = CP_UTF8;
331 buf += 3;
332 size -= 3;
336 if (cp == CP_UTF8)
338 if (copy)
340 memcpy(conv_buf + copy, buf, size);
341 buf = conv_buf;
342 size += copy;
344 end = size;
345 while ((buf[end-1] & 0xC0) == 0x80)
347 --end;
348 --total_bytes_read; /* strange, but seems to match windows */
350 if (buf[end-1] & 0x80)
352 DWORD need = 0;
353 if ((buf[end-1] & 0xE0) == 0xC0)
354 need = 1;
355 if ((buf[end-1] & 0xF0) == 0xE0)
356 need = 2;
357 if ((buf[end-1] & 0xF8) == 0xF0)
358 need = 3;
360 if (size - end >= need)
362 /* we have enough bytes for this sequence */
363 end = size;
365 else
367 /* need more bytes, so don't transcode this sequence */
368 --end;
372 else
373 end = size;
375 nWideChars = MultiByteToWideChar(cp, 0, buf, end, wszText, STREAMIN_BUFFER_SIZE);
376 pText = wszText;
378 if (cp == CP_UTF8)
380 if (end != size)
382 memcpy(conv_buf, buf + end, size - end);
383 copy = size - end;
387 else
389 nWideChars = stream->dwSize >> 1;
390 pText = (WCHAR *)stream->buffer;
393 ME_InsertTextFromCursor(editor, 0, pText, nWideChars, style);
394 if (stream->dwSize == 0)
395 break;
396 stream->dwSize = 0;
397 } while(1);
398 return total_bytes_read;
401 static void ME_ApplyBorderProperties(RTF_Info *info,
402 ME_BorderRect *borderRect,
403 RTFBorder *borderDef)
405 int i, colorNum;
406 ME_Border *pBorders[] = {&borderRect->top,
407 &borderRect->left,
408 &borderRect->bottom,
409 &borderRect->right};
410 for (i = 0; i < 4; i++)
412 RTFColor *colorDef = info->colorList;
413 pBorders[i]->width = borderDef[i].width;
414 colorNum = borderDef[i].color;
415 while (colorDef && colorDef->rtfCNum != colorNum)
416 colorDef = colorDef->rtfNextColor;
417 if (colorDef)
418 pBorders[i]->colorRef = RGB(
419 colorDef->rtfCRed >= 0 ? colorDef->rtfCRed : 0,
420 colorDef->rtfCGreen >= 0 ? colorDef->rtfCGreen : 0,
421 colorDef->rtfCBlue >= 0 ? colorDef->rtfCBlue : 0);
422 else
423 pBorders[i]->colorRef = RGB(0, 0, 0);
427 void ME_RTFCharAttrHook(RTF_Info *info)
429 CHARFORMAT2W fmt;
430 fmt.cbSize = sizeof(fmt);
431 fmt.dwMask = 0;
432 fmt.dwEffects = 0;
434 switch(info->rtfMinor)
436 case rtfPlain:
437 /* FIXME add more flags once they're implemented */
438 fmt.dwMask = CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE | CFM_UNDERLINETYPE | CFM_STRIKEOUT |
439 CFM_COLOR | CFM_BACKCOLOR | CFM_SIZE | CFM_WEIGHT;
440 fmt.dwEffects = CFE_AUTOCOLOR | CFE_AUTOBACKCOLOR;
441 fmt.yHeight = 12*20; /* 12pt */
442 fmt.wWeight = FW_NORMAL;
443 fmt.bUnderlineType = CFU_UNDERLINE;
444 break;
445 case rtfBold:
446 fmt.dwMask = CFM_BOLD | CFM_WEIGHT;
447 fmt.dwEffects = info->rtfParam ? CFE_BOLD : 0;
448 fmt.wWeight = info->rtfParam ? FW_BOLD : FW_NORMAL;
449 break;
450 case rtfItalic:
451 fmt.dwMask = CFM_ITALIC;
452 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
453 break;
454 case rtfUnderline:
455 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
456 fmt.bUnderlineType = CFU_UNDERLINE;
457 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
458 break;
459 case rtfDotUnderline:
460 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
461 fmt.bUnderlineType = CFU_UNDERLINEDOTTED;
462 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
463 break;
464 case rtfDbUnderline:
465 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
466 fmt.bUnderlineType = CFU_UNDERLINEDOUBLE;
467 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
468 break;
469 case rtfWordUnderline:
470 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
471 fmt.bUnderlineType = CFU_UNDERLINEWORD;
472 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
473 break;
474 case rtfNoUnderline:
475 fmt.dwMask = CFM_UNDERLINE;
476 fmt.dwEffects = 0;
477 break;
478 case rtfStrikeThru:
479 fmt.dwMask = CFM_STRIKEOUT;
480 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
481 break;
482 case rtfSubScript:
483 case rtfSuperScript:
484 case rtfSubScrShrink:
485 case rtfSuperScrShrink:
486 case rtfNoSuperSub:
487 fmt.dwMask = CFM_SUBSCRIPT|CFM_SUPERSCRIPT;
488 if (info->rtfMinor == rtfSubScrShrink) fmt.dwEffects = CFE_SUBSCRIPT;
489 if (info->rtfMinor == rtfSuperScrShrink) fmt.dwEffects = CFE_SUPERSCRIPT;
490 if (info->rtfMinor == rtfNoSuperSub) fmt.dwEffects = 0;
491 break;
492 case rtfInvisible:
493 fmt.dwMask = CFM_HIDDEN;
494 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
495 break;
496 case rtfBackColor:
497 fmt.dwMask = CFM_BACKCOLOR;
498 fmt.dwEffects = 0;
499 if (info->rtfParam == 0)
500 fmt.dwEffects = CFE_AUTOBACKCOLOR;
501 else if (info->rtfParam != rtfNoParam)
503 RTFColor *c = RTFGetColor(info, info->rtfParam);
504 if (c && c->rtfCBlue >= 0)
505 fmt.crBackColor = (c->rtfCBlue<<16)|(c->rtfCGreen<<8)|(c->rtfCRed);
506 else
507 fmt.dwEffects = CFE_AUTOBACKCOLOR;
509 break;
510 case rtfForeColor:
511 fmt.dwMask = CFM_COLOR;
512 fmt.dwEffects = 0;
513 if (info->rtfParam == 0)
514 fmt.dwEffects = CFE_AUTOCOLOR;
515 else if (info->rtfParam != rtfNoParam)
517 RTFColor *c = RTFGetColor(info, info->rtfParam);
518 if (c && c->rtfCBlue >= 0)
519 fmt.crTextColor = (c->rtfCBlue<<16)|(c->rtfCGreen<<8)|(c->rtfCRed);
520 else {
521 fmt.dwEffects = CFE_AUTOCOLOR;
524 break;
525 case rtfFontNum:
526 if (info->rtfParam != rtfNoParam)
528 RTFFont *f = RTFGetFont(info, info->rtfParam);
529 if (f)
531 MultiByteToWideChar(CP_ACP, 0, f->rtfFName, -1, fmt.szFaceName, ARRAY_SIZE(fmt.szFaceName));
532 fmt.szFaceName[ARRAY_SIZE(fmt.szFaceName)-1] = '\0';
533 fmt.bCharSet = f->rtfFCharSet;
534 fmt.dwMask = CFM_FACE | CFM_CHARSET;
535 fmt.bPitchAndFamily = f->rtfFPitch | (f->rtfFFamily << 4);
538 break;
539 case rtfFontSize:
540 fmt.dwMask = CFM_SIZE;
541 if (info->rtfParam != rtfNoParam)
542 fmt.yHeight = info->rtfParam*10;
543 break;
545 if (fmt.dwMask) {
546 ME_Style *style2;
547 RTFFlushOutputBuffer(info);
548 /* FIXME too slow ? how come ? */
549 style2 = ME_ApplyStyle(info->editor, info->style, &fmt);
550 ME_ReleaseStyle(info->style);
551 info->style = style2;
552 info->styleChanged = TRUE;
556 /* FIXME this function doesn't get any information about context of the RTF tag, which is very bad,
557 the same tags mean different things in different contexts */
558 void ME_RTFParAttrHook(RTF_Info *info)
560 switch(info->rtfMinor)
562 case rtfParDef: /* restores default paragraph attributes */
563 if (!info->editor->bEmulateVersion10) /* v4.1 */
564 info->borderType = RTFBorderParaLeft;
565 else /* v1.0 - 3.0 */
566 info->borderType = RTFBorderParaTop;
567 info->fmt.dwMask = PFM_ALIGNMENT | PFM_BORDER | PFM_LINESPACING | PFM_TABSTOPS |
568 PFM_OFFSET | PFM_RIGHTINDENT | PFM_SPACEAFTER | PFM_SPACEBEFORE |
569 PFM_STARTINDENT | PFM_RTLPARA | PFM_NUMBERING | PFM_NUMBERINGSTART |
570 PFM_NUMBERINGSTYLE | PFM_NUMBERINGTAB;
571 /* TODO: shading */
572 info->fmt.wAlignment = PFA_LEFT;
573 info->fmt.cTabCount = 0;
574 info->fmt.dxOffset = info->fmt.dxStartIndent = info->fmt.dxRightIndent = 0;
575 info->fmt.wBorderWidth = info->fmt.wBorders = 0;
576 info->fmt.wBorderSpace = 0;
577 info->fmt.bLineSpacingRule = 0;
578 info->fmt.dySpaceBefore = info->fmt.dySpaceAfter = 0;
579 info->fmt.dyLineSpacing = 0;
580 info->fmt.wEffects &= ~PFE_RTLPARA;
581 info->fmt.wNumbering = 0;
582 info->fmt.wNumberingStart = 0;
583 info->fmt.wNumberingStyle = 0;
584 info->fmt.wNumberingTab = 0;
586 if (!info->editor->bEmulateVersion10) /* v4.1 */
588 if (info->tableDef && info->tableDef->row_start &&
589 info->tableDef->row_start->nFlags & MEPF_ROWEND)
591 ME_Cursor cursor;
592 ME_Paragraph *para;
593 /* We are just after a table row. */
594 RTFFlushOutputBuffer(info);
595 cursor = info->editor->pCursors[0];
596 para = &cursor.pPara->member.para;
597 if (para == para_next( info->tableDef->row_start )
598 && !cursor.nOffset && !cursor.pRun->member.run.nCharOfs)
600 /* Since the table row end, no text has been inserted, and the \intbl
601 * control word has not be used. We can confirm that we are not in a
602 * table anymore.
604 info->tableDef->row_start = NULL;
605 info->canInheritInTbl = FALSE;
609 else /* v1.0 - v3.0 */
611 info->fmt.dwMask |= PFM_TABLE;
612 info->fmt.wEffects &= ~PFE_TABLE;
614 break;
615 case rtfNestLevel:
616 if (!info->editor->bEmulateVersion10) /* v4.1 */
618 while (info->rtfParam > info->nestingLevel)
620 RTFTable *tableDef = heap_alloc_zero(sizeof(*tableDef));
621 tableDef->parent = info->tableDef;
622 info->tableDef = tableDef;
624 RTFFlushOutputBuffer(info);
625 if (tableDef->row_start && tableDef->row_start->nFlags & MEPF_ROWEND)
627 ME_Paragraph *para = para_next( tableDef->row_start );
628 tableDef->row_start = table_insert_row_start_at_para( info->editor, para );
630 else
632 ME_Cursor cursor;
633 WCHAR endl = '\r';
634 cursor = info->editor->pCursors[0];
635 if (cursor.nOffset || cursor.pRun->member.run.nCharOfs)
636 ME_InsertTextFromCursor(info->editor, 0, &endl, 1, info->style);
637 tableDef->row_start = table_insert_row_start( info->editor, info->editor->pCursors );
640 info->nestingLevel++;
642 info->canInheritInTbl = FALSE;
644 break;
645 case rtfInTable:
647 if (!info->editor->bEmulateVersion10) /* v4.1 */
649 if (info->nestingLevel < 1)
651 RTFTable *tableDef;
652 ME_Paragraph *para;
654 if (!info->tableDef)
655 info->tableDef = heap_alloc_zero(sizeof(*info->tableDef));
656 tableDef = info->tableDef;
657 RTFFlushOutputBuffer(info);
658 if (tableDef->row_start && tableDef->row_start->nFlags & MEPF_ROWEND)
659 para = para_next( tableDef->row_start );
660 else
661 para = &info->editor->pCursors[0].pPara->member.para;
663 tableDef->row_start = table_insert_row_start_at_para( info->editor, para );
665 info->nestingLevel = 1;
666 info->canInheritInTbl = TRUE;
668 return;
669 } else { /* v1.0 - v3.0 */
670 info->fmt.dwMask |= PFM_TABLE;
671 info->fmt.wEffects |= PFE_TABLE;
673 break;
675 case rtfFirstIndent:
676 case rtfLeftIndent:
677 if ((info->fmt.dwMask & (PFM_STARTINDENT | PFM_OFFSET)) != (PFM_STARTINDENT | PFM_OFFSET))
679 PARAFORMAT2 fmt;
680 fmt.cbSize = sizeof(fmt);
681 ME_GetSelectionParaFormat(info->editor, &fmt);
682 info->fmt.dwMask |= PFM_STARTINDENT | PFM_OFFSET;
683 info->fmt.dxStartIndent = fmt.dxStartIndent;
684 info->fmt.dxOffset = fmt.dxOffset;
686 if (info->rtfMinor == rtfFirstIndent)
688 info->fmt.dxStartIndent += info->fmt.dxOffset + info->rtfParam;
689 info->fmt.dxOffset = -info->rtfParam;
691 else
692 info->fmt.dxStartIndent = info->rtfParam - info->fmt.dxOffset;
693 break;
694 case rtfRightIndent:
695 info->fmt.dwMask |= PFM_RIGHTINDENT;
696 info->fmt.dxRightIndent = info->rtfParam;
697 break;
698 case rtfQuadLeft:
699 case rtfQuadJust:
700 info->fmt.dwMask |= PFM_ALIGNMENT;
701 info->fmt.wAlignment = PFA_LEFT;
702 break;
703 case rtfQuadRight:
704 info->fmt.dwMask |= PFM_ALIGNMENT;
705 info->fmt.wAlignment = PFA_RIGHT;
706 break;
707 case rtfQuadCenter:
708 info->fmt.dwMask |= PFM_ALIGNMENT;
709 info->fmt.wAlignment = PFA_CENTER;
710 break;
711 case rtfTabPos:
712 if (!(info->fmt.dwMask & PFM_TABSTOPS))
714 PARAFORMAT2 fmt;
715 fmt.cbSize = sizeof(fmt);
716 ME_GetSelectionParaFormat(info->editor, &fmt);
717 memcpy(info->fmt.rgxTabs, fmt.rgxTabs,
718 fmt.cTabCount * sizeof(fmt.rgxTabs[0]));
719 info->fmt.cTabCount = fmt.cTabCount;
720 info->fmt.dwMask |= PFM_TABSTOPS;
722 if (info->fmt.cTabCount < MAX_TAB_STOPS && info->rtfParam < 0x1000000)
723 info->fmt.rgxTabs[info->fmt.cTabCount++] = info->rtfParam;
724 break;
725 case rtfKeep:
726 info->fmt.dwMask |= PFM_KEEP;
727 info->fmt.wEffects |= PFE_KEEP;
728 break;
729 case rtfNoWidowControl:
730 info->fmt.dwMask |= PFM_NOWIDOWCONTROL;
731 info->fmt.wEffects |= PFE_NOWIDOWCONTROL;
732 break;
733 case rtfKeepNext:
734 info->fmt.dwMask |= PFM_KEEPNEXT;
735 info->fmt.wEffects |= PFE_KEEPNEXT;
736 break;
737 case rtfSpaceAfter:
738 info->fmt.dwMask |= PFM_SPACEAFTER;
739 info->fmt.dySpaceAfter = info->rtfParam;
740 break;
741 case rtfSpaceBefore:
742 info->fmt.dwMask |= PFM_SPACEBEFORE;
743 info->fmt.dySpaceBefore = info->rtfParam;
744 break;
745 case rtfSpaceBetween:
746 info->fmt.dwMask |= PFM_LINESPACING;
747 if ((int)info->rtfParam > 0)
749 info->fmt.dyLineSpacing = info->rtfParam;
750 info->fmt.bLineSpacingRule = 3;
752 else
754 info->fmt.dyLineSpacing = info->rtfParam;
755 info->fmt.bLineSpacingRule = 4;
757 break;
758 case rtfSpaceMultiply:
759 info->fmt.dwMask |= PFM_LINESPACING;
760 info->fmt.dyLineSpacing = info->rtfParam * 20;
761 info->fmt.bLineSpacingRule = 5;
762 break;
763 case rtfParBullet:
764 info->fmt.dwMask |= PFM_NUMBERING;
765 info->fmt.wNumbering = PFN_BULLET;
766 break;
767 case rtfParSimple:
768 info->fmt.dwMask |= PFM_NUMBERING;
769 info->fmt.wNumbering = 2; /* FIXME: MSDN says it's not used ?? */
770 break;
771 case rtfBorderLeft:
772 info->borderType = RTFBorderParaLeft;
773 info->fmt.wBorders |= 1;
774 info->fmt.dwMask |= PFM_BORDER;
775 break;
776 case rtfBorderRight:
777 info->borderType = RTFBorderParaRight;
778 info->fmt.wBorders |= 2;
779 info->fmt.dwMask |= PFM_BORDER;
780 break;
781 case rtfBorderTop:
782 info->borderType = RTFBorderParaTop;
783 info->fmt.wBorders |= 4;
784 info->fmt.dwMask |= PFM_BORDER;
785 break;
786 case rtfBorderBottom:
787 info->borderType = RTFBorderParaBottom;
788 info->fmt.wBorders |= 8;
789 info->fmt.dwMask |= PFM_BORDER;
790 break;
791 case rtfBorderSingle:
792 info->fmt.wBorders &= ~0x700;
793 info->fmt.wBorders |= 1 << 8;
794 info->fmt.dwMask |= PFM_BORDER;
795 break;
796 case rtfBorderThick:
797 info->fmt.wBorders &= ~0x700;
798 info->fmt.wBorders |= 2 << 8;
799 info->fmt.dwMask |= PFM_BORDER;
800 break;
801 case rtfBorderShadow:
802 info->fmt.wBorders &= ~0x700;
803 info->fmt.wBorders |= 10 << 8;
804 info->fmt.dwMask |= PFM_BORDER;
805 break;
806 case rtfBorderDouble:
807 info->fmt.wBorders &= ~0x700;
808 info->fmt.wBorders |= 7 << 8;
809 info->fmt.dwMask |= PFM_BORDER;
810 break;
811 case rtfBorderDot:
812 info->fmt.wBorders &= ~0x700;
813 info->fmt.wBorders |= 11 << 8;
814 info->fmt.dwMask |= PFM_BORDER;
815 break;
816 case rtfBorderWidth:
818 int borderSide = info->borderType & RTFBorderSideMask;
819 RTFTable *tableDef = info->tableDef;
820 if ((info->borderType & RTFBorderTypeMask) == RTFBorderTypeCell)
822 RTFBorder *border;
823 if (!tableDef || tableDef->numCellsDefined >= MAX_TABLE_CELLS)
824 break;
825 border = &tableDef->cells[tableDef->numCellsDefined].border[borderSide];
826 border->width = info->rtfParam;
827 break;
829 info->fmt.wBorderWidth = info->rtfParam;
830 info->fmt.dwMask |= PFM_BORDER;
831 break;
833 case rtfBorderSpace:
834 info->fmt.wBorderSpace = info->rtfParam;
835 info->fmt.dwMask |= PFM_BORDER;
836 break;
837 case rtfBorderColor:
839 RTFTable *tableDef = info->tableDef;
840 int borderSide = info->borderType & RTFBorderSideMask;
841 int borderType = info->borderType & RTFBorderTypeMask;
842 switch(borderType)
844 case RTFBorderTypePara:
845 if (!info->editor->bEmulateVersion10) /* v4.1 */
846 break;
847 /* v1.0 - 3.0 treat paragraph and row borders the same. */
848 case RTFBorderTypeRow:
849 if (tableDef) {
850 tableDef->border[borderSide].color = info->rtfParam;
852 break;
853 case RTFBorderTypeCell:
854 if (tableDef && tableDef->numCellsDefined < MAX_TABLE_CELLS) {
855 tableDef->cells[tableDef->numCellsDefined].border[borderSide].color = info->rtfParam;
857 break;
859 break;
861 case rtfRTLPar:
862 info->fmt.dwMask |= PFM_RTLPARA;
863 info->fmt.wEffects |= PFE_RTLPARA;
864 break;
865 case rtfLTRPar:
866 info->fmt.dwMask |= PFM_RTLPARA;
867 info->fmt.wEffects &= ~PFE_RTLPARA;
868 break;
872 void ME_RTFTblAttrHook(RTF_Info *info)
874 switch (info->rtfMinor)
876 case rtfRowDef:
878 if (!info->editor->bEmulateVersion10) /* v4.1 */
879 info->borderType = 0; /* Not sure */
880 else /* v1.0 - 3.0 */
881 info->borderType = RTFBorderRowTop;
882 if (!info->tableDef) {
883 info->tableDef = ME_MakeTableDef(info->editor);
884 } else {
885 ME_InitTableDef(info->editor, info->tableDef);
887 break;
889 case rtfCellPos:
891 int cellNum;
892 if (!info->tableDef)
894 info->tableDef = ME_MakeTableDef(info->editor);
896 cellNum = info->tableDef->numCellsDefined;
897 if (cellNum >= MAX_TABLE_CELLS)
898 break;
899 info->tableDef->cells[cellNum].rightBoundary = info->rtfParam;
900 if (cellNum < MAX_TAB_STOPS) {
901 /* Tab stops were used to store cell positions before v4.1 but v4.1
902 * still seems to set the tabstops without using them. */
903 ME_DisplayItem *para = info->editor->pCursors[0].pPara;
904 PARAFORMAT2 *pFmt = &para->member.para.fmt;
905 pFmt->rgxTabs[cellNum] &= ~0x00FFFFFF;
906 pFmt->rgxTabs[cellNum] |= 0x00FFFFFF & info->rtfParam;
908 info->tableDef->numCellsDefined++;
909 break;
911 case rtfRowBordTop:
912 info->borderType = RTFBorderRowTop;
913 break;
914 case rtfRowBordLeft:
915 info->borderType = RTFBorderRowLeft;
916 break;
917 case rtfRowBordBottom:
918 info->borderType = RTFBorderRowBottom;
919 break;
920 case rtfRowBordRight:
921 info->borderType = RTFBorderRowRight;
922 break;
923 case rtfCellBordTop:
924 info->borderType = RTFBorderCellTop;
925 break;
926 case rtfCellBordLeft:
927 info->borderType = RTFBorderCellLeft;
928 break;
929 case rtfCellBordBottom:
930 info->borderType = RTFBorderCellBottom;
931 break;
932 case rtfCellBordRight:
933 info->borderType = RTFBorderCellRight;
934 break;
935 case rtfRowGapH:
936 if (info->tableDef)
937 info->tableDef->gapH = info->rtfParam;
938 break;
939 case rtfRowLeftEdge:
940 if (info->tableDef)
941 info->tableDef->leftEdge = info->rtfParam;
942 break;
946 void ME_RTFSpecialCharHook(RTF_Info *info)
948 RTFTable *tableDef = info->tableDef;
949 switch (info->rtfMinor)
951 case rtfNestCell:
952 if (info->editor->bEmulateVersion10) /* v1.0 - v3.0 */
953 break;
954 /* else fall through since v4.1 treats rtfNestCell and rtfCell the same */
955 case rtfCell:
956 if (!tableDef)
957 break;
958 RTFFlushOutputBuffer(info);
959 if (!info->editor->bEmulateVersion10) /* v4.1 */
961 if (tableDef->row_start)
963 if (!info->nestingLevel && tableDef->row_start->nFlags & MEPF_ROWEND)
965 ME_Paragraph *para = para_next( tableDef->row_start );
966 tableDef->row_start = table_insert_row_start_at_para( info->editor, para );
967 info->nestingLevel = 1;
969 table_insert_cell( info->editor, info->editor->pCursors );
972 else /* v1.0 - v3.0 */
974 ME_Paragraph *para = &info->editor->pCursors[0].pPara->member.para;
976 if (para_in_table( para ) && tableDef->numCellsInserted < tableDef->numCellsDefined)
978 WCHAR tab = '\t';
979 ME_InsertTextFromCursor(info->editor, 0, &tab, 1, info->style);
980 tableDef->numCellsInserted++;
983 break;
984 case rtfNestRow:
985 if (info->editor->bEmulateVersion10) /* v1.0 - v3.0 */
986 break;
987 /* else fall through since v4.1 treats rtfNestRow and rtfRow the same */
988 case rtfRow:
990 ME_DisplayItem *run;
991 ME_Paragraph *para;
992 ME_Cell *cell;
993 int i;
995 if (!tableDef)
996 break;
997 RTFFlushOutputBuffer(info);
998 if (!info->editor->bEmulateVersion10) /* v4.1 */
1000 if (!tableDef->row_start) break;
1001 if (!info->nestingLevel && tableDef->row_start->nFlags & MEPF_ROWEND)
1003 para = para_next( tableDef->row_start );
1004 tableDef->row_start = table_insert_row_start_at_para( info->editor, para );
1005 info->nestingLevel++;
1007 para = tableDef->row_start;
1008 cell = table_row_first_cell( para );
1009 assert( cell && !cell_prev( cell ) );
1010 if (tableDef->numCellsDefined < 1)
1012 /* 2000 twips appears to be the cell size that native richedit uses
1013 * when no cell sizes are specified. */
1014 const int default_size = 2000;
1015 int right_boundary = default_size;
1016 cell->nRightBoundary = right_boundary;
1017 while (cell_next( cell ))
1019 cell = cell_next( cell );
1020 right_boundary += default_size;
1021 cell->nRightBoundary = right_boundary;
1023 para = table_insert_cell( info->editor, info->editor->pCursors );
1024 cell = para_cell( para );
1025 cell->nRightBoundary = right_boundary;
1027 else
1029 for (i = 0; i < tableDef->numCellsDefined; i++)
1031 RTFCell *cellDef = &tableDef->cells[i];
1032 cell->nRightBoundary = cellDef->rightBoundary;
1033 ME_ApplyBorderProperties( info, &cell->border, cellDef->border );
1034 cell = cell_next( cell );
1035 if (!cell)
1037 para = table_insert_cell( info->editor, info->editor->pCursors );
1038 cell = para_cell( para );
1041 /* Cell for table row delimiter is empty */
1042 cell->nRightBoundary = tableDef->cells[i - 1].rightBoundary;
1045 run = ME_FindItemFwd( cell_get_di( cell) , diRun );
1046 if (info->editor->pCursors[0].pRun != run ||
1047 info->editor->pCursors[0].nOffset)
1049 int nOfs, nChars;
1050 /* Delete inserted cells that aren't defined. */
1051 info->editor->pCursors[1].pRun = run;
1052 info->editor->pCursors[1].pPara = ME_GetParagraph(run);
1053 info->editor->pCursors[1].nOffset = 0;
1054 nOfs = ME_GetCursorOfs(&info->editor->pCursors[1]);
1055 nChars = ME_GetCursorOfs(&info->editor->pCursors[0]) - nOfs;
1056 ME_InternalDeleteText(info->editor, &info->editor->pCursors[1],
1057 nChars, TRUE);
1060 para = table_insert_row_end( info->editor, info->editor->pCursors );
1061 para->fmt.dxOffset = abs(info->tableDef->gapH);
1062 para->fmt.dxStartIndent = info->tableDef->leftEdge;
1063 ME_ApplyBorderProperties( info, &para->border, tableDef->border );
1064 info->nestingLevel--;
1065 if (!info->nestingLevel)
1067 if (info->canInheritInTbl) tableDef->row_start = para;
1068 else
1070 while (info->tableDef)
1072 tableDef = info->tableDef;
1073 info->tableDef = tableDef->parent;
1074 heap_free(tableDef);
1078 else
1080 info->tableDef = tableDef->parent;
1081 heap_free(tableDef);
1084 else /* v1.0 - v3.0 */
1086 WCHAR endl = '\r';
1088 para = &info->editor->pCursors[0].pPara->member.para;
1089 para->fmt.dxOffset = info->tableDef->gapH;
1090 para->fmt.dxStartIndent = info->tableDef->leftEdge;
1092 ME_ApplyBorderProperties( info, &para->border, tableDef->border );
1093 while (tableDef->numCellsInserted < tableDef->numCellsDefined)
1095 WCHAR tab = '\t';
1096 ME_InsertTextFromCursor(info->editor, 0, &tab, 1, info->style);
1097 tableDef->numCellsInserted++;
1099 para->fmt.cTabCount = min(tableDef->numCellsDefined, MAX_TAB_STOPS);
1100 if (!tableDef->numCellsDefined) para->fmt.wEffects &= ~PFE_TABLE;
1101 ME_InsertTextFromCursor(info->editor, 0, &endl, 1, info->style);
1102 tableDef->numCellsInserted = 0;
1104 break;
1106 case rtfTab:
1107 case rtfPar:
1108 if (info->editor->bEmulateVersion10) /* v1.0 - 3.0 */
1110 ME_Paragraph *para;
1112 RTFFlushOutputBuffer(info);
1113 para = &info->editor->pCursors[0].pPara->member.para;
1114 if (para_in_table( para ))
1116 /* rtfPar is treated like a space within a table. */
1117 info->rtfClass = rtfText;
1118 info->rtfMajor = ' ';
1120 else if (info->rtfMinor == rtfPar && tableDef)
1121 tableDef->numCellsInserted = 0;
1123 break;
1127 static HRESULT insert_static_object(ME_TextEditor *editor, HENHMETAFILE hemf, HBITMAP hbmp,
1128 const SIZEL* sz)
1130 LPOLEOBJECT lpObject = NULL;
1131 LPSTORAGE lpStorage = NULL;
1132 LPOLECLIENTSITE lpClientSite = NULL;
1133 LPDATAOBJECT lpDataObject = NULL;
1134 LPOLECACHE lpOleCache = NULL;
1135 LPRICHEDITOLE lpReOle = NULL;
1136 STGMEDIUM stgm;
1137 FORMATETC fm;
1138 CLSID clsid;
1139 HRESULT hr = E_FAIL;
1140 DWORD conn;
1142 if (hemf)
1144 stgm.tymed = TYMED_ENHMF;
1145 stgm.u.hEnhMetaFile = hemf;
1146 fm.cfFormat = CF_ENHMETAFILE;
1148 else if (hbmp)
1150 stgm.tymed = TYMED_GDI;
1151 stgm.u.hBitmap = hbmp;
1152 fm.cfFormat = CF_BITMAP;
1154 else return E_FAIL;
1156 stgm.pUnkForRelease = NULL;
1158 fm.ptd = NULL;
1159 fm.dwAspect = DVASPECT_CONTENT;
1160 fm.lindex = -1;
1161 fm.tymed = stgm.tymed;
1163 if (!editor->reOle)
1165 if (!CreateIRichEditOle(NULL, editor, (LPVOID *)&editor->reOle))
1166 return hr;
1169 if (OleCreateDefaultHandler(&CLSID_NULL, NULL, &IID_IOleObject, (void**)&lpObject) == S_OK &&
1170 IUnknown_QueryInterface(editor->reOle, &IID_IRichEditOle, (void**)&lpReOle) == S_OK &&
1171 IRichEditOle_GetClientSite(lpReOle, &lpClientSite) == S_OK &&
1172 IOleObject_SetClientSite(lpObject, lpClientSite) == S_OK &&
1173 IOleObject_GetUserClassID(lpObject, &clsid) == S_OK &&
1174 IOleObject_QueryInterface(lpObject, &IID_IOleCache, (void**)&lpOleCache) == S_OK &&
1175 IOleCache_Cache(lpOleCache, &fm, 0, &conn) == S_OK &&
1176 IOleObject_QueryInterface(lpObject, &IID_IDataObject, (void**)&lpDataObject) == S_OK &&
1177 IDataObject_SetData(lpDataObject, &fm, &stgm, TRUE) == S_OK)
1179 REOBJECT reobject;
1181 reobject.cbStruct = sizeof(reobject);
1182 reobject.cp = REO_CP_SELECTION;
1183 reobject.clsid = clsid;
1184 reobject.poleobj = lpObject;
1185 reobject.pstg = lpStorage;
1186 reobject.polesite = lpClientSite;
1187 /* convert from twips to .01 mm */
1188 reobject.sizel.cx = MulDiv(sz->cx, 254, 144);
1189 reobject.sizel.cy = MulDiv(sz->cy, 254, 144);
1190 reobject.dvaspect = DVASPECT_CONTENT;
1191 reobject.dwFlags = 0; /* FIXME */
1192 reobject.dwUser = 0;
1194 ME_InsertOLEFromCursor(editor, &reobject, 0);
1195 hr = S_OK;
1198 if (lpObject) IOleObject_Release(lpObject);
1199 if (lpClientSite) IOleClientSite_Release(lpClientSite);
1200 if (lpStorage) IStorage_Release(lpStorage);
1201 if (lpDataObject) IDataObject_Release(lpDataObject);
1202 if (lpOleCache) IOleCache_Release(lpOleCache);
1203 if (lpReOle) IRichEditOle_Release(lpReOle);
1205 return hr;
1208 static void ME_RTFReadShpPictGroup( RTF_Info *info )
1210 int level = 1;
1212 for (;;)
1214 RTFGetToken (info);
1216 if (info->rtfClass == rtfEOF) return;
1217 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1219 if (--level == 0) break;
1221 else if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1223 level++;
1225 else
1227 RTFRouteToken( info );
1228 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1229 level--;
1233 RTFRouteToken( info ); /* feed "}" back to router */
1234 return;
1237 static DWORD read_hex_data( RTF_Info *info, BYTE **out )
1239 DWORD read = 0, size = 1024;
1240 BYTE *buf, val;
1241 BOOL flip;
1243 *out = NULL;
1245 if (info->rtfClass != rtfText)
1247 ERR("Called with incorrect token\n");
1248 return 0;
1251 buf = HeapAlloc( GetProcessHeap(), 0, size );
1252 if (!buf) return 0;
1254 val = info->rtfMajor;
1255 for (flip = TRUE;; flip = !flip)
1257 RTFGetToken( info );
1258 if (info->rtfClass == rtfEOF)
1260 HeapFree( GetProcessHeap(), 0, buf );
1261 return 0;
1263 if (info->rtfClass != rtfText) break;
1264 if (flip)
1266 if (read >= size)
1268 size *= 2;
1269 buf = HeapReAlloc( GetProcessHeap(), 0, buf, size );
1270 if (!buf) return 0;
1272 buf[read++] = RTFCharToHex(val) * 16 + RTFCharToHex(info->rtfMajor);
1274 else
1275 val = info->rtfMajor;
1277 if (flip) FIXME("wrong hex string\n");
1279 *out = buf;
1280 return read;
1283 static void ME_RTFReadPictGroup(RTF_Info *info)
1285 SIZEL sz;
1286 BYTE *buffer = NULL;
1287 DWORD size = 0;
1288 METAFILEPICT mfp;
1289 HENHMETAFILE hemf;
1290 HBITMAP hbmp;
1291 enum gfxkind {gfx_unknown = 0, gfx_enhmetafile, gfx_metafile, gfx_dib} gfx = gfx_unknown;
1292 int level = 1;
1294 mfp.mm = MM_TEXT;
1295 sz.cx = sz.cy = 0;
1297 for (;;)
1299 RTFGetToken( info );
1301 if (info->rtfClass == rtfText)
1303 if (level == 1)
1305 if (!buffer)
1306 size = read_hex_data( info, &buffer );
1308 else
1310 RTFSkipGroup( info );
1312 } /* We potentially have a new token so fall through. */
1314 if (info->rtfClass == rtfEOF) return;
1316 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1318 if (--level == 0) break;
1319 continue;
1321 if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1323 level++;
1324 continue;
1326 if (!RTFCheckCM( info, rtfControl, rtfPictAttr ))
1328 RTFRouteToken( info );
1329 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1330 level--;
1331 continue;
1334 if (RTFCheckMM( info, rtfPictAttr, rtfWinMetafile ))
1336 mfp.mm = info->rtfParam;
1337 gfx = gfx_metafile;
1339 else if (RTFCheckMM( info, rtfPictAttr, rtfDevIndBitmap ))
1341 if (info->rtfParam != 0) FIXME("dibitmap should be 0 (%d)\n", info->rtfParam);
1342 gfx = gfx_dib;
1344 else if (RTFCheckMM( info, rtfPictAttr, rtfEmfBlip ))
1345 gfx = gfx_enhmetafile;
1346 else if (RTFCheckMM( info, rtfPictAttr, rtfPicWid ))
1347 mfp.xExt = info->rtfParam;
1348 else if (RTFCheckMM( info, rtfPictAttr, rtfPicHt ))
1349 mfp.yExt = info->rtfParam;
1350 else if (RTFCheckMM( info, rtfPictAttr, rtfPicGoalWid ))
1351 sz.cx = info->rtfParam;
1352 else if (RTFCheckMM( info, rtfPictAttr, rtfPicGoalHt ))
1353 sz.cy = info->rtfParam;
1354 else
1355 FIXME("Non supported attribute: %d %d %d\n", info->rtfClass, info->rtfMajor, info->rtfMinor);
1358 if (buffer)
1360 switch (gfx)
1362 case gfx_enhmetafile:
1363 if ((hemf = SetEnhMetaFileBits( size, buffer )))
1364 insert_static_object( info->editor, hemf, NULL, &sz );
1365 break;
1366 case gfx_metafile:
1367 if ((hemf = SetWinMetaFileBits( size, buffer, NULL, &mfp )))
1368 insert_static_object( info->editor, hemf, NULL, &sz );
1369 break;
1370 case gfx_dib:
1372 BITMAPINFO *bi = (BITMAPINFO*)buffer;
1373 HDC hdc = GetDC(0);
1374 unsigned nc = bi->bmiHeader.biClrUsed;
1376 /* not quite right, especially for bitfields type of compression */
1377 if (!nc && bi->bmiHeader.biBitCount <= 8)
1378 nc = 1 << bi->bmiHeader.biBitCount;
1379 if ((hbmp = CreateDIBitmap( hdc, &bi->bmiHeader,
1380 CBM_INIT, (char*)(bi + 1) + nc * sizeof(RGBQUAD),
1381 bi, DIB_RGB_COLORS)) )
1382 insert_static_object( info->editor, NULL, hbmp, &sz );
1383 ReleaseDC( 0, hdc );
1384 break;
1386 default:
1387 break;
1390 HeapFree( GetProcessHeap(), 0, buffer );
1391 RTFRouteToken( info ); /* feed "}" back to router */
1392 return;
1395 /* for now, lookup the \result part and use it, whatever the object */
1396 static void ME_RTFReadObjectGroup(RTF_Info *info)
1398 for (;;)
1400 RTFGetToken (info);
1401 if (info->rtfClass == rtfEOF)
1402 return;
1403 if (RTFCheckCM(info, rtfGroup, rtfEndGroup))
1404 break;
1405 if (RTFCheckCM(info, rtfGroup, rtfBeginGroup))
1407 RTFGetToken (info);
1408 if (info->rtfClass == rtfEOF)
1409 return;
1410 if (RTFCheckCMM(info, rtfControl, rtfDestination, rtfObjResult))
1412 int level = 1;
1414 while (RTFGetToken (info) != rtfEOF)
1416 if (info->rtfClass == rtfGroup)
1418 if (info->rtfMajor == rtfBeginGroup) level++;
1419 else if (info->rtfMajor == rtfEndGroup && --level < 0) break;
1421 RTFRouteToken(info);
1424 else RTFSkipGroup(info);
1425 continue;
1427 if (!RTFCheckCM (info, rtfControl, rtfObjAttr))
1429 FIXME("Non supported attribute: %d %d %d\n", info->rtfClass, info->rtfMajor, info->rtfMinor);
1430 return;
1433 RTFRouteToken(info); /* feed "}" back to router */
1436 static void ME_RTFReadParnumGroup( RTF_Info *info )
1438 int level = 1, type = -1;
1439 WORD indent = 0, start = 1;
1440 WCHAR txt_before = 0, txt_after = 0;
1442 for (;;)
1444 RTFGetToken( info );
1446 if (RTFCheckCMM( info, rtfControl, rtfDestination, rtfParNumTextBefore ) ||
1447 RTFCheckCMM( info, rtfControl, rtfDestination, rtfParNumTextAfter ))
1449 int loc = info->rtfMinor;
1451 RTFGetToken( info );
1452 if (info->rtfClass == rtfText)
1454 if (loc == rtfParNumTextBefore)
1455 txt_before = info->rtfMajor;
1456 else
1457 txt_after = info->rtfMajor;
1458 continue;
1460 /* falling through to catch EOFs and group level changes */
1463 if (info->rtfClass == rtfEOF)
1464 return;
1466 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1468 if (--level == 0) break;
1469 continue;
1472 if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1474 level++;
1475 continue;
1478 /* Ignore non para-attr */
1479 if (!RTFCheckCM( info, rtfControl, rtfParAttr ))
1480 continue;
1482 switch (info->rtfMinor)
1484 case rtfParLevel: /* Para level is ignored */
1485 case rtfParSimple:
1486 break;
1487 case rtfParBullet:
1488 type = PFN_BULLET;
1489 break;
1491 case rtfParNumDecimal:
1492 type = PFN_ARABIC;
1493 break;
1494 case rtfParNumULetter:
1495 type = PFN_UCLETTER;
1496 break;
1497 case rtfParNumURoman:
1498 type = PFN_UCROMAN;
1499 break;
1500 case rtfParNumLLetter:
1501 type = PFN_LCLETTER;
1502 break;
1503 case rtfParNumLRoman:
1504 type = PFN_LCROMAN;
1505 break;
1507 case rtfParNumIndent:
1508 indent = info->rtfParam;
1509 break;
1510 case rtfParNumStartAt:
1511 start = info->rtfParam;
1512 break;
1516 if (type != -1)
1518 info->fmt.dwMask |= (PFM_NUMBERING | PFM_NUMBERINGSTART | PFM_NUMBERINGSTYLE | PFM_NUMBERINGTAB);
1519 info->fmt.wNumbering = type;
1520 info->fmt.wNumberingStart = start;
1521 info->fmt.wNumberingStyle = PFNS_PAREN;
1522 if (type != PFN_BULLET)
1524 if (txt_before == 0 && txt_after == 0)
1525 info->fmt.wNumberingStyle = PFNS_PLAIN;
1526 else if (txt_after == '.')
1527 info->fmt.wNumberingStyle = PFNS_PERIOD;
1528 else if (txt_before == '(' && txt_after == ')')
1529 info->fmt.wNumberingStyle = PFNS_PARENS;
1531 info->fmt.wNumberingTab = indent;
1534 TRACE("type %d indent %d start %d txt before %04x txt after %04x\n",
1535 type, indent, start, txt_before, txt_after);
1537 RTFRouteToken( info ); /* feed "}" back to router */
1540 static void ME_RTFReadHook(RTF_Info *info)
1542 switch(info->rtfClass)
1544 case rtfGroup:
1545 switch(info->rtfMajor)
1547 case rtfBeginGroup:
1548 if (info->stackTop < maxStack) {
1549 info->stack[info->stackTop].style = info->style;
1550 ME_AddRefStyle(info->style);
1551 info->stack[info->stackTop].codePage = info->codePage;
1552 info->stack[info->stackTop].unicodeLength = info->unicodeLength;
1554 info->stackTop++;
1555 info->styleChanged = FALSE;
1556 break;
1557 case rtfEndGroup:
1559 RTFFlushOutputBuffer(info);
1560 info->stackTop--;
1561 if (info->stackTop <= 0)
1562 info->rtfClass = rtfEOF;
1563 if (info->stackTop < 0)
1564 return;
1566 ME_ReleaseStyle(info->style);
1567 info->style = info->stack[info->stackTop].style;
1568 info->codePage = info->stack[info->stackTop].codePage;
1569 info->unicodeLength = info->stack[info->stackTop].unicodeLength;
1570 break;
1573 break;
1577 void
1578 ME_StreamInFill(ME_InStream *stream)
1580 stream->editstream->dwError = stream->editstream->pfnCallback(stream->editstream->dwCookie,
1581 (BYTE *)stream->buffer,
1582 sizeof(stream->buffer),
1583 (LONG *)&stream->dwSize);
1584 stream->dwUsed = 0;
1587 static LRESULT ME_StreamIn(ME_TextEditor *editor, DWORD format, EDITSTREAM *stream, BOOL stripLastCR)
1589 RTF_Info parser;
1590 ME_Style *style;
1591 int from, to, nUndoMode;
1592 int nEventMask = editor->nEventMask;
1593 ME_InStream inStream;
1594 BOOL invalidRTF = FALSE;
1595 ME_Cursor *selStart, *selEnd;
1596 LRESULT num_read = 0; /* bytes read for SF_TEXT, non-control chars inserted for SF_RTF */
1598 TRACE("stream==%p editor==%p format==0x%X\n", stream, editor, format);
1599 editor->nEventMask = 0;
1601 ME_GetSelectionOfs(editor, &from, &to);
1602 if (format & SFF_SELECTION && editor->mode & TM_RICHTEXT)
1604 ME_GetSelection(editor, &selStart, &selEnd);
1605 style = ME_GetSelectionInsertStyle(editor);
1607 ME_InternalDeleteText(editor, selStart, to - from, FALSE);
1609 /* Don't insert text at the end of the table row */
1610 if (!editor->bEmulateVersion10) { /* v4.1 */
1611 ME_DisplayItem *para = editor->pCursors->pPara;
1612 if (para->member.para.nFlags & MEPF_ROWEND)
1614 para = para->member.para.next_para;
1615 editor->pCursors[0].pPara = para;
1616 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
1617 editor->pCursors[0].nOffset = 0;
1619 if (para->member.para.nFlags & MEPF_ROWSTART)
1621 para = para->member.para.next_para;
1622 editor->pCursors[0].pPara = para;
1623 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
1624 editor->pCursors[0].nOffset = 0;
1626 editor->pCursors[1] = editor->pCursors[0];
1627 } else { /* v1.0 - 3.0 */
1628 if (editor->pCursors[0].pRun->member.run.nFlags & MERF_ENDPARA &&
1629 ME_IsInTable(editor->pCursors[0].pRun))
1630 return 0;
1632 } else {
1633 style = editor->pBuffer->pDefaultStyle;
1634 ME_AddRefStyle(style);
1635 set_selection_cursors(editor, 0, 0);
1636 ME_InternalDeleteText(editor, &editor->pCursors[1],
1637 ME_GetTextLength(editor), FALSE);
1638 from = to = 0;
1639 ME_ClearTempStyle(editor);
1640 ME_SetDefaultParaFormat(editor, &editor->pCursors[0].pPara->member.para.fmt);
1644 /* Back up undo mode to a local variable */
1645 nUndoMode = editor->nUndoMode;
1647 /* Only create an undo if SFF_SELECTION is set */
1648 if (!(format & SFF_SELECTION))
1649 editor->nUndoMode = umIgnore;
1651 inStream.editstream = stream;
1652 inStream.editstream->dwError = 0;
1653 inStream.dwSize = 0;
1654 inStream.dwUsed = 0;
1656 if (format & SF_RTF)
1658 /* Check if it's really RTF, and if it is not, use plain text */
1659 ME_StreamInFill(&inStream);
1660 if (!inStream.editstream->dwError)
1662 if ((!editor->bEmulateVersion10 && strncmp(inStream.buffer, "{\\rtf", 5) && strncmp(inStream.buffer, "{\\urtf", 6))
1663 || (editor->bEmulateVersion10 && *inStream.buffer != '{'))
1665 invalidRTF = TRUE;
1666 inStream.editstream->dwError = -16;
1671 if (!invalidRTF && !inStream.editstream->dwError)
1673 ME_Cursor start;
1674 from = ME_GetCursorOfs(&editor->pCursors[0]);
1675 if (format & SF_RTF) {
1677 /* setup the RTF parser */
1678 memset(&parser, 0, sizeof parser);
1679 RTFSetEditStream(&parser, &inStream);
1680 parser.rtfFormat = format&(SF_TEXT|SF_RTF);
1681 parser.editor = editor;
1682 parser.style = style;
1683 WriterInit(&parser);
1684 RTFInit(&parser);
1685 RTFSetReadHook(&parser, ME_RTFReadHook);
1686 RTFSetDestinationCallback(&parser, rtfShpPict, ME_RTFReadShpPictGroup);
1687 RTFSetDestinationCallback(&parser, rtfPict, ME_RTFReadPictGroup);
1688 RTFSetDestinationCallback(&parser, rtfObject, ME_RTFReadObjectGroup);
1689 RTFSetDestinationCallback(&parser, rtfParNumbering, ME_RTFReadParnumGroup);
1690 if (!parser.editor->bEmulateVersion10) /* v4.1 */
1692 RTFSetDestinationCallback(&parser, rtfNoNestTables, RTFSkipGroup);
1693 RTFSetDestinationCallback(&parser, rtfNestTableProps, RTFReadGroup);
1695 BeginFile(&parser);
1697 /* do the parsing */
1698 RTFRead(&parser);
1699 RTFFlushOutputBuffer(&parser);
1700 if (!editor->bEmulateVersion10) /* v4.1 */
1702 if (parser.tableDef && parser.tableDef->row_start &&
1703 (parser.nestingLevel > 0 || parser.canInheritInTbl))
1705 /* Delete any incomplete table row at the end of the rich text. */
1706 int nOfs, nChars;
1707 ME_Paragraph *para;
1709 parser.rtfMinor = rtfRow;
1710 /* Complete the table row before deleting it.
1711 * By doing it this way we will have the current paragraph format set
1712 * properly to reflect that is not in the complete table, and undo items
1713 * will be added for this change to the current paragraph format. */
1714 if (parser.nestingLevel > 0)
1716 while (parser.nestingLevel > 1)
1717 ME_RTFSpecialCharHook(&parser); /* Decrements nestingLevel */
1718 para = parser.tableDef->row_start;
1719 ME_RTFSpecialCharHook(&parser);
1721 else
1723 para = parser.tableDef->row_start;
1724 ME_RTFSpecialCharHook(&parser);
1725 assert( para->nFlags & MEPF_ROWEND );
1726 para = para_next( para );
1729 editor->pCursors[1].pPara = para_get_di( para );
1730 editor->pCursors[1].pRun = run_get_di( para_first_run( para ) );
1731 editor->pCursors[1].nOffset = 0;
1732 nOfs = ME_GetCursorOfs(&editor->pCursors[1]);
1733 nChars = ME_GetCursorOfs(&editor->pCursors[0]) - nOfs;
1734 ME_InternalDeleteText(editor, &editor->pCursors[1], nChars, TRUE);
1735 if (parser.tableDef) parser.tableDef->row_start = NULL;
1738 ME_CheckTablesForCorruption(editor);
1739 RTFDestroy(&parser);
1741 if (parser.stackTop > 0)
1743 while (--parser.stackTop >= 0)
1745 ME_ReleaseStyle(parser.style);
1746 parser.style = parser.stack[parser.stackTop].style;
1748 if (!inStream.editstream->dwError)
1749 inStream.editstream->dwError = HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
1752 /* Remove last line break, as mandated by tests. This is not affected by
1753 CR/LF counters, since RTF streaming presents only \para tokens, which
1754 are converted according to the standard rules: \r for 2.0, \r\n for 1.0
1756 if (stripLastCR && !(format & SFF_SELECTION)) {
1757 int newto;
1758 ME_GetSelection(editor, &selStart, &selEnd);
1759 newto = ME_GetCursorOfs(selEnd);
1760 if (newto > to + (editor->bEmulateVersion10 ? 1 : 0)) {
1761 WCHAR lastchar[3] = {'\0', '\0'};
1762 int linebreakSize = editor->bEmulateVersion10 ? 2 : 1;
1763 ME_Cursor linebreakCursor = *selEnd, lastcharCursor = *selEnd;
1764 CHARFORMAT2W cf;
1766 /* Set the final eop to the char fmt of the last char */
1767 cf.cbSize = sizeof(cf);
1768 cf.dwMask = CFM_ALL2;
1769 ME_MoveCursorChars(editor, &lastcharCursor, -1, FALSE);
1770 ME_GetCharFormat(editor, &lastcharCursor, &linebreakCursor, &cf);
1771 set_selection_cursors(editor, newto, -1);
1772 ME_SetSelectionCharFormat(editor, &cf);
1773 set_selection_cursors(editor, newto, newto);
1775 ME_MoveCursorChars(editor, &linebreakCursor, -linebreakSize, FALSE);
1776 ME_GetTextW(editor, lastchar, 2, &linebreakCursor, linebreakSize, FALSE, FALSE);
1777 if (lastchar[0] == '\r' && (lastchar[1] == '\n' || lastchar[1] == '\0')) {
1778 ME_InternalDeleteText(editor, &linebreakCursor, linebreakSize, FALSE);
1782 to = ME_GetCursorOfs(&editor->pCursors[0]);
1783 num_read = to - from;
1785 style = parser.style;
1787 else if (format & SF_TEXT)
1789 num_read = ME_StreamInText(editor, format, &inStream, style);
1790 to = ME_GetCursorOfs(&editor->pCursors[0]);
1792 else
1793 ERR("EM_STREAMIN without SF_TEXT or SF_RTF\n");
1794 /* put the cursor at the top */
1795 if (!(format & SFF_SELECTION))
1796 set_selection_cursors(editor, 0, 0);
1797 cursor_from_char_ofs( editor, from, &start );
1798 ME_UpdateLinkAttribute(editor, &start, to - from);
1801 /* Restore saved undo mode */
1802 editor->nUndoMode = nUndoMode;
1804 /* even if we didn't add an undo, we need to commit anything on the stack */
1805 ME_CommitUndo(editor);
1807 /* If SFF_SELECTION isn't set, delete any undos from before we started too */
1808 if (!(format & SFF_SELECTION))
1809 ME_EmptyUndoStack(editor);
1811 ME_ReleaseStyle(style);
1812 editor->nEventMask = nEventMask;
1813 ME_UpdateRepaint(editor, FALSE);
1814 if (!(format & SFF_SELECTION)) {
1815 ME_ClearTempStyle(editor);
1817 update_caret(editor);
1818 ME_SendSelChange(editor);
1819 ME_SendRequestResize(editor, FALSE);
1821 return num_read;
1825 typedef struct tagME_RTFStringStreamStruct
1827 char *string;
1828 int pos;
1829 int length;
1830 } ME_RTFStringStreamStruct;
1832 static DWORD CALLBACK ME_ReadFromRTFString(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
1834 ME_RTFStringStreamStruct *pStruct = (ME_RTFStringStreamStruct *)dwCookie;
1835 int count;
1837 count = min(cb, pStruct->length - pStruct->pos);
1838 memmove(lpBuff, pStruct->string + pStruct->pos, count);
1839 pStruct->pos += count;
1840 *pcb = count;
1841 return 0;
1844 static void
1845 ME_StreamInRTFString(ME_TextEditor *editor, BOOL selection, char *string)
1847 EDITSTREAM es;
1848 ME_RTFStringStreamStruct data;
1850 data.string = string;
1851 data.length = strlen(string);
1852 data.pos = 0;
1853 es.dwCookie = (DWORD_PTR)&data;
1854 es.pfnCallback = ME_ReadFromRTFString;
1855 ME_StreamIn(editor, SF_RTF | (selection ? SFF_SELECTION : 0), &es, TRUE);
1859 static int
1860 ME_FindText(ME_TextEditor *editor, DWORD flags, const CHARRANGE *chrg, const WCHAR *text, CHARRANGE *chrgText)
1862 const int nLen = lstrlenW(text);
1863 const int nTextLen = ME_GetTextLength(editor);
1864 int nMin, nMax;
1865 ME_Cursor cursor;
1866 WCHAR wLastChar = ' ';
1868 TRACE("flags==0x%08x, chrg->cpMin==%d, chrg->cpMax==%d text==%s\n",
1869 flags, chrg->cpMin, chrg->cpMax, debugstr_w(text));
1871 if (flags & ~(FR_DOWN | FR_MATCHCASE | FR_WHOLEWORD))
1872 FIXME("Flags 0x%08x not implemented\n",
1873 flags & ~(FR_DOWN | FR_MATCHCASE | FR_WHOLEWORD));
1875 nMin = chrg->cpMin;
1876 if (chrg->cpMax == -1)
1877 nMax = nTextLen;
1878 else
1879 nMax = chrg->cpMax > nTextLen ? nTextLen : chrg->cpMax;
1881 /* In 1.0 emulation, if cpMax reaches end of text, add the FR_DOWN flag */
1882 if (editor->bEmulateVersion10 && nMax == nTextLen)
1884 flags |= FR_DOWN;
1887 /* In 1.0 emulation, cpMin must always be no greater than cpMax */
1888 if (editor->bEmulateVersion10 && nMax < nMin)
1890 if (chrgText)
1892 chrgText->cpMin = -1;
1893 chrgText->cpMax = -1;
1895 return -1;
1898 /* when searching up, if cpMin < cpMax, then instead of searching
1899 * on [cpMin,cpMax], we search on [0,cpMin], otherwise, search on
1900 * [cpMax, cpMin]. The exception is when cpMax is -1, in which
1901 * case, it is always bigger than cpMin.
1903 if (!editor->bEmulateVersion10 && !(flags & FR_DOWN))
1905 int nSwap = nMax;
1907 nMax = nMin > nTextLen ? nTextLen : nMin;
1908 if (nMin < nSwap || chrg->cpMax == -1)
1909 nMin = 0;
1910 else
1911 nMin = nSwap;
1914 if (!nLen || nMin < 0 || nMax < 0 || nMax < nMin)
1916 if (chrgText)
1917 chrgText->cpMin = chrgText->cpMax = -1;
1918 return -1;
1921 if (flags & FR_DOWN) /* Forward search */
1923 /* If possible, find the character before where the search starts */
1924 if ((flags & FR_WHOLEWORD) && nMin)
1926 cursor_from_char_ofs( editor, nMin - 1, &cursor );
1927 wLastChar = *get_text( &cursor.pRun->member.run, cursor.nOffset );
1928 ME_MoveCursorChars(editor, &cursor, 1, FALSE);
1930 else cursor_from_char_ofs( editor, nMin, &cursor );
1932 while (cursor.pRun && ME_GetCursorOfs(&cursor) + nLen <= nMax)
1934 ME_DisplayItem *pCurItem = cursor.pRun;
1935 int nCurStart = cursor.nOffset;
1936 int nMatched = 0;
1938 while (pCurItem && ME_CharCompare( *get_text( &pCurItem->member.run, nCurStart + nMatched ), text[nMatched], (flags & FR_MATCHCASE)))
1940 if ((flags & FR_WHOLEWORD) && iswalnum(wLastChar))
1941 break;
1943 nMatched++;
1944 if (nMatched == nLen)
1946 ME_DisplayItem *pNextItem = pCurItem;
1947 int nNextStart = nCurStart;
1948 WCHAR wNextChar;
1950 /* Check to see if next character is a whitespace */
1951 if (flags & FR_WHOLEWORD)
1953 if (nCurStart + nMatched == pCurItem->member.run.len)
1955 pNextItem = ME_FindItemFwd(pCurItem, diRun);
1956 nNextStart = -nMatched;
1959 if (pNextItem)
1960 wNextChar = *get_text( &pNextItem->member.run, nNextStart + nMatched );
1961 else
1962 wNextChar = ' ';
1964 if (iswalnum(wNextChar))
1965 break;
1968 cursor.nOffset += cursor.pPara->member.para.nCharOfs + cursor.pRun->member.run.nCharOfs;
1969 if (chrgText)
1971 chrgText->cpMin = cursor.nOffset;
1972 chrgText->cpMax = cursor.nOffset + nLen;
1974 TRACE("found at %d-%d\n", cursor.nOffset, cursor.nOffset + nLen);
1975 return cursor.nOffset;
1977 if (nCurStart + nMatched == pCurItem->member.run.len)
1979 pCurItem = ME_FindItemFwd(pCurItem, diRun);
1980 nCurStart = -nMatched;
1983 if (pCurItem)
1984 wLastChar = *get_text( &pCurItem->member.run, nCurStart + nMatched );
1985 else
1986 wLastChar = ' ';
1988 cursor.nOffset++;
1989 if (cursor.nOffset == cursor.pRun->member.run.len)
1991 ME_NextRun(&cursor.pPara, &cursor.pRun, TRUE);
1992 cursor.nOffset = 0;
1996 else /* Backward search */
1998 /* If possible, find the character after where the search ends */
1999 if ((flags & FR_WHOLEWORD) && nMax < nTextLen - 1)
2001 cursor_from_char_ofs( editor, nMax + 1, &cursor );
2002 wLastChar = *get_text( &cursor.pRun->member.run, cursor.nOffset );
2003 ME_MoveCursorChars(editor, &cursor, -1, FALSE);
2005 else cursor_from_char_ofs( editor, nMax, &cursor );
2007 while (cursor.pRun && ME_GetCursorOfs(&cursor) - nLen >= nMin)
2009 ME_DisplayItem *pCurItem = cursor.pRun;
2010 ME_DisplayItem *pCurPara = cursor.pPara;
2011 int nCurEnd = cursor.nOffset;
2012 int nMatched = 0;
2014 if (nCurEnd == 0)
2016 ME_PrevRun(&pCurPara, &pCurItem, TRUE);
2017 nCurEnd = pCurItem->member.run.len;
2020 while (pCurItem && ME_CharCompare( *get_text( &pCurItem->member.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_DisplayItem *pPrevItem = pCurItem;
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 pPrevItem = ME_FindItemBack(pCurItem, diRun);
2040 if (pPrevItem)
2041 nPrevEnd = pPrevItem->member.run.len + nMatched;
2044 if (pPrevItem)
2045 wPrevChar = *get_text( &pPrevItem->member.run, nPrevEnd - nMatched - 1 );
2046 else
2047 wPrevChar = ' ';
2049 if (iswalnum(wPrevChar))
2050 break;
2053 nStart = pCurPara->member.para.nCharOfs
2054 + pCurItem->member.run.nCharOfs + nCurEnd - nMatched;
2055 if (chrgText)
2057 chrgText->cpMin = nStart;
2058 chrgText->cpMax = nStart + nLen;
2060 TRACE("found at %d-%d\n", nStart, nStart + nLen);
2061 return nStart;
2063 if (nCurEnd - nMatched == 0)
2065 ME_PrevRun(&pCurPara, &pCurItem, TRUE);
2066 /* Don't care about pCurItem becoming NULL here; it's already taken
2067 * care of in the exterior loop condition */
2068 nCurEnd = pCurItem->member.run.len + nMatched;
2071 if (pCurItem)
2072 wLastChar = *get_text( &pCurItem->member.run, nCurEnd - nMatched - 1 );
2073 else
2074 wLastChar = ' ';
2076 cursor.nOffset--;
2077 if (cursor.nOffset < 0)
2079 ME_PrevRun(&cursor.pPara, &cursor.pRun, TRUE);
2080 cursor.nOffset = cursor.pRun->member.run.len;
2084 TRACE("not found\n");
2085 if (chrgText)
2086 chrgText->cpMin = chrgText->cpMax = -1;
2087 return -1;
2090 static int ME_GetTextEx(ME_TextEditor *editor, GETTEXTEX *ex, LPARAM pText)
2092 int nChars;
2093 ME_Cursor start;
2095 if (!ex->cb || !pText) return 0;
2097 if (ex->flags & ~(GT_SELECTION | GT_USECRLF))
2098 FIXME("GETTEXTEX flags 0x%08x not supported\n", ex->flags & ~(GT_SELECTION | GT_USECRLF));
2100 if (ex->flags & GT_SELECTION)
2102 int from, to;
2103 int nStartCur = ME_GetSelectionOfs(editor, &from, &to);
2104 start = editor->pCursors[nStartCur];
2105 nChars = to - from;
2107 else
2109 ME_SetCursorToStart(editor, &start);
2110 nChars = INT_MAX;
2112 if (ex->codepage == CP_UNICODE)
2114 return ME_GetTextW(editor, (LPWSTR)pText, ex->cb / sizeof(WCHAR) - 1,
2115 &start, nChars, ex->flags & GT_USECRLF, FALSE);
2117 else
2119 /* potentially each char may be a CR, why calculate the exact value with O(N) when
2120 we can just take a bigger buffer? :)
2121 The above assumption still holds with CR/LF counters, since CR->CRLF expansion
2122 occurs only in richedit 2.0 mode, in which line breaks have only one CR
2124 int crlfmul = (ex->flags & GT_USECRLF) ? 2 : 1;
2125 DWORD buflen;
2126 LPWSTR buffer;
2127 LRESULT rc;
2129 buflen = min(crlfmul * nChars, ex->cb - 1);
2130 buffer = heap_alloc((buflen + 1) * sizeof(WCHAR));
2132 nChars = ME_GetTextW(editor, buffer, buflen, &start, nChars, ex->flags & GT_USECRLF, FALSE);
2133 rc = WideCharToMultiByte(ex->codepage, 0, buffer, nChars + 1,
2134 (LPSTR)pText, ex->cb, ex->lpDefaultChar, ex->lpUsedDefChar);
2135 if (rc) rc--; /* do not count 0 terminator */
2137 heap_free(buffer);
2138 return rc;
2142 static int ME_GetTextRange(ME_TextEditor *editor, WCHAR *strText,
2143 const ME_Cursor *start, int nLen, BOOL unicode)
2145 if (!strText) return 0;
2146 if (unicode) {
2147 return ME_GetTextW(editor, strText, INT_MAX, start, nLen, FALSE, FALSE);
2148 } else {
2149 int nChars;
2150 WCHAR *p = heap_alloc((nLen+1) * sizeof(*p));
2151 if (!p) return 0;
2152 nChars = ME_GetTextW(editor, p, nLen, start, nLen, FALSE, FALSE);
2153 WideCharToMultiByte(CP_ACP, 0, p, nChars+1, (char *)strText,
2154 nLen+1, NULL, NULL);
2155 heap_free(p);
2156 return nChars;
2160 int set_selection( ME_TextEditor *editor, int to, int from )
2162 int end;
2164 TRACE("%d - %d\n", to, from );
2166 if (!editor->bHideSelection) ME_InvalidateSelection( editor );
2167 end = set_selection_cursors( editor, to, from );
2168 if (!editor->bHideSelection) ME_InvalidateSelection( editor );
2169 update_caret( editor );
2170 ME_SendSelChange( editor );
2172 return end;
2175 typedef struct tagME_GlobalDestStruct
2177 HGLOBAL hData;
2178 int nLength;
2179 } ME_GlobalDestStruct;
2181 static DWORD CALLBACK ME_ReadFromHGLOBALUnicode(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
2183 ME_GlobalDestStruct *pData = (ME_GlobalDestStruct *)dwCookie;
2184 int i;
2185 WORD *pSrc, *pDest;
2187 cb = cb >> 1;
2188 pDest = (WORD *)lpBuff;
2189 pSrc = GlobalLock(pData->hData);
2190 for (i = 0; i<cb && pSrc[pData->nLength+i]; i++) {
2191 pDest[i] = pSrc[pData->nLength+i];
2193 pData->nLength += i;
2194 *pcb = 2*i;
2195 GlobalUnlock(pData->hData);
2196 return 0;
2199 static DWORD CALLBACK ME_ReadFromHGLOBALRTF(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
2201 ME_GlobalDestStruct *pData = (ME_GlobalDestStruct *)dwCookie;
2202 int i;
2203 BYTE *pSrc, *pDest;
2205 pDest = lpBuff;
2206 pSrc = GlobalLock(pData->hData);
2207 for (i = 0; i<cb && pSrc[pData->nLength+i]; i++) {
2208 pDest[i] = pSrc[pData->nLength+i];
2210 pData->nLength += i;
2211 *pcb = i;
2212 GlobalUnlock(pData->hData);
2213 return 0;
2216 static const WCHAR rtfW[] = {'R','i','c','h',' ','T','e','x','t',' ','F','o','r','m','a','t',0};
2218 static HRESULT paste_rtf(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2220 EDITSTREAM es;
2221 ME_GlobalDestStruct gds;
2222 HRESULT hr;
2224 gds.hData = med->u.hGlobal;
2225 gds.nLength = 0;
2226 es.dwCookie = (DWORD_PTR)&gds;
2227 es.pfnCallback = ME_ReadFromHGLOBALRTF;
2228 hr = ME_StreamIn( editor, SF_RTF | SFF_SELECTION, &es, FALSE ) == 0 ? E_FAIL : S_OK;
2229 ReleaseStgMedium( med );
2230 return hr;
2233 static HRESULT paste_text(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2235 EDITSTREAM es;
2236 ME_GlobalDestStruct gds;
2237 HRESULT hr;
2239 gds.hData = med->u.hGlobal;
2240 gds.nLength = 0;
2241 es.dwCookie = (DWORD_PTR)&gds;
2242 es.pfnCallback = ME_ReadFromHGLOBALUnicode;
2243 hr = ME_StreamIn( editor, SF_TEXT | SF_UNICODE | SFF_SELECTION, &es, FALSE ) == 0 ? E_FAIL : S_OK;
2244 ReleaseStgMedium( med );
2245 return hr;
2248 static HRESULT paste_emf(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2250 HRESULT hr;
2251 SIZEL sz = {0, 0};
2253 hr = insert_static_object( editor, med->u.hEnhMetaFile, NULL, &sz );
2254 if (SUCCEEDED(hr))
2256 ME_CommitUndo( editor );
2257 ME_UpdateRepaint( editor, FALSE );
2259 else
2260 ReleaseStgMedium( med );
2262 return hr;
2265 static struct paste_format
2267 FORMATETC fmt;
2268 HRESULT (*paste)(ME_TextEditor *, FORMATETC *, STGMEDIUM *);
2269 const WCHAR *name;
2270 } paste_formats[] =
2272 {{ -1, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, paste_rtf, rtfW },
2273 {{ CF_UNICODETEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, paste_text },
2274 {{ CF_ENHMETAFILE, NULL, DVASPECT_CONTENT, -1, TYMED_ENHMF }, paste_emf },
2275 {{ 0 }}
2278 static void init_paste_formats(void)
2280 struct paste_format *format;
2281 static int done;
2283 if (!done)
2285 for (format = paste_formats; format->fmt.cfFormat; format++)
2287 if (format->name)
2288 format->fmt.cfFormat = RegisterClipboardFormatW( format->name );
2290 done = 1;
2294 static BOOL paste_special(ME_TextEditor *editor, UINT cf, REPASTESPECIAL *ps, BOOL check_only)
2296 HRESULT hr;
2297 STGMEDIUM med;
2298 struct paste_format *format;
2299 IDataObject *data;
2301 /* Protect read-only edit control from modification */
2302 if (editor->styleFlags & ES_READONLY)
2304 if (!check_only)
2305 MessageBeep(MB_ICONERROR);
2306 return FALSE;
2309 init_paste_formats();
2311 if (ps && ps->dwAspect != DVASPECT_CONTENT)
2312 FIXME("Ignoring aspect %x\n", ps->dwAspect);
2314 hr = OleGetClipboard( &data );
2315 if (hr != S_OK) return FALSE;
2317 if (cf == CF_TEXT) cf = CF_UNICODETEXT;
2319 hr = S_FALSE;
2320 for (format = paste_formats; format->fmt.cfFormat; format++)
2322 if (cf && cf != format->fmt.cfFormat) continue;
2323 hr = IDataObject_QueryGetData( data, &format->fmt );
2324 if (hr == S_OK)
2326 if (!check_only)
2328 hr = IDataObject_GetData( data, &format->fmt, &med );
2329 if (hr != S_OK) goto done;
2330 hr = format->paste( editor, &format->fmt, &med );
2332 break;
2336 done:
2337 IDataObject_Release( data );
2339 return hr == S_OK;
2342 static HRESULT editor_copy( ME_TextEditor *editor, ME_Cursor *start, int chars, IDataObject **data_out )
2344 IDataObject *data = NULL;
2345 HRESULT hr = S_OK;
2347 if (editor->lpOleCallback)
2349 CHARRANGE range;
2350 range.cpMin = ME_GetCursorOfs( start );
2351 range.cpMax = range.cpMin + chars;
2352 hr = IRichEditOleCallback_GetClipboardData( editor->lpOleCallback, &range, RECO_COPY, &data );
2355 if (FAILED( hr ) || !data)
2356 hr = ME_GetDataObject( editor, start, chars, &data );
2358 if (SUCCEEDED( hr ))
2360 if (data_out)
2361 *data_out = data;
2362 else
2364 hr = OleSetClipboard( data );
2365 IDataObject_Release( data );
2369 return hr;
2372 HRESULT editor_copy_or_cut( ME_TextEditor *editor, BOOL cut, ME_Cursor *start, int count,
2373 IDataObject **data_out )
2375 HRESULT hr;
2377 if (cut && (editor->styleFlags & ES_READONLY))
2379 return E_ACCESSDENIED;
2382 hr = editor_copy( editor, start, count, data_out );
2383 if (SUCCEEDED(hr) && cut)
2385 ME_InternalDeleteText( editor, start, count, FALSE );
2386 ME_CommitUndo( editor );
2387 ME_UpdateRepaint( editor, TRUE );
2389 return hr;
2392 static BOOL copy_or_cut( ME_TextEditor *editor, BOOL cut )
2394 HRESULT hr;
2395 int offs, count;
2396 int start_cursor = ME_GetSelectionOfs( editor, &offs, &count );
2397 ME_Cursor *sel_start = &editor->pCursors[start_cursor];
2399 if (editor->cPasswordMask) return FALSE;
2401 count -= offs;
2402 hr = editor_copy_or_cut( editor, cut, sel_start, count, NULL );
2403 if (FAILED( hr )) MessageBeep( MB_ICONERROR );
2405 return SUCCEEDED( hr );
2408 /* helper to send a msg filter notification */
2409 static BOOL
2410 ME_FilterEvent(ME_TextEditor *editor, UINT msg, WPARAM* wParam, LPARAM* lParam)
2412 MSGFILTER msgf;
2414 if (!editor->hWnd || !editor->hwndParent) return FALSE;
2415 msgf.nmhdr.hwndFrom = editor->hWnd;
2416 msgf.nmhdr.idFrom = GetWindowLongW(editor->hWnd, GWLP_ID);
2417 msgf.nmhdr.code = EN_MSGFILTER;
2418 msgf.msg = msg;
2419 msgf.wParam = *wParam;
2420 msgf.lParam = *lParam;
2421 if (SendMessageW(editor->hwndParent, WM_NOTIFY, msgf.nmhdr.idFrom, (LPARAM)&msgf))
2422 return FALSE;
2423 *wParam = msgf.wParam;
2424 *lParam = msgf.lParam;
2425 msgf.wParam = *wParam;
2427 return TRUE;
2430 static void ME_UpdateSelectionLinkAttribute(ME_TextEditor *editor)
2432 ME_DisplayItem *startPara, *endPara;
2433 ME_DisplayItem *prev_para;
2434 ME_Cursor *from, *to;
2435 ME_Cursor start;
2436 int nChars;
2438 if (!editor->AutoURLDetect_bEnable) return;
2440 ME_GetSelection(editor, &from, &to);
2442 /* Find paragraph previous to the one that contains start cursor */
2443 startPara = from->pPara;
2444 prev_para = startPara->member.para.prev_para;
2445 if (prev_para->type == diParagraph) startPara = prev_para;
2447 /* Find paragraph that contains end cursor */
2448 endPara = to->pPara->member.para.next_para;
2450 start.pPara = startPara;
2451 start.pRun = ME_FindItemFwd(startPara, diRun);
2452 start.nOffset = 0;
2453 nChars = endPara->member.para.nCharOfs - startPara->member.para.nCharOfs;
2455 ME_UpdateLinkAttribute(editor, &start, nChars);
2458 static BOOL handle_enter(ME_TextEditor *editor)
2460 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2461 BOOL shift_is_down = GetKeyState(VK_SHIFT) & 0x8000;
2463 if (editor->bDialogMode)
2465 if (ctrl_is_down)
2466 return TRUE;
2468 if (!(editor->styleFlags & ES_WANTRETURN))
2470 if (editor->hwndParent)
2472 DWORD dw;
2473 dw = SendMessageW(editor->hwndParent, DM_GETDEFID, 0, 0);
2474 if (HIWORD(dw) == DC_HASDEFID)
2476 HWND hwDefCtrl = GetDlgItem(editor->hwndParent, LOWORD(dw));
2477 if (hwDefCtrl)
2479 SendMessageW(editor->hwndParent, WM_NEXTDLGCTL, (WPARAM)hwDefCtrl, TRUE);
2480 PostMessageW(hwDefCtrl, WM_KEYDOWN, VK_RETURN, 0);
2484 return TRUE;
2488 if (editor->styleFlags & ES_MULTILINE)
2490 static const WCHAR endl = '\r';
2491 static const WCHAR endlv10[] = {'\r','\n'};
2492 ME_Cursor cursor = editor->pCursors[0];
2493 ME_DisplayItem *para = cursor.pPara;
2494 int from, to;
2495 ME_Style *style, *eop_style;
2497 if (editor->styleFlags & ES_READONLY)
2499 MessageBeep(MB_ICONERROR);
2500 return TRUE;
2503 ME_GetSelectionOfs(editor, &from, &to);
2504 if (editor->nTextLimit > ME_GetTextLength(editor) - (to-from))
2506 if (!editor->bEmulateVersion10) /* v4.1 */
2508 if (para->member.para.nFlags & MEPF_ROWEND)
2510 /* Add a new table row after this row. */
2511 para = para_get_di( table_append_row( editor, &para->member.para ) );
2512 para = para->member.para.next_para;
2513 editor->pCursors[0].pPara = para;
2514 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2515 editor->pCursors[0].nOffset = 0;
2516 editor->pCursors[1] = editor->pCursors[0];
2517 ME_CommitUndo(editor);
2518 ME_CheckTablesForCorruption(editor);
2519 ME_UpdateRepaint(editor, FALSE);
2520 return TRUE;
2522 else if (para == editor->pCursors[1].pPara &&
2523 cursor.nOffset + cursor.pRun->member.run.nCharOfs == 0 &&
2524 para->member.para.prev_para->member.para.nFlags & MEPF_ROWSTART &&
2525 !para->member.para.prev_para->member.para.nCharOfs)
2527 /* Insert a newline before the table. */
2528 para = para->member.para.prev_para;
2529 para->member.para.nFlags &= ~MEPF_ROWSTART;
2530 editor->pCursors[0].pPara = para;
2531 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2532 editor->pCursors[1] = editor->pCursors[0];
2533 ME_InsertTextFromCursor(editor, 0, &endl, 1,
2534 editor->pCursors[0].pRun->member.run.style);
2535 para = editor->pBuffer->pFirst->member.para.next_para;
2536 ME_SetDefaultParaFormat(editor, &para->member.para.fmt);
2537 para->member.para.nFlags = 0;
2538 para_mark_rewrap( editor, &para->member.para );
2539 editor->pCursors[0].pPara = para;
2540 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2541 editor->pCursors[1] = editor->pCursors[0];
2542 para->member.para.next_para->member.para.nFlags |= MEPF_ROWSTART;
2543 ME_CommitCoalescingUndo(editor);
2544 ME_CheckTablesForCorruption(editor);
2545 ME_UpdateRepaint(editor, FALSE);
2546 return TRUE;
2549 else /* v1.0 - 3.0 */
2551 ME_DisplayItem *para = cursor.pPara;
2552 if (ME_IsInTable(para))
2554 if (cursor.pRun->member.run.nFlags & MERF_ENDPARA)
2556 if (from == to)
2558 ME_ContinueCoalescingTransaction(editor);
2559 para = para_get_di( table_append_row( editor, &para->member.para ) );
2560 editor->pCursors[0].pPara = para;
2561 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2562 editor->pCursors[0].nOffset = 0;
2563 editor->pCursors[1] = editor->pCursors[0];
2564 ME_CommitCoalescingUndo(editor);
2565 ME_UpdateRepaint(editor, FALSE);
2566 return TRUE;
2569 else
2571 ME_ContinueCoalescingTransaction(editor);
2572 if (cursor.pRun->member.run.nCharOfs + cursor.nOffset == 0 &&
2573 !ME_IsInTable(para->member.para.prev_para))
2575 /* Insert newline before table */
2576 cursor.pRun = ME_FindItemBack(para, diRun);
2577 if (cursor.pRun)
2579 editor->pCursors[0].pRun = cursor.pRun;
2580 editor->pCursors[0].pPara = para->member.para.prev_para;
2582 editor->pCursors[0].nOffset = 0;
2583 editor->pCursors[1] = editor->pCursors[0];
2584 ME_InsertTextFromCursor(editor, 0, &endl, 1,
2585 editor->pCursors[0].pRun->member.run.style);
2587 else
2589 editor->pCursors[1] = editor->pCursors[0];
2590 para = para_get_di( table_append_row( editor, &para->member.para ) );
2591 editor->pCursors[0].pPara = para;
2592 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2593 editor->pCursors[0].nOffset = 0;
2594 editor->pCursors[1] = editor->pCursors[0];
2596 ME_CommitCoalescingUndo(editor);
2597 ME_UpdateRepaint(editor, FALSE);
2598 return TRUE;
2603 style = style_get_insert_style( editor, editor->pCursors );
2605 /* Normally the new eop style is the insert style, however in a list it is copied from the existing
2606 eop style (this prevents the list label style changing when the new eop is inserted).
2607 No extra ref is taken here on eop_style. */
2608 if (para->member.para.fmt.wNumbering)
2609 eop_style = para->member.para.eop_run->style;
2610 else
2611 eop_style = style;
2612 ME_ContinueCoalescingTransaction(editor);
2613 if (shift_is_down)
2614 ME_InsertEndRowFromCursor(editor, 0);
2615 else
2616 if (!editor->bEmulateVersion10)
2617 ME_InsertTextFromCursor(editor, 0, &endl, 1, eop_style);
2618 else
2619 ME_InsertTextFromCursor(editor, 0, endlv10, 2, eop_style);
2620 ME_CommitCoalescingUndo(editor);
2621 SetCursor(NULL);
2623 ME_UpdateSelectionLinkAttribute(editor);
2624 ME_UpdateRepaint(editor, FALSE);
2625 ME_SaveTempStyle(editor, style); /* set the temp insert style for the new para */
2626 ME_ReleaseStyle(style);
2628 return TRUE;
2630 return FALSE;
2633 static BOOL
2634 ME_KeyDown(ME_TextEditor *editor, WORD nKey)
2636 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2637 BOOL shift_is_down = GetKeyState(VK_SHIFT) & 0x8000;
2639 if (editor->bMouseCaptured)
2640 return FALSE;
2641 if (nKey != VK_SHIFT && nKey != VK_CONTROL && nKey != VK_MENU)
2642 editor->nSelectionType = stPosition;
2644 switch (nKey)
2646 case VK_LEFT:
2647 case VK_RIGHT:
2648 case VK_HOME:
2649 case VK_END:
2650 editor->nUDArrowX = -1;
2651 /* fall through */
2652 case VK_UP:
2653 case VK_DOWN:
2654 case VK_PRIOR:
2655 case VK_NEXT:
2656 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
2657 ME_ArrowKey(editor, nKey, shift_is_down, ctrl_is_down);
2658 return TRUE;
2659 case VK_BACK:
2660 case VK_DELETE:
2661 editor->nUDArrowX = -1;
2662 /* FIXME backspace and delete aren't the same, they act different wrt paragraph style of the merged paragraph */
2663 if (editor->styleFlags & ES_READONLY)
2664 return FALSE;
2665 if (ME_IsSelection(editor))
2667 ME_DeleteSelection(editor);
2668 ME_CommitUndo(editor);
2670 else if (nKey == VK_DELETE)
2672 /* Delete stops group typing.
2673 * (See MSDN remarks on EM_STOPGROUPTYPING message) */
2674 ME_DeleteTextAtCursor(editor, 1, 1);
2675 ME_CommitUndo(editor);
2677 else if (ME_ArrowKey(editor, VK_LEFT, FALSE, FALSE))
2679 BOOL bDeletionSucceeded;
2680 /* Backspace can be grouped for a single undo */
2681 ME_ContinueCoalescingTransaction(editor);
2682 bDeletionSucceeded = ME_DeleteTextAtCursor(editor, 1, 1);
2683 if (!bDeletionSucceeded && !editor->bEmulateVersion10) { /* v4.1 */
2684 /* Deletion was prevented so the cursor is moved back to where it was.
2685 * (e.g. this happens when trying to delete cell boundaries)
2687 ME_ArrowKey(editor, VK_RIGHT, FALSE, FALSE);
2689 ME_CommitCoalescingUndo(editor);
2691 else
2692 return TRUE;
2693 ME_MoveCursorFromTableRowStartParagraph(editor);
2694 ME_UpdateSelectionLinkAttribute(editor);
2695 ME_UpdateRepaint(editor, FALSE);
2696 ME_SendRequestResize(editor, FALSE);
2697 return TRUE;
2698 case VK_RETURN:
2699 if (!editor->bEmulateVersion10)
2700 return handle_enter(editor);
2701 break;
2702 case VK_ESCAPE:
2703 if (editor->bDialogMode && editor->hwndParent)
2704 PostMessageW(editor->hwndParent, WM_CLOSE, 0, 0);
2705 return TRUE;
2706 case VK_TAB:
2707 if (editor->bDialogMode && editor->hwndParent)
2708 SendMessageW(editor->hwndParent, WM_NEXTDLGCTL, shift_is_down, 0);
2709 return TRUE;
2710 case 'A':
2711 if (ctrl_is_down)
2713 set_selection( editor, 0, -1 );
2714 return TRUE;
2716 break;
2717 case 'V':
2718 if (ctrl_is_down)
2719 return paste_special( editor, 0, NULL, FALSE );
2720 break;
2721 case 'C':
2722 case 'X':
2723 if (ctrl_is_down)
2724 return copy_or_cut(editor, nKey == 'X');
2725 break;
2726 case 'Z':
2727 if (ctrl_is_down)
2729 ME_Undo(editor);
2730 return TRUE;
2732 break;
2733 case 'Y':
2734 if (ctrl_is_down)
2736 ME_Redo(editor);
2737 return TRUE;
2739 break;
2741 default:
2742 if (nKey != VK_SHIFT && nKey != VK_CONTROL && nKey && nKey != VK_MENU)
2743 editor->nUDArrowX = -1;
2744 if (ctrl_is_down)
2746 if (nKey == 'W')
2748 CHARFORMAT2W chf;
2749 char buf[2048];
2750 chf.cbSize = sizeof(chf);
2752 ME_GetSelectionCharFormat(editor, &chf);
2753 ME_DumpStyleToBuf(&chf, buf);
2754 MessageBoxA(NULL, buf, "Style dump", MB_OK);
2756 if (nKey == 'Q')
2758 ME_CheckCharOffsets(editor);
2762 return FALSE;
2765 static LRESULT ME_Char(ME_TextEditor *editor, WPARAM charCode,
2766 LPARAM flags, BOOL unicode)
2768 WCHAR wstr;
2770 if (editor->bMouseCaptured)
2771 return 0;
2773 if (editor->styleFlags & ES_READONLY)
2775 MessageBeep(MB_ICONERROR);
2776 return 0; /* FIXME really 0 ? */
2779 if (unicode)
2780 wstr = (WCHAR)charCode;
2781 else
2783 CHAR charA = charCode;
2784 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &wstr, 1);
2787 if (editor->bEmulateVersion10 && wstr == '\r')
2788 handle_enter(editor);
2790 if ((unsigned)wstr >= ' ' || wstr == '\t')
2792 ME_Cursor cursor = editor->pCursors[0];
2793 ME_DisplayItem *para = cursor.pPara;
2794 int from, to;
2795 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2796 ME_GetSelectionOfs(editor, &from, &to);
2797 if (wstr == '\t' &&
2798 /* v4.1 allows tabs to be inserted with ctrl key down */
2799 !(ctrl_is_down && !editor->bEmulateVersion10))
2801 ME_DisplayItem *para;
2802 BOOL bSelectedRow = FALSE;
2804 para = cursor.pPara;
2805 if (ME_IsSelection(editor) &&
2806 cursor.pRun->member.run.nCharOfs + cursor.nOffset == 0 &&
2807 to == ME_GetCursorOfs(&editor->pCursors[0]) &&
2808 para->member.para.prev_para->type == diParagraph)
2810 para = para->member.para.prev_para;
2811 bSelectedRow = TRUE;
2813 if (ME_IsInTable(para))
2815 ME_TabPressedInTable(editor, bSelectedRow);
2816 ME_CommitUndo(editor);
2817 return 0;
2819 } else if (!editor->bEmulateVersion10) { /* v4.1 */
2820 if (para->member.para.nFlags & MEPF_ROWEND) {
2821 if (from == to) {
2822 para = para->member.para.next_para;
2823 if (para->member.para.nFlags & MEPF_ROWSTART)
2824 para = para->member.para.next_para;
2825 editor->pCursors[0].pPara = para;
2826 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2827 editor->pCursors[0].nOffset = 0;
2828 editor->pCursors[1] = editor->pCursors[0];
2831 } else { /* v1.0 - 3.0 */
2832 if (ME_IsInTable(cursor.pRun) &&
2833 cursor.pRun->member.run.nFlags & MERF_ENDPARA &&
2834 from == to)
2836 /* Text should not be inserted at the end of the table. */
2837 MessageBeep(-1);
2838 return 0;
2841 /* FIXME maybe it would make sense to call EM_REPLACESEL instead ? */
2842 /* WM_CHAR is restricted to nTextLimit */
2843 if(editor->nTextLimit > ME_GetTextLength(editor) - (to-from))
2845 ME_Style *style = style_get_insert_style( editor, editor->pCursors );
2846 ME_ContinueCoalescingTransaction(editor);
2847 ME_InsertTextFromCursor(editor, 0, &wstr, 1, style);
2848 ME_ReleaseStyle(style);
2849 ME_CommitCoalescingUndo(editor);
2850 ITextHost_TxSetCursor(editor->texthost, NULL, FALSE);
2853 ME_UpdateSelectionLinkAttribute(editor);
2854 ME_UpdateRepaint(editor, FALSE);
2856 return 0;
2859 /* Process the message and calculate the new click count.
2861 * returns: The click count if it is mouse down event, else returns 0. */
2862 static int ME_CalculateClickCount(ME_TextEditor *editor, UINT msg, WPARAM wParam,
2863 LPARAM lParam)
2865 static int clickNum = 0;
2866 if (msg < WM_MOUSEFIRST || msg > WM_MOUSELAST)
2867 return 0;
2869 if ((msg == WM_LBUTTONDBLCLK) ||
2870 (msg == WM_RBUTTONDBLCLK) ||
2871 (msg == WM_MBUTTONDBLCLK) ||
2872 (msg == WM_XBUTTONDBLCLK))
2874 msg -= (WM_LBUTTONDBLCLK - WM_LBUTTONDOWN);
2877 if ((msg == WM_LBUTTONDOWN) ||
2878 (msg == WM_RBUTTONDOWN) ||
2879 (msg == WM_MBUTTONDOWN) ||
2880 (msg == WM_XBUTTONDOWN))
2882 static MSG prevClickMsg;
2883 MSG clickMsg;
2884 /* Compare the editor instead of the hwnd so that the this
2885 * can still be done for windowless richedit controls. */
2886 clickMsg.hwnd = (HWND)editor;
2887 clickMsg.message = msg;
2888 clickMsg.wParam = wParam;
2889 clickMsg.lParam = lParam;
2890 clickMsg.time = GetMessageTime();
2891 clickMsg.pt.x = (short)LOWORD(lParam);
2892 clickMsg.pt.y = (short)HIWORD(lParam);
2893 if ((clickNum != 0) &&
2894 (clickMsg.message == prevClickMsg.message) &&
2895 (clickMsg.hwnd == prevClickMsg.hwnd) &&
2896 (clickMsg.wParam == prevClickMsg.wParam) &&
2897 (clickMsg.time - prevClickMsg.time < GetDoubleClickTime()) &&
2898 (abs(clickMsg.pt.x - prevClickMsg.pt.x) < GetSystemMetrics(SM_CXDOUBLECLK)/2) &&
2899 (abs(clickMsg.pt.y - prevClickMsg.pt.y) < GetSystemMetrics(SM_CYDOUBLECLK)/2))
2901 clickNum++;
2902 } else {
2903 clickNum = 1;
2905 prevClickMsg = clickMsg;
2906 } else {
2907 return 0;
2909 return clickNum;
2912 static BOOL is_link( ME_Run *run )
2914 return (run->style->fmt.dwMask & CFM_LINK) && (run->style->fmt.dwEffects & CFE_LINK);
2917 static BOOL ME_SetCursor(ME_TextEditor *editor)
2919 ME_Cursor cursor;
2920 POINT pt;
2921 BOOL isExact;
2922 SCROLLBARINFO sbi;
2923 DWORD messagePos = GetMessagePos();
2924 pt.x = (short)LOWORD(messagePos);
2925 pt.y = (short)HIWORD(messagePos);
2927 if (editor->hWnd)
2929 sbi.cbSize = sizeof(sbi);
2930 GetScrollBarInfo(editor->hWnd, OBJID_HSCROLL, &sbi);
2931 if (!(sbi.rgstate[0] & (STATE_SYSTEM_INVISIBLE|STATE_SYSTEM_OFFSCREEN)) &&
2932 PtInRect(&sbi.rcScrollBar, pt))
2934 ITextHost_TxSetCursor(editor->texthost,
2935 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2936 return TRUE;
2938 sbi.cbSize = sizeof(sbi);
2939 GetScrollBarInfo(editor->hWnd, OBJID_VSCROLL, &sbi);
2940 if (!(sbi.rgstate[0] & (STATE_SYSTEM_INVISIBLE|STATE_SYSTEM_OFFSCREEN)) &&
2941 PtInRect(&sbi.rcScrollBar, pt))
2943 ITextHost_TxSetCursor(editor->texthost,
2944 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2945 return TRUE;
2948 ITextHost_TxScreenToClient(editor->texthost, &pt);
2950 if (editor->nSelectionType == stLine && editor->bMouseCaptured) {
2951 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2952 return TRUE;
2954 if (!editor->bEmulateVersion10 /* v4.1 */ &&
2955 pt.y < editor->rcFormat.top &&
2956 pt.x < editor->rcFormat.left)
2958 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2959 return TRUE;
2961 if (pt.y < editor->rcFormat.top || pt.y > editor->rcFormat.bottom)
2963 if (editor->bEmulateVersion10) /* v1.0 - 3.0 */
2964 ITextHost_TxSetCursor(editor->texthost,
2965 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2966 else /* v4.1 */
2967 ITextHost_TxSetCursor(editor->texthost,
2968 LoadCursorW(NULL, (WCHAR*)IDC_IBEAM), TRUE);
2969 return TRUE;
2971 if (pt.x < editor->rcFormat.left)
2973 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2974 return TRUE;
2976 ME_CharFromPos(editor, pt.x, pt.y, &cursor, &isExact);
2977 if (isExact)
2979 ME_Run *run;
2981 run = &cursor.pRun->member.run;
2982 if (is_link( run ))
2984 ITextHost_TxSetCursor(editor->texthost,
2985 LoadCursorW(NULL, (WCHAR*)IDC_HAND),
2986 FALSE);
2987 return TRUE;
2990 if (ME_IsSelection(editor))
2992 int selStart, selEnd;
2993 int offset = ME_GetCursorOfs(&cursor);
2995 ME_GetSelectionOfs(editor, &selStart, &selEnd);
2996 if (selStart <= offset && selEnd >= offset) {
2997 ITextHost_TxSetCursor(editor->texthost,
2998 LoadCursorW(NULL, (WCHAR*)IDC_ARROW),
2999 FALSE);
3000 return TRUE;
3004 ITextHost_TxSetCursor(editor->texthost,
3005 LoadCursorW(NULL, (WCHAR*)IDC_IBEAM), TRUE);
3006 return TRUE;
3009 static void ME_SetDefaultFormatRect(ME_TextEditor *editor)
3011 ITextHost_TxGetClientRect(editor->texthost, &editor->rcFormat);
3012 editor->rcFormat.top += editor->exStyleFlags & WS_EX_CLIENTEDGE ? 1 : 0;
3013 editor->rcFormat.left += 1 + editor->selofs;
3014 editor->rcFormat.right -= 1;
3017 static LONG ME_GetSelectionType(ME_TextEditor *editor)
3019 LONG sel_type = SEL_EMPTY;
3020 LONG start, end;
3022 ME_GetSelectionOfs(editor, &start, &end);
3023 if (start == end)
3024 sel_type = SEL_EMPTY;
3025 else
3027 LONG object_count = 0, character_count = 0;
3028 int i;
3030 for (i = 0; i < end - start; i++)
3032 ME_Cursor cursor;
3034 cursor_from_char_ofs( editor, start + i, &cursor );
3035 if (cursor.pRun->member.run.reobj)
3036 object_count++;
3037 else
3038 character_count++;
3039 if (character_count >= 2 && object_count >= 2)
3040 return (SEL_TEXT | SEL_MULTICHAR | SEL_OBJECT | SEL_MULTIOBJECT);
3042 if (character_count)
3044 sel_type |= SEL_TEXT;
3045 if (character_count >= 2)
3046 sel_type |= SEL_MULTICHAR;
3048 if (object_count)
3050 sel_type |= SEL_OBJECT;
3051 if (object_count >= 2)
3052 sel_type |= SEL_MULTIOBJECT;
3055 return sel_type;
3058 static BOOL ME_ShowContextMenu(ME_TextEditor *editor, int x, int y)
3060 CHARRANGE selrange;
3061 HMENU menu;
3062 int seltype;
3064 if(!editor->lpOleCallback || !editor->hWnd)
3065 return FALSE;
3066 ME_GetSelectionOfs(editor, &selrange.cpMin, &selrange.cpMax);
3067 seltype = ME_GetSelectionType(editor);
3068 if(SUCCEEDED(IRichEditOleCallback_GetContextMenu(editor->lpOleCallback, seltype, NULL, &selrange, &menu)))
3070 TrackPopupMenu(menu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, x, y, 0, editor->hwndParent, NULL);
3071 DestroyMenu(menu);
3073 return TRUE;
3076 ME_TextEditor *ME_MakeEditor(ITextHost *texthost, BOOL bEmulateVersion10)
3078 ME_TextEditor *ed = heap_alloc(sizeof(*ed));
3079 int i;
3080 DWORD props;
3081 LONG selbarwidth;
3083 ed->hWnd = NULL;
3084 ed->hwndParent = NULL;
3085 ed->sizeWindow.cx = ed->sizeWindow.cy = 0;
3086 ed->texthost = texthost;
3087 ed->reOle = NULL;
3088 ed->bEmulateVersion10 = bEmulateVersion10;
3089 ed->styleFlags = 0;
3090 ed->exStyleFlags = 0;
3091 ed->total_rows = 0;
3092 ITextHost_TxGetPropertyBits(texthost,
3093 (TXTBIT_RICHTEXT|TXTBIT_MULTILINE|
3094 TXTBIT_READONLY|TXTBIT_USEPASSWORD|
3095 TXTBIT_HIDESELECTION|TXTBIT_SAVESELECTION|
3096 TXTBIT_AUTOWORDSEL|TXTBIT_VERTICAL|
3097 TXTBIT_WORDWRAP|TXTBIT_DISABLEDRAG),
3098 &props);
3099 ITextHost_TxGetScrollBars(texthost, &ed->styleFlags);
3100 ed->styleFlags &= (WS_VSCROLL|WS_HSCROLL|ES_AUTOVSCROLL|
3101 ES_AUTOHSCROLL|ES_DISABLENOSCROLL);
3102 ed->pBuffer = ME_MakeText();
3103 ed->nZoomNumerator = ed->nZoomDenominator = 0;
3104 ed->nAvailWidth = 0; /* wrap to client area */
3105 list_init( &ed->style_list );
3106 ME_MakeFirstParagraph(ed);
3107 /* The four cursors are for:
3108 * 0 - The position where the caret is shown
3109 * 1 - The anchored end of the selection (for normal selection)
3110 * 2 & 3 - The anchored start and end respectively for word, line,
3111 * or paragraph selection.
3113 ed->nCursors = 4;
3114 ed->pCursors = heap_alloc(ed->nCursors * sizeof(*ed->pCursors));
3115 ME_SetCursorToStart(ed, &ed->pCursors[0]);
3116 ed->pCursors[1] = ed->pCursors[0];
3117 ed->pCursors[2] = ed->pCursors[0];
3118 ed->pCursors[3] = ed->pCursors[1];
3119 ed->nLastTotalLength = ed->nTotalLength = 0;
3120 ed->nLastTotalWidth = ed->nTotalWidth = 0;
3121 ed->nUDArrowX = -1;
3122 ed->rgbBackColor = -1;
3123 ed->hbrBackground = GetSysColorBrush(COLOR_WINDOW);
3124 ed->nEventMask = 0;
3125 ed->nModifyStep = 0;
3126 ed->nTextLimit = TEXT_LIMIT_DEFAULT;
3127 list_init( &ed->undo_stack );
3128 list_init( &ed->redo_stack );
3129 ed->nUndoStackSize = 0;
3130 ed->nUndoLimit = STACK_SIZE_DEFAULT;
3131 ed->nUndoMode = umAddToUndo;
3132 ed->nParagraphs = 1;
3133 ed->nLastSelStart = ed->nLastSelEnd = 0;
3134 ed->pLastSelStartPara = ed->pLastSelEndPara = ed->pCursors[0].pPara;
3135 ed->bHideSelection = FALSE;
3136 ed->pfnWordBreak = NULL;
3137 ed->lpOleCallback = NULL;
3138 ed->mode = TM_MULTILEVELUNDO | TM_MULTICODEPAGE;
3139 ed->mode |= (props & TXTBIT_RICHTEXT) ? TM_RICHTEXT : TM_PLAINTEXT;
3140 ed->AutoURLDetect_bEnable = FALSE;
3141 ed->bHaveFocus = FALSE;
3142 ed->bDialogMode = FALSE;
3143 ed->bMouseCaptured = FALSE;
3144 ed->caret_hidden = FALSE;
3145 ed->caret_height = 0;
3146 for (i=0; i<HFONT_CACHE_SIZE; i++)
3148 ed->pFontCache[i].nRefs = 0;
3149 ed->pFontCache[i].nAge = 0;
3150 ed->pFontCache[i].hFont = NULL;
3153 ME_CheckCharOffsets(ed);
3154 SetRectEmpty(&ed->rcFormat);
3155 ed->bDefaultFormatRect = TRUE;
3156 ITextHost_TxGetSelectionBarWidth(ed->texthost, &selbarwidth);
3157 if (selbarwidth) {
3158 /* FIXME: Convert selbarwidth from HIMETRIC to pixels */
3159 ed->selofs = SELECTIONBAR_WIDTH;
3160 ed->styleFlags |= ES_SELECTIONBAR;
3161 } else {
3162 ed->selofs = 0;
3164 ed->nSelectionType = stPosition;
3166 ed->cPasswordMask = 0;
3167 if (props & TXTBIT_USEPASSWORD)
3168 ITextHost_TxGetPasswordChar(texthost, &ed->cPasswordMask);
3170 if (props & TXTBIT_AUTOWORDSEL)
3171 ed->styleFlags |= ECO_AUTOWORDSELECTION;
3172 if (props & TXTBIT_MULTILINE) {
3173 ed->styleFlags |= ES_MULTILINE;
3174 ed->bWordWrap = (props & TXTBIT_WORDWRAP) != 0;
3175 } else {
3176 ed->bWordWrap = FALSE;
3178 if (props & TXTBIT_READONLY)
3179 ed->styleFlags |= ES_READONLY;
3180 if (!(props & TXTBIT_HIDESELECTION))
3181 ed->styleFlags |= ES_NOHIDESEL;
3182 if (props & TXTBIT_SAVESELECTION)
3183 ed->styleFlags |= ES_SAVESEL;
3184 if (props & TXTBIT_VERTICAL)
3185 ed->styleFlags |= ES_VERTICAL;
3186 if (props & TXTBIT_DISABLEDRAG)
3187 ed->styleFlags |= ES_NOOLEDRAGDROP;
3189 ed->notified_cr.cpMin = ed->notified_cr.cpMax = 0;
3191 /* Default scrollbar information */
3192 ed->vert_si.cbSize = sizeof(SCROLLINFO);
3193 ed->vert_si.nMin = 0;
3194 ed->vert_si.nMax = 0;
3195 ed->vert_si.nPage = 0;
3196 ed->vert_si.nPos = 0;
3198 ed->horz_si.cbSize = sizeof(SCROLLINFO);
3199 ed->horz_si.nMin = 0;
3200 ed->horz_si.nMax = 0;
3201 ed->horz_si.nPage = 0;
3202 ed->horz_si.nPos = 0;
3204 ed->wheel_remain = 0;
3206 list_init( &ed->reobj_list );
3207 OleInitialize(NULL);
3209 return ed;
3212 void ME_DestroyEditor(ME_TextEditor *editor)
3214 ME_DisplayItem *p = editor->pBuffer->pFirst, *pNext = NULL;
3215 ME_Style *s, *cursor2;
3216 int i;
3218 ME_ClearTempStyle(editor);
3219 ME_EmptyUndoStack(editor);
3220 editor->pBuffer->pFirst = NULL;
3221 while(p)
3223 pNext = p->next;
3224 if (p->type == diParagraph)
3225 para_destroy( editor, &p->member.para );
3226 else
3227 ME_DestroyDisplayItem(p);
3228 p = pNext;
3231 LIST_FOR_EACH_ENTRY_SAFE( s, cursor2, &editor->style_list, ME_Style, entry )
3232 ME_DestroyStyle( s );
3234 ME_ReleaseStyle(editor->pBuffer->pDefaultStyle);
3235 for (i=0; i<HFONT_CACHE_SIZE; i++)
3237 if (editor->pFontCache[i].hFont)
3238 DeleteObject(editor->pFontCache[i].hFont);
3240 if (editor->rgbBackColor != -1)
3241 DeleteObject(editor->hbrBackground);
3242 if(editor->lpOleCallback)
3243 IRichEditOleCallback_Release(editor->lpOleCallback);
3244 ITextHost_Release(editor->texthost);
3245 if (editor->reOle)
3247 IUnknown_Release(editor->reOle);
3248 editor->reOle = NULL;
3250 OleUninitialize();
3252 heap_free(editor->pBuffer);
3253 heap_free(editor->pCursors);
3254 heap_free(editor);
3257 BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
3259 TRACE("\n");
3260 switch (fdwReason)
3262 case DLL_PROCESS_ATTACH:
3263 DisableThreadLibraryCalls(hinstDLL);
3264 me_heap = HeapCreate (0, 0x10000, 0);
3265 if (!ME_RegisterEditorClass(hinstDLL)) return FALSE;
3266 hLeft = LoadCursorW(hinstDLL, MAKEINTRESOURCEW(OCR_REVERSE));
3267 LookupInit();
3268 break;
3270 case DLL_PROCESS_DETACH:
3271 if (lpvReserved) break;
3272 UnregisterClassW(RICHEDIT_CLASS20W, 0);
3273 UnregisterClassW(MSFTEDIT_CLASS, 0);
3274 UnregisterClassA(RICHEDIT_CLASS20A, 0);
3275 UnregisterClassA("RichEdit50A", 0);
3276 if (ME_ListBoxRegistered)
3277 UnregisterClassW(REListBox20W, 0);
3278 if (ME_ComboBoxRegistered)
3279 UnregisterClassW(REComboBox20W, 0);
3280 LookupCleanup();
3281 HeapDestroy (me_heap);
3282 release_typelib();
3283 break;
3285 return TRUE;
3288 static inline int get_default_line_height( ME_TextEditor *editor )
3290 int height = 0;
3292 if (editor->pBuffer && editor->pBuffer->pDefaultStyle)
3293 height = editor->pBuffer->pDefaultStyle->tm.tmHeight;
3294 if (height <= 0) height = 24;
3296 return height;
3299 static inline int calc_wheel_change( int *remain, int amount_per_click )
3301 int change = amount_per_click * (float)*remain / WHEEL_DELTA;
3302 *remain -= WHEEL_DELTA * change / amount_per_click;
3303 return change;
3306 static const char * const edit_messages[] = {
3307 "EM_GETSEL",
3308 "EM_SETSEL",
3309 "EM_GETRECT",
3310 "EM_SETRECT",
3311 "EM_SETRECTNP",
3312 "EM_SCROLL",
3313 "EM_LINESCROLL",
3314 "EM_SCROLLCARET",
3315 "EM_GETMODIFY",
3316 "EM_SETMODIFY",
3317 "EM_GETLINECOUNT",
3318 "EM_LINEINDEX",
3319 "EM_SETHANDLE",
3320 "EM_GETHANDLE",
3321 "EM_GETTHUMB",
3322 "EM_UNKNOWN_BF",
3323 "EM_UNKNOWN_C0",
3324 "EM_LINELENGTH",
3325 "EM_REPLACESEL",
3326 "EM_UNKNOWN_C3",
3327 "EM_GETLINE",
3328 "EM_LIMITTEXT",
3329 "EM_CANUNDO",
3330 "EM_UNDO",
3331 "EM_FMTLINES",
3332 "EM_LINEFROMCHAR",
3333 "EM_UNKNOWN_CA",
3334 "EM_SETTABSTOPS",
3335 "EM_SETPASSWORDCHAR",
3336 "EM_EMPTYUNDOBUFFER",
3337 "EM_GETFIRSTVISIBLELINE",
3338 "EM_SETREADONLY",
3339 "EM_SETWORDBREAKPROC",
3340 "EM_GETWORDBREAKPROC",
3341 "EM_GETPASSWORDCHAR",
3342 "EM_SETMARGINS",
3343 "EM_GETMARGINS",
3344 "EM_GETLIMITTEXT",
3345 "EM_POSFROMCHAR",
3346 "EM_CHARFROMPOS",
3347 "EM_SETIMESTATUS",
3348 "EM_GETIMESTATUS"
3351 static const char * const richedit_messages[] = {
3352 "EM_CANPASTE",
3353 "EM_DISPLAYBAND",
3354 "EM_EXGETSEL",
3355 "EM_EXLIMITTEXT",
3356 "EM_EXLINEFROMCHAR",
3357 "EM_EXSETSEL",
3358 "EM_FINDTEXT",
3359 "EM_FORMATRANGE",
3360 "EM_GETCHARFORMAT",
3361 "EM_GETEVENTMASK",
3362 "EM_GETOLEINTERFACE",
3363 "EM_GETPARAFORMAT",
3364 "EM_GETSELTEXT",
3365 "EM_HIDESELECTION",
3366 "EM_PASTESPECIAL",
3367 "EM_REQUESTRESIZE",
3368 "EM_SELECTIONTYPE",
3369 "EM_SETBKGNDCOLOR",
3370 "EM_SETCHARFORMAT",
3371 "EM_SETEVENTMASK",
3372 "EM_SETOLECALLBACK",
3373 "EM_SETPARAFORMAT",
3374 "EM_SETTARGETDEVICE",
3375 "EM_STREAMIN",
3376 "EM_STREAMOUT",
3377 "EM_GETTEXTRANGE",
3378 "EM_FINDWORDBREAK",
3379 "EM_SETOPTIONS",
3380 "EM_GETOPTIONS",
3381 "EM_FINDTEXTEX",
3382 "EM_GETWORDBREAKPROCEX",
3383 "EM_SETWORDBREAKPROCEX",
3384 "EM_SETUNDOLIMIT",
3385 "EM_UNKNOWN_USER_83",
3386 "EM_REDO",
3387 "EM_CANREDO",
3388 "EM_GETUNDONAME",
3389 "EM_GETREDONAME",
3390 "EM_STOPGROUPTYPING",
3391 "EM_SETTEXTMODE",
3392 "EM_GETTEXTMODE",
3393 "EM_AUTOURLDETECT",
3394 "EM_GETAUTOURLDETECT",
3395 "EM_SETPALETTE",
3396 "EM_GETTEXTEX",
3397 "EM_GETTEXTLENGTHEX",
3398 "EM_SHOWSCROLLBAR",
3399 "EM_SETTEXTEX",
3400 "EM_UNKNOWN_USER_98",
3401 "EM_UNKNOWN_USER_99",
3402 "EM_SETPUNCTUATION",
3403 "EM_GETPUNCTUATION",
3404 "EM_SETWORDWRAPMODE",
3405 "EM_GETWORDWRAPMODE",
3406 "EM_SETIMECOLOR",
3407 "EM_GETIMECOLOR",
3408 "EM_SETIMEOPTIONS",
3409 "EM_GETIMEOPTIONS",
3410 "EM_CONVPOSITION",
3411 "EM_UNKNOWN_USER_109",
3412 "EM_UNKNOWN_USER_110",
3413 "EM_UNKNOWN_USER_111",
3414 "EM_UNKNOWN_USER_112",
3415 "EM_UNKNOWN_USER_113",
3416 "EM_UNKNOWN_USER_114",
3417 "EM_UNKNOWN_USER_115",
3418 "EM_UNKNOWN_USER_116",
3419 "EM_UNKNOWN_USER_117",
3420 "EM_UNKNOWN_USER_118",
3421 "EM_UNKNOWN_USER_119",
3422 "EM_SETLANGOPTIONS",
3423 "EM_GETLANGOPTIONS",
3424 "EM_GETIMECOMPMODE",
3425 "EM_FINDTEXTW",
3426 "EM_FINDTEXTEXW",
3427 "EM_RECONVERSION",
3428 "EM_SETIMEMODEBIAS",
3429 "EM_GETIMEMODEBIAS"
3432 static const char *
3433 get_msg_name(UINT msg)
3435 if (msg >= EM_GETSEL && msg <= EM_CHARFROMPOS)
3436 return edit_messages[msg - EM_GETSEL];
3437 if (msg >= EM_CANPASTE && msg <= EM_GETIMEMODEBIAS)
3438 return richedit_messages[msg - EM_CANPASTE];
3439 return "";
3442 static void ME_LinkNotify(ME_TextEditor *editor, UINT msg, WPARAM wParam, LPARAM lParam)
3444 int x,y;
3445 BOOL isExact;
3446 ME_Cursor cursor; /* The start of the clicked text. */
3448 ENLINK info;
3449 x = (short)LOWORD(lParam);
3450 y = (short)HIWORD(lParam);
3451 ME_CharFromPos(editor, x, y, &cursor, &isExact);
3452 if (!isExact) return;
3454 if (is_link( &cursor.pRun->member.run ))
3455 { /* The clicked run has CFE_LINK set */
3456 ME_DisplayItem *di;
3458 info.nmhdr.hwndFrom = NULL;
3459 info.nmhdr.idFrom = 0;
3460 info.nmhdr.code = EN_LINK;
3461 info.msg = msg;
3462 info.wParam = wParam;
3463 info.lParam = lParam;
3464 cursor.nOffset = 0;
3466 /* find the first contiguous run with CFE_LINK set */
3467 info.chrg.cpMin = ME_GetCursorOfs(&cursor);
3468 di = cursor.pRun;
3469 while (ME_PrevRun( NULL, &di, FALSE ) && is_link( &di->member.run ))
3470 info.chrg.cpMin -= di->member.run.len;
3472 /* find the last contiguous run with CFE_LINK set */
3473 info.chrg.cpMax = ME_GetCursorOfs(&cursor) + cursor.pRun->member.run.len;
3474 di = cursor.pRun;
3475 while (ME_NextRun( NULL, &di, FALSE ) && is_link( &di->member.run ))
3476 info.chrg.cpMax += di->member.run.len;
3478 ITextHost_TxNotify(editor->texthost, info.nmhdr.code, &info);
3482 void ME_ReplaceSel(ME_TextEditor *editor, BOOL can_undo, const WCHAR *str, int len)
3484 int from, to, nStartCursor;
3485 ME_Style *style;
3487 nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3488 style = ME_GetSelectionInsertStyle(editor);
3489 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to-from, FALSE);
3490 ME_InsertTextFromCursor(editor, 0, str, len, style);
3491 ME_ReleaseStyle(style);
3492 /* drop temporary style if line end */
3494 * FIXME question: does abc\n mean: put abc,
3495 * clear temp style, put \n? (would require a change)
3497 if (len>0 && str[len-1] == '\n')
3498 ME_ClearTempStyle(editor);
3499 ME_CommitUndo(editor);
3500 ME_UpdateSelectionLinkAttribute(editor);
3501 if (!can_undo)
3502 ME_EmptyUndoStack(editor);
3503 ME_UpdateRepaint(editor, FALSE);
3506 static void ME_SetText(ME_TextEditor *editor, void *text, BOOL unicode)
3508 LONG codepage = unicode ? CP_UNICODE : CP_ACP;
3509 int textLen;
3511 LPWSTR wszText = ME_ToUnicode(codepage, text, &textLen);
3512 ME_InsertTextFromCursor(editor, 0, wszText, textLen, editor->pBuffer->pDefaultStyle);
3513 ME_EndToUnicode(codepage, wszText);
3516 static LRESULT ME_WmCreate(ME_TextEditor *editor, LPARAM lParam, BOOL unicode)
3518 CREATESTRUCTW *createW = (CREATESTRUCTW*)lParam;
3519 CREATESTRUCTA *createA = (CREATESTRUCTA*)lParam;
3520 void *text = NULL;
3521 INT max;
3523 if (lParam)
3524 text = unicode ? (void*)createW->lpszName : (void*)createA->lpszName;
3526 ME_SetDefaultFormatRect(editor);
3528 max = (editor->styleFlags & ES_DISABLENOSCROLL) ? 1 : 0;
3529 if (~editor->styleFlags & ES_DISABLENOSCROLL || editor->styleFlags & WS_VSCROLL)
3530 ITextHost_TxSetScrollRange(editor->texthost, SB_VERT, 0, max, TRUE);
3532 if (~editor->styleFlags & ES_DISABLENOSCROLL || editor->styleFlags & WS_HSCROLL)
3533 ITextHost_TxSetScrollRange(editor->texthost, SB_HORZ, 0, max, TRUE);
3535 if (editor->styleFlags & ES_DISABLENOSCROLL)
3537 if (editor->styleFlags & WS_VSCROLL)
3539 ITextHost_TxEnableScrollBar(editor->texthost, SB_VERT, ESB_DISABLE_BOTH);
3540 ITextHost_TxShowScrollBar(editor->texthost, SB_VERT, TRUE);
3542 if (editor->styleFlags & WS_HSCROLL)
3544 ITextHost_TxEnableScrollBar(editor->texthost, SB_HORZ, ESB_DISABLE_BOTH);
3545 ITextHost_TxShowScrollBar(editor->texthost, SB_HORZ, TRUE);
3549 if (text)
3551 ME_SetText(editor, text, unicode);
3552 ME_SetCursorToStart(editor, &editor->pCursors[0]);
3553 ME_SetCursorToStart(editor, &editor->pCursors[1]);
3556 ME_CommitUndo(editor);
3557 ME_WrapMarkedParagraphs(editor);
3558 update_caret(editor);
3559 return 0;
3562 static LRESULT handle_EM_SETCHARFORMAT( ME_TextEditor *editor, WPARAM flags, const CHARFORMAT2W *fmt_in )
3564 CHARFORMAT2W fmt;
3565 BOOL changed = TRUE;
3566 ME_Cursor start, end;
3568 if (!cfany_to_cf2w( &fmt, fmt_in )) return 0;
3570 if (flags & SCF_ALL)
3572 if (editor->mode & TM_PLAINTEXT)
3574 ME_SetDefaultCharFormat( editor, &fmt );
3576 else
3578 ME_SetCursorToStart( editor, &start );
3579 ME_SetCharFormat( editor, &start, NULL, &fmt );
3580 editor->nModifyStep = 1;
3583 else if (flags & SCF_SELECTION)
3585 if (editor->mode & TM_PLAINTEXT) return 0;
3586 if (flags & SCF_WORD)
3588 end = editor->pCursors[0];
3589 ME_MoveCursorWords( editor, &end, +1 );
3590 start = end;
3591 ME_MoveCursorWords( editor, &start, -1 );
3592 ME_SetCharFormat( editor, &start, &end, &fmt );
3594 changed = ME_IsSelection( editor );
3595 ME_SetSelectionCharFormat( editor, &fmt );
3596 if (changed) editor->nModifyStep = 1;
3598 else /* SCF_DEFAULT */
3600 ME_SetDefaultCharFormat( editor, &fmt );
3603 ME_CommitUndo( editor );
3604 if (changed)
3606 ME_WrapMarkedParagraphs( editor );
3607 ME_UpdateScrollBar( editor );
3609 return 1;
3612 #define UNSUPPORTED_MSG(e) \
3613 case e: \
3614 FIXME(#e ": stub\n"); \
3615 *phresult = S_FALSE; \
3616 return 0;
3618 /* Handle messages for windowless and windowed richedit controls.
3620 * The LRESULT that is returned is a return value for window procs,
3621 * and the phresult parameter is the COM return code needed by the
3622 * text services interface. */
3623 LRESULT ME_HandleMessage(ME_TextEditor *editor, UINT msg, WPARAM wParam,
3624 LPARAM lParam, BOOL unicode, HRESULT* phresult)
3626 *phresult = S_OK;
3628 switch(msg) {
3630 UNSUPPORTED_MSG(EM_DISPLAYBAND)
3631 UNSUPPORTED_MSG(EM_FINDWORDBREAK)
3632 UNSUPPORTED_MSG(EM_FMTLINES)
3633 UNSUPPORTED_MSG(EM_FORMATRANGE)
3634 UNSUPPORTED_MSG(EM_GETBIDIOPTIONS)
3635 UNSUPPORTED_MSG(EM_GETEDITSTYLE)
3636 UNSUPPORTED_MSG(EM_GETIMECOMPMODE)
3637 UNSUPPORTED_MSG(EM_GETIMESTATUS)
3638 UNSUPPORTED_MSG(EM_SETIMESTATUS)
3639 UNSUPPORTED_MSG(EM_GETLANGOPTIONS)
3640 UNSUPPORTED_MSG(EM_GETREDONAME)
3641 UNSUPPORTED_MSG(EM_GETTYPOGRAPHYOPTIONS)
3642 UNSUPPORTED_MSG(EM_GETUNDONAME)
3643 UNSUPPORTED_MSG(EM_GETWORDBREAKPROCEX)
3644 UNSUPPORTED_MSG(EM_SETBIDIOPTIONS)
3645 UNSUPPORTED_MSG(EM_SETEDITSTYLE)
3646 UNSUPPORTED_MSG(EM_SETLANGOPTIONS)
3647 UNSUPPORTED_MSG(EM_SETMARGINS)
3648 UNSUPPORTED_MSG(EM_SETPALETTE)
3649 UNSUPPORTED_MSG(EM_SETTABSTOPS)
3650 UNSUPPORTED_MSG(EM_SETTYPOGRAPHYOPTIONS)
3651 UNSUPPORTED_MSG(EM_SETWORDBREAKPROCEX)
3653 /* Messages specific to Richedit controls */
3655 case EM_STREAMIN:
3656 return ME_StreamIn(editor, wParam, (EDITSTREAM*)lParam, TRUE);
3657 case EM_STREAMOUT:
3658 return ME_StreamOut(editor, wParam, (EDITSTREAM *)lParam);
3659 case WM_GETDLGCODE:
3661 UINT code = DLGC_WANTCHARS|DLGC_WANTTAB|DLGC_WANTARROWS;
3663 if (lParam)
3664 editor->bDialogMode = TRUE;
3665 if (editor->styleFlags & ES_MULTILINE)
3666 code |= DLGC_WANTMESSAGE;
3667 if (!(editor->styleFlags & ES_SAVESEL))
3668 code |= DLGC_HASSETSEL;
3669 return code;
3671 case EM_EMPTYUNDOBUFFER:
3672 ME_EmptyUndoStack(editor);
3673 return 0;
3674 case EM_GETSEL:
3676 /* Note: wParam/lParam can be NULL */
3677 UINT from, to;
3678 PUINT pfrom = wParam ? (PUINT)wParam : &from;
3679 PUINT pto = lParam ? (PUINT)lParam : &to;
3680 ME_GetSelectionOfs(editor, (int *)pfrom, (int *)pto);
3681 if ((*pfrom|*pto) & 0xFFFF0000)
3682 return -1;
3683 return MAKELONG(*pfrom,*pto);
3685 case EM_EXGETSEL:
3687 CHARRANGE *pRange = (CHARRANGE *)lParam;
3688 ME_GetSelectionOfs(editor, &pRange->cpMin, &pRange->cpMax);
3689 TRACE("EM_EXGETSEL = (%d,%d)\n", pRange->cpMin, pRange->cpMax);
3690 return 0;
3692 case EM_SETUNDOLIMIT:
3694 if ((int)wParam < 0)
3695 editor->nUndoLimit = STACK_SIZE_DEFAULT;
3696 else
3697 editor->nUndoLimit = min(wParam, STACK_SIZE_MAX);
3698 /* Setting a max stack size keeps wine from getting killed
3699 for hogging memory. Windows allocates all this memory at once, so
3700 no program would realistically set a value above our maximum. */
3701 return editor->nUndoLimit;
3703 case EM_CANUNDO:
3704 return !list_empty( &editor->undo_stack );
3705 case EM_CANREDO:
3706 return !list_empty( &editor->redo_stack );
3707 case WM_UNDO: /* FIXME: actually not the same */
3708 case EM_UNDO:
3709 return ME_Undo(editor);
3710 case EM_REDO:
3711 return ME_Redo(editor);
3712 case EM_GETOPTIONS:
3714 /* these flags are equivalent to the ES_* counterparts */
3715 DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
3716 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN | ECO_SELECTIONBAR;
3717 DWORD settings = editor->styleFlags & mask;
3719 return settings;
3721 case EM_SETFONTSIZE:
3723 CHARFORMAT2W cf;
3724 LONG tmp_size, size;
3725 BOOL is_increase = ((LONG)wParam > 0);
3727 if (editor->mode & TM_PLAINTEXT)
3728 return FALSE;
3730 cf.cbSize = sizeof(cf);
3731 cf.dwMask = CFM_SIZE;
3732 ME_GetSelectionCharFormat(editor, &cf);
3733 tmp_size = (cf.yHeight / 20) + wParam;
3735 if (tmp_size <= 1)
3736 size = 1;
3737 else if (tmp_size > 12 && tmp_size < 28 && tmp_size % 2)
3738 size = tmp_size + (is_increase ? 1 : -1);
3739 else if (tmp_size > 28 && tmp_size < 36)
3740 size = is_increase ? 36 : 28;
3741 else if (tmp_size > 36 && tmp_size < 48)
3742 size = is_increase ? 48 : 36;
3743 else if (tmp_size > 48 && tmp_size < 72)
3744 size = is_increase ? 72 : 48;
3745 else if (tmp_size > 72 && tmp_size < 80)
3746 size = is_increase ? 80 : 72;
3747 else if (tmp_size > 80 && tmp_size < 1638)
3748 size = 10 * (is_increase ? (tmp_size / 10 + 1) : (tmp_size / 10));
3749 else if (tmp_size >= 1638)
3750 size = 1638;
3751 else
3752 size = tmp_size;
3754 cf.yHeight = size * 20; /* convert twips to points */
3755 ME_SetSelectionCharFormat(editor, &cf);
3756 ME_CommitUndo(editor);
3757 ME_WrapMarkedParagraphs(editor);
3758 ME_UpdateScrollBar(editor);
3760 return TRUE;
3762 case EM_SETOPTIONS:
3764 /* these flags are equivalent to ES_* counterparts, except for
3765 * ECO_AUTOWORDSELECTION that doesn't have an ES_* counterpart,
3766 * but is still stored in editor->styleFlags. */
3767 const DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
3768 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN |
3769 ECO_SELECTIONBAR | ECO_AUTOWORDSELECTION;
3770 DWORD settings = mask & editor->styleFlags;
3771 DWORD oldSettings = settings;
3772 DWORD changedSettings;
3774 switch(wParam)
3776 case ECOOP_SET:
3777 settings = lParam;
3778 break;
3779 case ECOOP_OR:
3780 settings |= lParam;
3781 break;
3782 case ECOOP_AND:
3783 settings &= lParam;
3784 break;
3785 case ECOOP_XOR:
3786 settings ^= lParam;
3788 changedSettings = oldSettings ^ settings;
3790 if (changedSettings) {
3791 editor->styleFlags = (editor->styleFlags & ~mask) | (settings & mask);
3793 if (changedSettings & ECO_SELECTIONBAR)
3795 ITextHost_TxInvalidateRect(editor->texthost, &editor->rcFormat, TRUE);
3796 if (settings & ECO_SELECTIONBAR) {
3797 assert(!editor->selofs);
3798 editor->selofs = SELECTIONBAR_WIDTH;
3799 editor->rcFormat.left += editor->selofs;
3800 } else {
3801 editor->rcFormat.left -= editor->selofs;
3802 editor->selofs = 0;
3804 ME_RewrapRepaint(editor);
3807 if ((changedSettings & settings & ES_NOHIDESEL) && !editor->bHaveFocus)
3808 ME_InvalidateSelection( editor );
3810 if (changedSettings & settings & ECO_VERTICAL)
3811 FIXME("ECO_VERTICAL not implemented yet!\n");
3812 if (changedSettings & settings & ECO_AUTOHSCROLL)
3813 FIXME("ECO_AUTOHSCROLL not implemented yet!\n");
3814 if (changedSettings & settings & ECO_AUTOVSCROLL)
3815 FIXME("ECO_AUTOVSCROLL not implemented yet!\n");
3816 if (changedSettings & settings & ECO_WANTRETURN)
3817 FIXME("ECO_WANTRETURN not implemented yet!\n");
3818 if (changedSettings & settings & ECO_AUTOWORDSELECTION)
3819 FIXME("ECO_AUTOWORDSELECTION not implemented yet!\n");
3822 return settings;
3824 case EM_SETSEL:
3826 return set_selection( editor, wParam, lParam );
3828 case EM_SETSCROLLPOS:
3830 POINT *point = (POINT *)lParam;
3831 ME_ScrollAbs(editor, point->x, point->y);
3832 return 0;
3834 case EM_AUTOURLDETECT:
3836 if (wParam==1 || wParam ==0)
3838 editor->AutoURLDetect_bEnable = (BOOL)wParam;
3839 return 0;
3841 return E_INVALIDARG;
3843 case EM_GETAUTOURLDETECT:
3845 return editor->AutoURLDetect_bEnable;
3847 case EM_EXSETSEL:
3849 CHARRANGE range = *(CHARRANGE *)lParam;
3851 return set_selection( editor, range.cpMin, range.cpMax );
3853 case EM_SHOWSCROLLBAR:
3855 DWORD flags;
3857 switch (wParam)
3859 case SB_HORZ:
3860 flags = WS_HSCROLL;
3861 break;
3862 case SB_VERT:
3863 flags = WS_VSCROLL;
3864 break;
3865 case SB_BOTH:
3866 flags = WS_HSCROLL|WS_VSCROLL;
3867 break;
3868 default:
3869 return 0;
3872 if (lParam) {
3873 editor->styleFlags |= flags;
3874 if (flags & WS_HSCROLL)
3875 ITextHost_TxShowScrollBar(editor->texthost, SB_HORZ,
3876 editor->nTotalWidth > editor->sizeWindow.cx);
3877 if (flags & WS_VSCROLL)
3878 ITextHost_TxShowScrollBar(editor->texthost, SB_VERT,
3879 editor->nTotalLength > editor->sizeWindow.cy);
3880 } else {
3881 editor->styleFlags &= ~flags;
3882 ITextHost_TxShowScrollBar(editor->texthost, wParam, FALSE);
3884 return 0;
3886 case EM_SETTEXTEX:
3888 LPWSTR wszText;
3889 SETTEXTEX *pStruct = (SETTEXTEX *)wParam;
3890 int from, to, len;
3891 ME_Style *style;
3892 BOOL bRtf, bUnicode, bSelection, bUTF8;
3893 int oldModify = editor->nModifyStep;
3894 static const char utf8_bom[] = {0xef, 0xbb, 0xbf};
3896 if (!pStruct) return 0;
3898 /* If we detect ascii rtf at the start of the string,
3899 * we know it isn't unicode. */
3900 bRtf = (lParam && (!strncmp((char *)lParam, "{\\rtf", 5) ||
3901 !strncmp((char *)lParam, "{\\urtf", 6)));
3902 bUnicode = !bRtf && pStruct->codepage == CP_UNICODE;
3903 bUTF8 = (lParam && (!strncmp((char *)lParam, utf8_bom, 3)));
3905 TRACE("EM_SETTEXTEX - %s, flags %d, cp %d\n",
3906 bUnicode ? debugstr_w((LPCWSTR)lParam) : debugstr_a((LPCSTR)lParam),
3907 pStruct->flags, pStruct->codepage);
3909 bSelection = (pStruct->flags & ST_SELECTION) != 0;
3910 if (bSelection) {
3911 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3912 style = ME_GetSelectionInsertStyle(editor);
3913 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to - from, FALSE);
3914 } else {
3915 ME_Cursor start;
3916 ME_SetCursorToStart(editor, &start);
3917 ME_InternalDeleteText(editor, &start, ME_GetTextLength(editor), FALSE);
3918 style = editor->pBuffer->pDefaultStyle;
3921 if (bRtf) {
3922 ME_StreamInRTFString(editor, bSelection, (char *)lParam);
3923 if (bSelection) {
3924 /* FIXME: The length returned doesn't include the rtf control
3925 * characters, only the actual text. */
3926 len = lParam ? strlen((char *)lParam) : 0;
3928 } else {
3929 if (bUTF8 && !bUnicode) {
3930 wszText = ME_ToUnicode(CP_UTF8, (void *)(lParam+3), &len);
3931 ME_InsertTextFromCursor(editor, 0, wszText, len, style);
3932 ME_EndToUnicode(CP_UTF8, wszText);
3933 } else {
3934 wszText = ME_ToUnicode(pStruct->codepage, (void *)lParam, &len);
3935 ME_InsertTextFromCursor(editor, 0, wszText, len, style);
3936 ME_EndToUnicode(pStruct->codepage, wszText);
3940 if (bSelection) {
3941 ME_ReleaseStyle(style);
3942 ME_UpdateSelectionLinkAttribute(editor);
3943 } else {
3944 ME_Cursor cursor;
3945 len = 1;
3946 ME_SetCursorToStart(editor, &cursor);
3947 ME_UpdateLinkAttribute(editor, &cursor, INT_MAX);
3949 ME_CommitUndo(editor);
3950 if (!(pStruct->flags & ST_KEEPUNDO))
3952 editor->nModifyStep = oldModify;
3953 ME_EmptyUndoStack(editor);
3955 ME_UpdateRepaint(editor, FALSE);
3956 return len;
3958 case EM_SELECTIONTYPE:
3959 return ME_GetSelectionType(editor);
3960 case EM_SETBKGNDCOLOR:
3962 LRESULT lColor;
3963 if (editor->rgbBackColor != -1) {
3964 DeleteObject(editor->hbrBackground);
3965 lColor = editor->rgbBackColor;
3967 else lColor = ITextHost_TxGetSysColor(editor->texthost, COLOR_WINDOW);
3969 if (wParam)
3971 editor->rgbBackColor = -1;
3972 editor->hbrBackground = GetSysColorBrush(COLOR_WINDOW);
3974 else
3976 editor->rgbBackColor = lParam;
3977 editor->hbrBackground = CreateSolidBrush(editor->rgbBackColor);
3979 ITextHost_TxInvalidateRect(editor->texthost, NULL, TRUE);
3980 return lColor;
3982 case EM_GETMODIFY:
3983 return editor->nModifyStep == 0 ? 0 : -1;
3984 case EM_SETMODIFY:
3986 if (wParam)
3987 editor->nModifyStep = 1;
3988 else
3989 editor->nModifyStep = 0;
3991 return 0;
3993 case EM_SETREADONLY:
3995 if (wParam)
3996 editor->styleFlags |= ES_READONLY;
3997 else
3998 editor->styleFlags &= ~ES_READONLY;
3999 return 1;
4001 case EM_SETEVENTMASK:
4003 DWORD nOldMask = editor->nEventMask;
4005 editor->nEventMask = lParam;
4006 return nOldMask;
4008 case EM_GETEVENTMASK:
4009 return editor->nEventMask;
4010 case EM_SETCHARFORMAT:
4011 return handle_EM_SETCHARFORMAT( editor, wParam, (CHARFORMAT2W *)lParam );
4012 case EM_GETCHARFORMAT:
4014 CHARFORMAT2W tmp, *dst = (CHARFORMAT2W *)lParam;
4015 if (dst->cbSize != sizeof(CHARFORMATA) &&
4016 dst->cbSize != sizeof(CHARFORMATW) &&
4017 dst->cbSize != sizeof(CHARFORMAT2A) &&
4018 dst->cbSize != sizeof(CHARFORMAT2W))
4019 return 0;
4020 tmp.cbSize = sizeof(tmp);
4021 if (!wParam)
4022 ME_GetDefaultCharFormat(editor, &tmp);
4023 else
4024 ME_GetSelectionCharFormat(editor, &tmp);
4025 cf2w_to_cfany(dst, &tmp);
4026 return tmp.dwMask;
4028 case EM_SETPARAFORMAT:
4030 BOOL result = ME_SetSelectionParaFormat(editor, (PARAFORMAT2 *)lParam);
4031 ME_WrapMarkedParagraphs(editor);
4032 ME_UpdateScrollBar(editor);
4033 ME_CommitUndo(editor);
4034 return result;
4036 case EM_GETPARAFORMAT:
4037 ME_GetSelectionParaFormat(editor, (PARAFORMAT2 *)lParam);
4038 return ((PARAFORMAT2 *)lParam)->dwMask;
4039 case EM_GETFIRSTVISIBLELINE:
4041 ME_DisplayItem *p = editor->pBuffer->pFirst;
4042 int y = editor->vert_si.nPos;
4043 int ypara = 0;
4044 int count = 0;
4045 int ystart, yend;
4046 while(p) {
4047 p = ME_FindItemFwd(p, diStartRowOrParagraphOrEnd);
4048 if (p->type == diTextEnd)
4049 break;
4050 if (p->type == diParagraph) {
4051 ypara = p->member.para.pt.y;
4052 continue;
4054 ystart = ypara + p->member.row.pt.y;
4055 yend = ystart + p->member.row.nHeight;
4056 if (y < yend) {
4057 break;
4059 count++;
4061 return count;
4063 case EM_HIDESELECTION:
4065 editor->bHideSelection = (wParam != 0);
4066 ME_InvalidateSelection(editor);
4067 return 0;
4069 case EM_LINESCROLL:
4071 if (!(editor->styleFlags & ES_MULTILINE))
4072 return FALSE;
4073 ME_ScrollDown( editor, lParam * get_default_line_height( editor ) );
4074 return TRUE;
4076 case WM_CLEAR:
4078 int from, to;
4079 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
4080 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to-from, FALSE);
4081 ME_CommitUndo(editor);
4082 ME_UpdateRepaint(editor, TRUE);
4083 return 0;
4085 case EM_REPLACESEL:
4087 int len = 0;
4088 LONG codepage = unicode ? CP_UNICODE : CP_ACP;
4089 LPWSTR wszText = ME_ToUnicode(codepage, (void *)lParam, &len);
4091 TRACE("EM_REPLACESEL - %s\n", debugstr_w(wszText));
4093 ME_ReplaceSel(editor, !!wParam, wszText, len);
4094 ME_EndToUnicode(codepage, wszText);
4095 return len;
4097 case EM_SCROLLCARET:
4098 ME_EnsureVisible(editor, &editor->pCursors[0]);
4099 return 0;
4100 case WM_SETFONT:
4102 LOGFONTW lf;
4103 CHARFORMAT2W fmt;
4104 HDC hDC;
4105 BOOL bRepaint = LOWORD(lParam);
4107 if (!wParam)
4108 wParam = (WPARAM)GetStockObject(SYSTEM_FONT);
4110 if (!GetObjectW((HGDIOBJ)wParam, sizeof(LOGFONTW), &lf))
4111 return 0;
4113 hDC = ITextHost_TxGetDC(editor->texthost);
4114 ME_CharFormatFromLogFont(hDC, &lf, &fmt);
4115 ITextHost_TxReleaseDC(editor->texthost, hDC);
4116 if (editor->mode & TM_RICHTEXT) {
4117 ME_Cursor start;
4118 ME_SetCursorToStart(editor, &start);
4119 ME_SetCharFormat(editor, &start, NULL, &fmt);
4121 ME_SetDefaultCharFormat(editor, &fmt);
4123 ME_CommitUndo(editor);
4124 ME_MarkAllForWrapping(editor);
4125 ME_WrapMarkedParagraphs(editor);
4126 ME_UpdateScrollBar(editor);
4127 if (bRepaint)
4128 ME_Repaint(editor);
4129 return 0;
4131 case WM_SETTEXT:
4133 ME_Cursor cursor;
4134 ME_SetCursorToStart(editor, &cursor);
4135 ME_InternalDeleteText(editor, &cursor, ME_GetTextLength(editor), FALSE);
4136 if (lParam)
4138 TRACE("WM_SETTEXT lParam==%lx\n",lParam);
4139 if (!strncmp((char *)lParam, "{\\rtf", 5) ||
4140 !strncmp((char *)lParam, "{\\urtf", 6))
4142 /* Undocumented: WM_SETTEXT supports RTF text */
4143 ME_StreamInRTFString(editor, 0, (char *)lParam);
4145 else
4146 ME_SetText(editor, (void*)lParam, unicode);
4148 else
4149 TRACE("WM_SETTEXT - NULL\n");
4150 ME_SetCursorToStart(editor, &cursor);
4151 ME_UpdateLinkAttribute(editor, &cursor, INT_MAX);
4152 set_selection_cursors(editor, 0, 0);
4153 editor->nModifyStep = 0;
4154 ME_CommitUndo(editor);
4155 ME_EmptyUndoStack(editor);
4156 ME_UpdateRepaint(editor, FALSE);
4157 return 1;
4159 case EM_CANPASTE:
4160 return paste_special( editor, 0, NULL, TRUE );
4161 case WM_PASTE:
4162 case WM_MBUTTONDOWN:
4163 wParam = 0;
4164 lParam = 0;
4165 /* fall through */
4166 case EM_PASTESPECIAL:
4167 paste_special( editor, wParam, (REPASTESPECIAL *)lParam, FALSE );
4168 return 0;
4169 case WM_CUT:
4170 case WM_COPY:
4171 copy_or_cut(editor, msg == WM_CUT);
4172 return 0;
4173 case WM_GETTEXTLENGTH:
4175 GETTEXTLENGTHEX how;
4177 /* CR/LF conversion required in 2.0 mode, verbatim in 1.0 mode */
4178 how.flags = GTL_CLOSE | (editor->bEmulateVersion10 ? 0 : GTL_USECRLF) | GTL_NUMCHARS;
4179 how.codepage = unicode ? CP_UNICODE : CP_ACP;
4180 return ME_GetTextLengthEx(editor, &how);
4182 case EM_GETTEXTLENGTHEX:
4183 return ME_GetTextLengthEx(editor, (GETTEXTLENGTHEX *)wParam);
4184 case WM_GETTEXT:
4186 GETTEXTEX ex;
4187 ex.cb = wParam * (unicode ? sizeof(WCHAR) : sizeof(CHAR));
4188 ex.flags = GT_USECRLF;
4189 ex.codepage = unicode ? CP_UNICODE : CP_ACP;
4190 ex.lpDefaultChar = NULL;
4191 ex.lpUsedDefChar = NULL;
4192 return ME_GetTextEx(editor, &ex, lParam);
4194 case EM_GETTEXTEX:
4195 return ME_GetTextEx(editor, (GETTEXTEX*)wParam, lParam);
4196 case EM_GETSELTEXT:
4198 int nFrom, nTo, nStartCur = ME_GetSelectionOfs(editor, &nFrom, &nTo);
4199 ME_Cursor *from = &editor->pCursors[nStartCur];
4200 return ME_GetTextRange(editor, (WCHAR *)lParam, from,
4201 nTo - nFrom, unicode);
4203 case EM_GETSCROLLPOS:
4205 POINT *point = (POINT *)lParam;
4206 point->x = editor->horz_si.nPos;
4207 point->y = editor->vert_si.nPos;
4208 /* 16-bit scaled value is returned as stored in scrollinfo */
4209 if (editor->horz_si.nMax > 0xffff)
4210 point->x = MulDiv(point->x, 0xffff, editor->horz_si.nMax);
4211 if (editor->vert_si.nMax > 0xffff)
4212 point->y = MulDiv(point->y, 0xffff, editor->vert_si.nMax);
4213 return 1;
4215 case EM_GETTEXTRANGE:
4217 TEXTRANGEW *rng = (TEXTRANGEW *)lParam;
4218 ME_Cursor start;
4219 int nStart = rng->chrg.cpMin;
4220 int nEnd = rng->chrg.cpMax;
4221 int textlength = ME_GetTextLength(editor);
4223 TRACE("EM_GETTEXTRANGE min=%d max=%d unicode=%d textlength=%d\n",
4224 rng->chrg.cpMin, rng->chrg.cpMax, unicode, textlength);
4225 if (nStart < 0) return 0;
4226 if ((nStart == 0 && nEnd == -1) || nEnd > textlength)
4227 nEnd = textlength;
4228 if (nStart >= nEnd) return 0;
4230 cursor_from_char_ofs( editor, nStart, &start );
4231 return ME_GetTextRange(editor, rng->lpstrText, &start, nEnd - nStart, unicode);
4233 case EM_GETLINE:
4235 ME_DisplayItem *run;
4236 const unsigned int nMaxChars = *(WORD *) lParam;
4237 unsigned int nCharsLeft = nMaxChars;
4238 char *dest = (char *) lParam;
4239 BOOL wroteNull = FALSE;
4241 TRACE("EM_GETLINE: row=%d, nMaxChars=%d (%s)\n", (int) wParam, nMaxChars,
4242 unicode ? "Unicode" : "Ansi");
4244 run = ME_FindRowWithNumber(editor, wParam);
4245 if (run == NULL)
4246 return 0;
4248 while (nCharsLeft && (run = ME_FindItemFwd(run, diRunOrStartRow))
4249 && run->type == diRun)
4251 WCHAR *str = get_text( &run->member.run, 0 );
4252 unsigned int nCopy;
4254 nCopy = min(nCharsLeft, run->member.run.len);
4256 if (unicode)
4257 memcpy(dest, str, nCopy * sizeof(WCHAR));
4258 else
4259 nCopy = WideCharToMultiByte(CP_ACP, 0, str, nCopy, dest,
4260 nCharsLeft, NULL, NULL);
4261 dest += nCopy * (unicode ? sizeof(WCHAR) : 1);
4262 nCharsLeft -= nCopy;
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 ME_DisplayItem *item = editor->pBuffer->pLast;
4282 int nRows = editor->total_rows;
4283 ME_DisplayItem *prev_para = NULL, *last_para = NULL;
4285 last_para = ME_FindItemBack(item, diRun);
4286 prev_para = ME_FindItemBack(last_para, diRun);
4287 assert(last_para);
4288 assert(last_para->member.run.nFlags & MERF_ENDPARA);
4289 if (editor->bEmulateVersion10 && prev_para &&
4290 last_para->member.run.nCharOfs == 0 &&
4291 prev_para->member.run.len == 1 &&
4292 *get_text( &prev_para->member.run, 0 ) == '\r')
4294 /* In 1.0 emulation, the last solitary \r at the very end of the text
4295 (if one exists) is NOT a line break.
4296 FIXME: this is an ugly hack. This should have a more regular model. */
4297 nRows--;
4300 TRACE("EM_GETLINECOUNT: nRows==%d\n", nRows);
4301 return max(1, nRows);
4303 case EM_LINEFROMCHAR:
4305 if (wParam == -1)
4306 return ME_RowNumberFromCharOfs(editor, ME_GetCursorOfs(&editor->pCursors[1]));
4307 else
4308 return ME_RowNumberFromCharOfs(editor, wParam);
4310 case EM_EXLINEFROMCHAR:
4312 if (lParam == -1)
4313 return ME_RowNumberFromCharOfs(editor, ME_GetCursorOfs(&editor->pCursors[1]));
4314 else
4315 return ME_RowNumberFromCharOfs(editor, lParam);
4317 case EM_LINEINDEX:
4319 ME_DisplayItem *item, *para;
4320 int nCharOfs;
4322 if (wParam == -1)
4323 item = ME_FindItemBack(editor->pCursors[0].pRun, diStartRow);
4324 else
4325 item = ME_FindRowWithNumber(editor, wParam);
4326 if (!item)
4327 return -1;
4328 para = ME_GetParagraph(item);
4329 item = ME_FindItemFwd(item, diRun);
4330 nCharOfs = para->member.para.nCharOfs + item->member.run.nCharOfs;
4331 TRACE("EM_LINEINDEX: nCharOfs==%d\n", nCharOfs);
4332 return nCharOfs;
4334 case EM_LINELENGTH:
4336 ME_DisplayItem *item, *item_end;
4337 int nChars = 0, nThisLineOfs = 0, nNextLineOfs = 0;
4338 ME_Cursor cursor;
4340 if (wParam > ME_GetTextLength(editor))
4341 return 0;
4342 if (wParam == -1)
4344 FIXME("EM_LINELENGTH: returning number of unselected characters on lines with selection unsupported.\n");
4345 return 0;
4347 cursor_from_char_ofs( editor, wParam, &cursor );
4348 item = ME_RowStart( cursor.pRun );
4349 nThisLineOfs = run_char_ofs( &ME_FindItemFwd( item, diRun )->member.run, 0 );
4350 item_end = ME_FindItemFwd(item, diStartRowOrParagraphOrEnd);
4351 if (item_end->type == diStartRow)
4352 nNextLineOfs = run_char_ofs( &ME_FindItemFwd( item_end, diRun )->member.run, 0 );
4353 else
4355 ME_DisplayItem *endRun = ME_FindItemBack(item_end, diRun);
4356 assert(endRun && endRun->member.run.nFlags & MERF_ENDPARA);
4357 nNextLineOfs = run_char_ofs( &endRun->member.run, 0 );
4359 nChars = nNextLineOfs - nThisLineOfs;
4360 TRACE("EM_LINELENGTH(%ld)==%d\n", wParam, nChars);
4361 return nChars;
4363 case EM_EXLIMITTEXT:
4365 if ((int)lParam < 0)
4366 return 0;
4367 if (lParam == 0)
4368 editor->nTextLimit = 65536;
4369 else
4370 editor->nTextLimit = (int) lParam;
4371 return 0;
4373 case EM_LIMITTEXT:
4375 if (wParam == 0)
4376 editor->nTextLimit = 65536;
4377 else
4378 editor->nTextLimit = (int) wParam;
4379 return 0;
4381 case EM_GETLIMITTEXT:
4383 return editor->nTextLimit;
4385 case EM_FINDTEXT:
4387 LRESULT r;
4388 if(!unicode){
4389 FINDTEXTA *ft = (FINDTEXTA *)lParam;
4390 int nChars = MultiByteToWideChar(CP_ACP, 0, ft->lpstrText, -1, NULL, 0);
4391 WCHAR *tmp;
4393 if ((tmp = heap_alloc(nChars * sizeof(*tmp))) != NULL)
4394 MultiByteToWideChar(CP_ACP, 0, ft->lpstrText, -1, tmp, nChars);
4395 r = ME_FindText(editor, wParam, &ft->chrg, tmp, NULL);
4396 heap_free(tmp);
4397 }else{
4398 FINDTEXTW *ft = (FINDTEXTW *)lParam;
4399 r = ME_FindText(editor, wParam, &ft->chrg, ft->lpstrText, NULL);
4401 return r;
4403 case EM_FINDTEXTEX:
4405 LRESULT r;
4406 if(!unicode){
4407 FINDTEXTEXA *ex = (FINDTEXTEXA *)lParam;
4408 int nChars = MultiByteToWideChar(CP_ACP, 0, ex->lpstrText, -1, NULL, 0);
4409 WCHAR *tmp;
4411 if ((tmp = heap_alloc(nChars * sizeof(*tmp))) != NULL)
4412 MultiByteToWideChar(CP_ACP, 0, ex->lpstrText, -1, tmp, nChars);
4413 r = ME_FindText(editor, wParam, &ex->chrg, tmp, &ex->chrgText);
4414 heap_free(tmp);
4415 }else{
4416 FINDTEXTEXW *ex = (FINDTEXTEXW *)lParam;
4417 r = ME_FindText(editor, wParam, &ex->chrg, ex->lpstrText, &ex->chrgText);
4419 return r;
4421 case EM_FINDTEXTW:
4423 FINDTEXTW *ft = (FINDTEXTW *)lParam;
4424 return ME_FindText(editor, wParam, &ft->chrg, ft->lpstrText, NULL);
4426 case EM_FINDTEXTEXW:
4428 FINDTEXTEXW *ex = (FINDTEXTEXW *)lParam;
4429 return ME_FindText(editor, wParam, &ex->chrg, ex->lpstrText, &ex->chrgText);
4431 case EM_GETZOOM:
4432 if (!wParam || !lParam)
4433 return FALSE;
4434 *(int *)wParam = editor->nZoomNumerator;
4435 *(int *)lParam = editor->nZoomDenominator;
4436 return TRUE;
4437 case EM_SETZOOM:
4438 return ME_SetZoom(editor, wParam, lParam);
4439 case EM_CHARFROMPOS:
4441 ME_Cursor cursor;
4442 if (ME_CharFromPos(editor, ((POINTL *)lParam)->x, ((POINTL *)lParam)->y,
4443 &cursor, NULL))
4444 return ME_GetCursorOfs(&cursor);
4445 else
4446 return -1;
4448 case EM_POSFROMCHAR:
4450 ME_Cursor cursor;
4451 int nCharOfs, nLength;
4452 POINTL pt = {0,0};
4454 nCharOfs = wParam;
4455 /* detect which API version we're dealing with */
4456 if (wParam >= 0x40000)
4457 nCharOfs = lParam;
4458 nLength = ME_GetTextLength(editor);
4459 nCharOfs = min(nCharOfs, nLength);
4460 nCharOfs = max(nCharOfs, 0);
4462 cursor_from_char_ofs( editor, nCharOfs, &cursor );
4463 pt.y = cursor.pRun->member.run.pt.y;
4464 pt.x = cursor.pRun->member.run.pt.x +
4465 ME_PointFromChar( editor, &cursor.pRun->member.run, cursor.nOffset, TRUE );
4466 pt.y += cursor.pPara->member.para.pt.y + editor->rcFormat.top;
4467 pt.x += editor->rcFormat.left;
4469 pt.x -= editor->horz_si.nPos;
4470 pt.y -= editor->vert_si.nPos;
4472 if (wParam >= 0x40000) {
4473 *(POINTL *)wParam = pt;
4475 return (wParam >= 0x40000) ? 0 : MAKELONG( pt.x, pt.y );
4477 case WM_CREATE:
4478 return ME_WmCreate(editor, lParam, unicode);
4479 case WM_DESTROY:
4480 ME_DestroyEditor(editor);
4481 return 0;
4482 case WM_SETCURSOR:
4484 POINT cursor_pos;
4485 if (wParam == (WPARAM)editor->hWnd && GetCursorPos(&cursor_pos) &&
4486 ScreenToClient(editor->hWnd, &cursor_pos))
4487 ME_LinkNotify(editor, msg, 0, MAKELPARAM(cursor_pos.x, cursor_pos.y));
4488 return ME_SetCursor(editor);
4490 case WM_LBUTTONDBLCLK:
4491 case WM_LBUTTONDOWN:
4493 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4494 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4495 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4496 return 0;
4497 ITextHost_TxSetFocus(editor->texthost);
4498 ME_LButtonDown(editor, (short)LOWORD(lParam), (short)HIWORD(lParam),
4499 ME_CalculateClickCount(editor, msg, wParam, lParam));
4500 ITextHost_TxSetCapture(editor->texthost, TRUE);
4501 editor->bMouseCaptured = TRUE;
4502 ME_LinkNotify(editor, msg, wParam, lParam);
4503 if (!ME_SetCursor(editor)) goto do_default;
4504 break;
4506 case WM_MOUSEMOVE:
4507 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4508 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4509 return 0;
4510 if (editor->bMouseCaptured)
4511 ME_MouseMove(editor, (short)LOWORD(lParam), (short)HIWORD(lParam));
4512 else
4513 ME_LinkNotify(editor, msg, wParam, lParam);
4514 /* Set cursor if mouse is captured, since WM_SETCURSOR won't be received. */
4515 if (editor->bMouseCaptured)
4516 ME_SetCursor(editor);
4517 break;
4518 case WM_LBUTTONUP:
4519 if (editor->bMouseCaptured) {
4520 ITextHost_TxSetCapture(editor->texthost, FALSE);
4521 editor->bMouseCaptured = FALSE;
4523 if (editor->nSelectionType == stDocument)
4524 editor->nSelectionType = stPosition;
4525 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4526 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4527 return 0;
4528 else
4530 ME_SetCursor(editor);
4531 ME_LinkNotify(editor, msg, wParam, lParam);
4533 break;
4534 case WM_RBUTTONUP:
4535 case WM_RBUTTONDOWN:
4536 case WM_RBUTTONDBLCLK:
4537 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4538 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4539 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4540 return 0;
4541 ME_LinkNotify(editor, msg, wParam, lParam);
4542 goto do_default;
4543 case WM_CONTEXTMENU:
4544 if (!ME_ShowContextMenu(editor, (short)LOWORD(lParam), (short)HIWORD(lParam)))
4545 goto do_default;
4546 break;
4547 case WM_SETFOCUS:
4548 editor->bHaveFocus = TRUE;
4549 create_caret(editor);
4550 update_caret(editor);
4551 ME_SendOldNotify(editor, EN_SETFOCUS);
4552 if (!editor->bHideSelection && !(editor->styleFlags & ES_NOHIDESEL))
4553 ME_InvalidateSelection( editor );
4554 return 0;
4555 case WM_KILLFOCUS:
4556 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4557 editor->bHaveFocus = FALSE;
4558 editor->wheel_remain = 0;
4559 hide_caret(editor);
4560 DestroyCaret();
4561 ME_SendOldNotify(editor, EN_KILLFOCUS);
4562 if (!editor->bHideSelection && !(editor->styleFlags & ES_NOHIDESEL))
4563 ME_InvalidateSelection( editor );
4564 return 0;
4565 case WM_COMMAND:
4566 TRACE("editor wnd command = %d\n", LOWORD(wParam));
4567 return 0;
4568 case WM_KEYUP:
4569 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4570 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4571 return 0;
4572 goto do_default;
4573 case WM_KEYDOWN:
4574 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4575 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4576 return 0;
4577 if (ME_KeyDown(editor, LOWORD(wParam)))
4578 return 0;
4579 goto do_default;
4580 case WM_CHAR:
4581 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4582 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4583 return 0;
4584 return ME_Char(editor, wParam, lParam, unicode);
4585 case WM_UNICHAR:
4586 if (unicode)
4588 if(wParam == UNICODE_NOCHAR) return TRUE;
4589 if(wParam <= 0x000fffff)
4591 if(wParam > 0xffff) /* convert to surrogates */
4593 wParam -= 0x10000;
4594 ME_Char(editor, (wParam >> 10) + 0xd800, 0, TRUE);
4595 ME_Char(editor, (wParam & 0x03ff) + 0xdc00, 0, TRUE);
4596 } else {
4597 ME_Char(editor, wParam, 0, TRUE);
4600 return 0;
4602 break;
4603 case EM_STOPGROUPTYPING:
4604 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4605 return 0;
4606 case WM_HSCROLL:
4608 const int scrollUnit = 7;
4610 switch(LOWORD(wParam))
4612 case SB_LEFT:
4613 ME_ScrollAbs(editor, 0, 0);
4614 break;
4615 case SB_RIGHT:
4616 ME_ScrollAbs(editor,
4617 editor->horz_si.nMax - (int)editor->horz_si.nPage,
4618 editor->vert_si.nMax - (int)editor->vert_si.nPage);
4619 break;
4620 case SB_LINELEFT:
4621 ME_ScrollLeft(editor, scrollUnit);
4622 break;
4623 case SB_LINERIGHT:
4624 ME_ScrollRight(editor, scrollUnit);
4625 break;
4626 case SB_PAGELEFT:
4627 ME_ScrollLeft(editor, editor->sizeWindow.cx);
4628 break;
4629 case SB_PAGERIGHT:
4630 ME_ScrollRight(editor, editor->sizeWindow.cx);
4631 break;
4632 case SB_THUMBTRACK:
4633 case SB_THUMBPOSITION:
4635 int pos = HIWORD(wParam);
4636 if (editor->horz_si.nMax > 0xffff)
4637 pos = MulDiv(pos, editor->horz_si.nMax, 0xffff);
4638 ME_HScrollAbs(editor, pos);
4639 break;
4642 break;
4644 case EM_SCROLL: /* fall through */
4645 case WM_VSCROLL:
4647 int origNPos;
4648 int lineHeight = get_default_line_height( editor );
4650 origNPos = editor->vert_si.nPos;
4652 switch(LOWORD(wParam))
4654 case SB_TOP:
4655 ME_ScrollAbs(editor, 0, 0);
4656 break;
4657 case SB_BOTTOM:
4658 ME_ScrollAbs(editor,
4659 editor->horz_si.nMax - (int)editor->horz_si.nPage,
4660 editor->vert_si.nMax - (int)editor->vert_si.nPage);
4661 break;
4662 case SB_LINEUP:
4663 ME_ScrollUp(editor,lineHeight);
4664 break;
4665 case SB_LINEDOWN:
4666 ME_ScrollDown(editor,lineHeight);
4667 break;
4668 case SB_PAGEUP:
4669 ME_ScrollUp(editor,editor->sizeWindow.cy);
4670 break;
4671 case SB_PAGEDOWN:
4672 ME_ScrollDown(editor,editor->sizeWindow.cy);
4673 break;
4674 case SB_THUMBTRACK:
4675 case SB_THUMBPOSITION:
4677 int pos = HIWORD(wParam);
4678 if (editor->vert_si.nMax > 0xffff)
4679 pos = MulDiv(pos, editor->vert_si.nMax, 0xffff);
4680 ME_VScrollAbs(editor, pos);
4681 break;
4684 if (msg == EM_SCROLL)
4685 return 0x00010000 | (((editor->vert_si.nPos - origNPos)/lineHeight) & 0xffff);
4686 break;
4688 case WM_MOUSEWHEEL:
4690 int delta;
4691 BOOL ctrl_is_down;
4693 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4694 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4695 return 0;
4697 ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
4699 delta = GET_WHEEL_DELTA_WPARAM(wParam);
4701 /* if scrolling changes direction, ignore left overs */
4702 if ((delta < 0 && editor->wheel_remain < 0) ||
4703 (delta > 0 && editor->wheel_remain > 0))
4704 editor->wheel_remain += delta;
4705 else
4706 editor->wheel_remain = delta;
4708 if (editor->wheel_remain)
4710 if (ctrl_is_down) {
4711 int numerator;
4712 if (!editor->nZoomNumerator || !editor->nZoomDenominator)
4714 numerator = 100;
4715 } else {
4716 numerator = editor->nZoomNumerator * 100 / editor->nZoomDenominator;
4718 numerator += calc_wheel_change( &editor->wheel_remain, 10 );
4719 if (numerator >= 10 && numerator <= 500)
4720 ME_SetZoom(editor, numerator, 100);
4721 } else {
4722 UINT max_lines = 3;
4723 int lines = 0;
4725 SystemParametersInfoW( SPI_GETWHEELSCROLLLINES, 0, &max_lines, 0 );
4726 if (max_lines)
4727 lines = calc_wheel_change( &editor->wheel_remain, (int)max_lines );
4728 if (lines)
4729 ME_ScrollDown( editor, -lines * get_default_line_height( editor ) );
4732 break;
4734 case EM_GETRECT:
4736 *((RECT *)lParam) = editor->rcFormat;
4737 if (editor->bDefaultFormatRect)
4738 ((RECT *)lParam)->left -= editor->selofs;
4739 return 0;
4741 case EM_SETRECT:
4742 case EM_SETRECTNP:
4744 if (lParam)
4746 int border = 0;
4747 RECT clientRect;
4748 RECT *rc = (RECT *)lParam;
4750 border = editor->exStyleFlags & WS_EX_CLIENTEDGE ? 1 : 0;
4751 ITextHost_TxGetClientRect(editor->texthost, &clientRect);
4752 if (wParam == 0)
4754 editor->rcFormat.top = max(0, rc->top - border);
4755 editor->rcFormat.left = max(0, rc->left - border);
4756 editor->rcFormat.bottom = min(clientRect.bottom, rc->bottom);
4757 editor->rcFormat.right = min(clientRect.right, rc->right + border);
4758 } else if (wParam == 1) {
4759 /* MSDN incorrectly says a wParam value of 1 causes the
4760 * lParam rect to be used as a relative offset,
4761 * however, the tests show it just prevents min/max bound
4762 * checking. */
4763 editor->rcFormat.top = rc->top - border;
4764 editor->rcFormat.left = rc->left - border;
4765 editor->rcFormat.bottom = rc->bottom;
4766 editor->rcFormat.right = rc->right + border;
4767 } else {
4768 return 0;
4770 editor->bDefaultFormatRect = FALSE;
4772 else
4774 ME_SetDefaultFormatRect(editor);
4775 editor->bDefaultFormatRect = TRUE;
4777 ME_MarkAllForWrapping(editor);
4778 ME_WrapMarkedParagraphs(editor);
4779 ME_UpdateScrollBar(editor);
4780 if (msg != EM_SETRECTNP)
4781 ME_Repaint(editor);
4782 return 0;
4784 case EM_REQUESTRESIZE:
4785 ME_SendRequestResize(editor, TRUE);
4786 return 0;
4787 case WM_SETREDRAW:
4788 goto do_default;
4789 case WM_WINDOWPOSCHANGED:
4791 RECT clientRect;
4792 WINDOWPOS *winpos = (WINDOWPOS *)lParam;
4794 if (winpos->flags & SWP_NOCLIENTSIZE) goto do_default;
4795 ITextHost_TxGetClientRect(editor->texthost, &clientRect);
4796 if (editor->bDefaultFormatRect) {
4797 ME_SetDefaultFormatRect(editor);
4798 } else {
4799 editor->rcFormat.right += clientRect.right - editor->prevClientRect.right;
4800 editor->rcFormat.bottom += clientRect.bottom - editor->prevClientRect.bottom;
4802 editor->prevClientRect = clientRect;
4803 ME_RewrapRepaint(editor);
4804 goto do_default;
4806 /* IME messages to make richedit controls IME aware */
4807 case WM_IME_SETCONTEXT:
4808 case WM_IME_CONTROL:
4809 case WM_IME_SELECT:
4810 case WM_IME_COMPOSITIONFULL:
4811 return 0;
4812 case WM_IME_STARTCOMPOSITION:
4814 editor->imeStartIndex=ME_GetCursorOfs(&editor->pCursors[0]);
4815 ME_DeleteSelection(editor);
4816 ME_CommitUndo(editor);
4817 ME_UpdateRepaint(editor, FALSE);
4818 return 0;
4820 case WM_IME_COMPOSITION:
4822 HIMC hIMC;
4824 ME_Style *style = style_get_insert_style( editor, editor->pCursors );
4825 hIMC = ITextHost_TxImmGetContext(editor->texthost);
4826 ME_DeleteSelection(editor);
4827 ME_SaveTempStyle(editor, style);
4828 if (lParam & (GCS_RESULTSTR|GCS_COMPSTR))
4830 LPWSTR lpCompStr = NULL;
4831 DWORD dwBufLen;
4832 DWORD dwIndex = lParam & GCS_RESULTSTR;
4833 if (!dwIndex)
4834 dwIndex = GCS_COMPSTR;
4836 dwBufLen = ImmGetCompositionStringW(hIMC, dwIndex, NULL, 0);
4837 lpCompStr = HeapAlloc(GetProcessHeap(),0,dwBufLen + sizeof(WCHAR));
4838 ImmGetCompositionStringW(hIMC, dwIndex, lpCompStr, dwBufLen);
4839 lpCompStr[dwBufLen/sizeof(WCHAR)] = 0;
4840 ME_InsertTextFromCursor(editor,0,lpCompStr,dwBufLen/sizeof(WCHAR),style);
4841 HeapFree(GetProcessHeap(), 0, lpCompStr);
4843 if (dwIndex == GCS_COMPSTR)
4844 set_selection_cursors(editor,editor->imeStartIndex,
4845 editor->imeStartIndex + dwBufLen/sizeof(WCHAR));
4847 ME_ReleaseStyle(style);
4848 ME_CommitUndo(editor);
4849 ME_UpdateRepaint(editor, FALSE);
4850 return 0;
4852 case WM_IME_ENDCOMPOSITION:
4854 ME_DeleteSelection(editor);
4855 editor->imeStartIndex=-1;
4856 return 0;
4858 case EM_GETOLEINTERFACE:
4860 if (!editor->reOle)
4861 if (!CreateIRichEditOle(NULL, editor, (LPVOID *)&editor->reOle))
4862 return 0;
4863 if (IUnknown_QueryInterface(editor->reOle, &IID_IRichEditOle, (LPVOID *)lParam) == S_OK)
4864 return 1;
4865 return 0;
4867 case EM_GETPASSWORDCHAR:
4869 return editor->cPasswordMask;
4871 case EM_SETOLECALLBACK:
4872 if(editor->lpOleCallback)
4873 IRichEditOleCallback_Release(editor->lpOleCallback);
4874 editor->lpOleCallback = (IRichEditOleCallback*)lParam;
4875 if(editor->lpOleCallback)
4876 IRichEditOleCallback_AddRef(editor->lpOleCallback);
4877 return TRUE;
4878 case EM_GETWORDBREAKPROC:
4879 return (LRESULT)editor->pfnWordBreak;
4880 case EM_SETWORDBREAKPROC:
4882 EDITWORDBREAKPROCW pfnOld = editor->pfnWordBreak;
4884 editor->pfnWordBreak = (EDITWORDBREAKPROCW)lParam;
4885 return (LRESULT)pfnOld;
4887 case EM_GETTEXTMODE:
4888 return editor->mode;
4889 case EM_SETTEXTMODE:
4891 int mask = 0;
4892 int changes = 0;
4894 if (ME_GetTextLength(editor) ||
4895 !list_empty( &editor->undo_stack ) || !list_empty( &editor->redo_stack ))
4896 return E_UNEXPECTED;
4898 /* Check for mutually exclusive flags in adjacent bits of wParam */
4899 if ((wParam & (TM_RICHTEXT | TM_MULTILEVELUNDO | TM_MULTICODEPAGE)) &
4900 (wParam & (TM_PLAINTEXT | TM_SINGLELEVELUNDO | TM_SINGLECODEPAGE)) << 1)
4901 return E_INVALIDARG;
4903 if (wParam & (TM_RICHTEXT | TM_PLAINTEXT))
4905 mask |= TM_RICHTEXT | TM_PLAINTEXT;
4906 changes |= wParam & (TM_RICHTEXT | TM_PLAINTEXT);
4907 if (wParam & TM_PLAINTEXT) {
4908 /* Clear selection since it should be possible to select the
4909 * end of text run for rich text */
4910 ME_InvalidateSelection(editor);
4911 ME_SetCursorToStart(editor, &editor->pCursors[0]);
4912 editor->pCursors[1] = editor->pCursors[0];
4913 /* plain text can only have the default style. */
4914 ME_ClearTempStyle(editor);
4915 ME_AddRefStyle(editor->pBuffer->pDefaultStyle);
4916 ME_ReleaseStyle(editor->pCursors[0].pRun->member.run.style);
4917 editor->pCursors[0].pRun->member.run.style = editor->pBuffer->pDefaultStyle;
4920 /* FIXME: Currently no support for undo level and code page options */
4921 editor->mode = (editor->mode & ~mask) | changes;
4922 return 0;
4924 case EM_SETPASSWORDCHAR:
4926 editor->cPasswordMask = wParam;
4927 ME_RewrapRepaint(editor);
4928 return 0;
4930 case EM_SETTARGETDEVICE:
4931 if (wParam == 0)
4933 BOOL new = (lParam == 0 && (editor->styleFlags & ES_MULTILINE));
4934 if (editor->nAvailWidth || editor->bWordWrap != new)
4936 editor->bWordWrap = new;
4937 editor->nAvailWidth = 0; /* wrap to client area */
4938 ME_RewrapRepaint(editor);
4940 } else {
4941 int width = max(0, lParam);
4942 if ((editor->styleFlags & ES_MULTILINE) &&
4943 (!editor->bWordWrap || editor->nAvailWidth != width))
4945 editor->nAvailWidth = width;
4946 editor->bWordWrap = TRUE;
4947 ME_RewrapRepaint(editor);
4949 FIXME("EM_SETTARGETDEVICE doesn't use non-NULL target devices\n");
4951 return TRUE;
4952 default:
4953 do_default:
4954 *phresult = S_FALSE;
4955 break;
4957 return 0L;
4960 static BOOL create_windowed_editor(HWND hwnd, CREATESTRUCTW *create, BOOL emulate_10)
4962 ITextHost *host = ME_CreateTextHost( hwnd, create, emulate_10 );
4963 ME_TextEditor *editor;
4965 if (!host) return FALSE;
4967 editor = ME_MakeEditor( host, emulate_10 );
4968 if (!editor)
4970 ITextHost_Release( host );
4971 return FALSE;
4974 editor->exStyleFlags = GetWindowLongW( hwnd, GWL_EXSTYLE );
4975 editor->styleFlags |= GetWindowLongW( hwnd, GWL_STYLE ) & ES_WANTRETURN;
4976 editor->hWnd = hwnd; /* FIXME: Remove editor's dependence on hWnd */
4977 editor->hwndParent = create->hwndParent;
4979 SetWindowLongPtrW( hwnd, 0, (LONG_PTR)editor );
4981 return TRUE;
4984 static LRESULT RichEditWndProc_common(HWND hWnd, UINT msg, WPARAM wParam,
4985 LPARAM lParam, BOOL unicode)
4987 ME_TextEditor *editor;
4988 HRESULT hresult;
4989 LRESULT lresult = 0;
4991 TRACE("enter hwnd %p msg %04x (%s) %lx %lx, unicode %d\n",
4992 hWnd, msg, get_msg_name(msg), wParam, lParam, unicode);
4994 editor = (ME_TextEditor *)GetWindowLongPtrW(hWnd, 0);
4995 if (!editor)
4997 if (msg == WM_NCCREATE)
4999 CREATESTRUCTW *pcs = (CREATESTRUCTW *)lParam;
5001 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd, pcs->style);
5002 return create_windowed_editor( hWnd, pcs, FALSE );
5004 else
5006 return DefWindowProcW(hWnd, msg, wParam, lParam);
5010 switch (msg)
5012 case WM_PAINT:
5014 HDC hdc;
5015 RECT rc;
5016 PAINTSTRUCT ps;
5017 HBRUSH old_brush;
5019 update_caret(editor);
5020 hdc = BeginPaint(editor->hWnd, &ps);
5021 if (!editor->bEmulateVersion10 || (editor->nEventMask & ENM_UPDATE))
5022 ME_SendOldNotify(editor, EN_UPDATE);
5023 old_brush = SelectObject(hdc, editor->hbrBackground);
5025 /* Erase area outside of the formatting rectangle */
5026 if (ps.rcPaint.top < editor->rcFormat.top)
5028 rc = ps.rcPaint;
5029 rc.bottom = editor->rcFormat.top;
5030 PatBlt(hdc, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, PATCOPY);
5031 ps.rcPaint.top = editor->rcFormat.top;
5033 if (ps.rcPaint.bottom > editor->rcFormat.bottom) {
5034 rc = ps.rcPaint;
5035 rc.top = editor->rcFormat.bottom;
5036 PatBlt(hdc, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, PATCOPY);
5037 ps.rcPaint.bottom = editor->rcFormat.bottom;
5039 if (ps.rcPaint.left < editor->rcFormat.left) {
5040 rc = ps.rcPaint;
5041 rc.right = editor->rcFormat.left;
5042 PatBlt(hdc, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, PATCOPY);
5043 ps.rcPaint.left = editor->rcFormat.left;
5045 if (ps.rcPaint.right > editor->rcFormat.right) {
5046 rc = ps.rcPaint;
5047 rc.left = editor->rcFormat.right;
5048 PatBlt(hdc, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, PATCOPY);
5049 ps.rcPaint.right = editor->rcFormat.right;
5052 ME_PaintContent(editor, hdc, &ps.rcPaint);
5053 SelectObject(hdc, old_brush);
5054 EndPaint(editor->hWnd, &ps);
5055 return 0;
5057 case WM_ERASEBKGND:
5059 HDC hDC = (HDC)wParam;
5060 RECT rc;
5062 if (GetUpdateRect(editor->hWnd, &rc, TRUE))
5063 FillRect(hDC, &rc, editor->hbrBackground);
5064 return 1;
5066 case EM_SETOPTIONS:
5068 DWORD dwStyle;
5069 const DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
5070 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN |
5071 ECO_SELECTIONBAR;
5072 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
5073 dwStyle = GetWindowLongW(hWnd, GWL_STYLE);
5074 dwStyle = (dwStyle & ~mask) | (lresult & mask);
5075 SetWindowLongW(hWnd, GWL_STYLE, dwStyle);
5076 return lresult;
5078 case EM_SETREADONLY:
5080 DWORD dwStyle;
5081 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
5082 dwStyle = GetWindowLongW(hWnd, GWL_STYLE);
5083 dwStyle &= ~ES_READONLY;
5084 if (wParam)
5085 dwStyle |= ES_READONLY;
5086 SetWindowLongW(hWnd, GWL_STYLE, dwStyle);
5087 return lresult;
5089 default:
5090 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
5093 if (hresult == S_FALSE)
5094 lresult = DefWindowProcW(hWnd, msg, wParam, lParam);
5096 TRACE("exit hwnd %p msg %04x (%s) %lx %lx, unicode %d -> %lu\n",
5097 hWnd, msg, get_msg_name(msg), wParam, lParam, unicode, lresult);
5099 return lresult;
5102 static LRESULT WINAPI RichEditWndProcW(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5104 BOOL unicode = TRUE;
5106 /* Under Win9x RichEdit20W returns ANSI strings, see the tests. */
5107 if (msg == WM_GETTEXT && (GetVersion() & 0x80000000))
5108 unicode = FALSE;
5110 return RichEditWndProc_common(hWnd, msg, wParam, lParam, unicode);
5113 static LRESULT WINAPI RichEditWndProcA(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5115 return RichEditWndProc_common(hWnd, msg, wParam, lParam, FALSE);
5118 /******************************************************************
5119 * RichEditANSIWndProc (RICHED20.10)
5121 LRESULT WINAPI RichEditANSIWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5123 return RichEditWndProcA(hWnd, msg, wParam, lParam);
5126 /******************************************************************
5127 * RichEdit10ANSIWndProc (RICHED20.9)
5129 LRESULT WINAPI RichEdit10ANSIWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5131 if (msg == WM_NCCREATE && !GetWindowLongPtrW(hWnd, 0))
5133 CREATESTRUCTW *pcs = (CREATESTRUCTW *)lParam;
5135 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd, pcs->style);
5136 return create_windowed_editor( hWnd, pcs, TRUE );
5138 return RichEditANSIWndProc(hWnd, msg, wParam, lParam);
5141 void ME_SendOldNotify(ME_TextEditor *editor, int nCode)
5143 ITextHost_TxNotify(editor->texthost, nCode, NULL);
5146 /* Fill buffer with srcChars unicode characters from the start cursor.
5148 * buffer: destination buffer
5149 * buflen: length of buffer in characters excluding the NULL terminator.
5150 * start: start of editor text to copy into buffer.
5151 * srcChars: Number of characters to use from the editor text.
5152 * bCRLF: if true, replaces all end of lines with \r\n pairs.
5154 * returns the number of characters written excluding the NULL terminator.
5156 * The written text is always NULL terminated.
5158 int ME_GetTextW(ME_TextEditor *editor, WCHAR *buffer, int buflen,
5159 const ME_Cursor *start, int srcChars, BOOL bCRLF,
5160 BOOL bEOP)
5162 ME_DisplayItem *pRun, *pNextRun;
5163 const WCHAR *pStart = buffer;
5164 const WCHAR cr_lf[] = {'\r', '\n', 0};
5165 const WCHAR *str;
5166 int nLen;
5168 /* bCRLF flag is only honored in 2.0 and up. 1.0 must always return text verbatim */
5169 if (editor->bEmulateVersion10) bCRLF = FALSE;
5171 pRun = start->pRun;
5172 assert(pRun);
5173 pNextRun = ME_FindItemFwd(pRun, diRun);
5175 nLen = pRun->member.run.len - start->nOffset;
5176 str = get_text( &pRun->member.run, start->nOffset );
5178 while (srcChars && buflen && pNextRun)
5180 int nFlags = pRun->member.run.nFlags;
5182 if (bCRLF && nFlags & MERF_ENDPARA && ~nFlags & MERF_ENDCELL)
5184 if (buflen == 1) break;
5185 /* FIXME: native fails to reduce srcChars here for WM_GETTEXT or
5186 * EM_GETTEXTEX, however, this is done for copying text which
5187 * also uses this function. */
5188 srcChars -= min(nLen, srcChars);
5189 nLen = 2;
5190 str = cr_lf;
5191 } else {
5192 nLen = min(nLen, srcChars);
5193 srcChars -= nLen;
5196 nLen = min(nLen, buflen);
5197 buflen -= nLen;
5199 CopyMemory(buffer, str, sizeof(WCHAR) * nLen);
5201 buffer += nLen;
5203 pRun = pNextRun;
5204 pNextRun = ME_FindItemFwd(pRun, diRun);
5206 nLen = pRun->member.run.len;
5207 str = get_text( &pRun->member.run, 0 );
5209 /* append '\r' to the last paragraph. */
5210 if (pRun->next->type == diTextEnd && bEOP)
5212 *buffer = '\r';
5213 buffer ++;
5215 *buffer = 0;
5216 return buffer - pStart;
5219 static BOOL ME_RegisterEditorClass(HINSTANCE hInstance)
5221 WNDCLASSW wcW;
5222 WNDCLASSA wcA;
5224 wcW.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS;
5225 wcW.lpfnWndProc = RichEditWndProcW;
5226 wcW.cbClsExtra = 0;
5227 wcW.cbWndExtra = sizeof(ME_TextEditor *);
5228 wcW.hInstance = NULL; /* hInstance would register DLL-local class */
5229 wcW.hIcon = NULL;
5230 wcW.hCursor = LoadCursorW(NULL, (LPWSTR)IDC_IBEAM);
5231 wcW.hbrBackground = GetStockObject(NULL_BRUSH);
5232 wcW.lpszMenuName = NULL;
5234 if (is_version_nt())
5236 wcW.lpszClassName = RICHEDIT_CLASS20W;
5237 if (!RegisterClassW(&wcW)) return FALSE;
5238 wcW.lpszClassName = MSFTEDIT_CLASS;
5239 if (!RegisterClassW(&wcW)) return FALSE;
5241 else
5243 /* WNDCLASSA/W have the same layout */
5244 wcW.lpszClassName = (LPCWSTR)"RichEdit20W";
5245 if (!RegisterClassA((WNDCLASSA *)&wcW)) return FALSE;
5246 wcW.lpszClassName = (LPCWSTR)"RichEdit50W";
5247 if (!RegisterClassA((WNDCLASSA *)&wcW)) return FALSE;
5250 wcA.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS;
5251 wcA.lpfnWndProc = RichEditWndProcA;
5252 wcA.cbClsExtra = 0;
5253 wcA.cbWndExtra = sizeof(ME_TextEditor *);
5254 wcA.hInstance = NULL; /* hInstance would register DLL-local class */
5255 wcA.hIcon = NULL;
5256 wcA.hCursor = LoadCursorW(NULL, (LPWSTR)IDC_IBEAM);
5257 wcA.hbrBackground = GetStockObject(NULL_BRUSH);
5258 wcA.lpszMenuName = NULL;
5259 wcA.lpszClassName = RICHEDIT_CLASS20A;
5260 if (!RegisterClassA(&wcA)) return FALSE;
5261 wcA.lpszClassName = "RichEdit50A";
5262 if (!RegisterClassA(&wcA)) return FALSE;
5264 return TRUE;
5267 static LRESULT WINAPI REComboWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
5268 /* FIXME: Not implemented */
5269 TRACE("hWnd %p msg %04x (%s) %08lx %08lx\n",
5270 hWnd, msg, get_msg_name(msg), wParam, lParam);
5271 return DefWindowProcW(hWnd, msg, wParam, lParam);
5274 static LRESULT WINAPI REListWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
5275 /* FIXME: Not implemented */
5276 TRACE("hWnd %p msg %04x (%s) %08lx %08lx\n",
5277 hWnd, msg, get_msg_name(msg), wParam, lParam);
5278 return DefWindowProcW(hWnd, msg, wParam, lParam);
5281 /******************************************************************
5282 * REExtendedRegisterClass (RICHED20.8)
5284 * FIXME undocumented
5285 * Need to check for errors and implement controls and callbacks
5287 LRESULT WINAPI REExtendedRegisterClass(void)
5289 WNDCLASSW wcW;
5290 UINT result;
5292 FIXME("semi stub\n");
5294 wcW.cbClsExtra = 0;
5295 wcW.cbWndExtra = 4;
5296 wcW.hInstance = NULL;
5297 wcW.hIcon = NULL;
5298 wcW.hCursor = NULL;
5299 wcW.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
5300 wcW.lpszMenuName = NULL;
5302 if (!ME_ListBoxRegistered)
5304 wcW.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS;
5305 wcW.lpfnWndProc = REListWndProc;
5306 wcW.lpszClassName = REListBox20W;
5307 if (RegisterClassW(&wcW)) ME_ListBoxRegistered = TRUE;
5310 if (!ME_ComboBoxRegistered)
5312 wcW.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS | CS_VREDRAW | CS_HREDRAW;
5313 wcW.lpfnWndProc = REComboWndProc;
5314 wcW.lpszClassName = REComboBox20W;
5315 if (RegisterClassW(&wcW)) ME_ComboBoxRegistered = TRUE;
5318 result = 0;
5319 if (ME_ListBoxRegistered)
5320 result += 1;
5321 if (ME_ComboBoxRegistered)
5322 result += 2;
5324 return result;
5327 static int __cdecl wchar_comp( const void *key, const void *elem )
5329 return *(const WCHAR *)key - *(const WCHAR *)elem;
5332 /* neutral characters end the url if the next non-neutral character is a space character,
5333 otherwise they are included in the url. */
5334 static BOOL isurlneutral( WCHAR c )
5336 /* NB this list is sorted */
5337 static const WCHAR neutral_chars[] = {'!','\"','\'','(',')',',','-','.',':',';','<','>','?','[',']','{','}'};
5339 /* Some shortcuts */
5340 if (isalnum( c )) return FALSE;
5341 if (c > neutral_chars[ARRAY_SIZE( neutral_chars ) - 1]) return FALSE;
5343 return !!bsearch( &c, neutral_chars, ARRAY_SIZE( neutral_chars ), sizeof(c), wchar_comp );
5347 * This proc takes a selection, and scans it forward in order to select the span
5348 * of a possible URL candidate. A possible URL candidate must start with isalnum
5349 * or one of the following special characters: *|/\+%#@ and must consist entirely
5350 * of the characters allowed to start the URL, plus : (colon) which may occur
5351 * at most once, and not at either end.
5353 static BOOL ME_FindNextURLCandidate(ME_TextEditor *editor,
5354 const ME_Cursor *start,
5355 int nChars,
5356 ME_Cursor *candidate_min,
5357 ME_Cursor *candidate_max)
5359 ME_Cursor cursor = *start, neutral_end, space_end;
5360 BOOL candidateStarted = FALSE, quoted = FALSE;
5361 WCHAR c;
5363 while (nChars > 0)
5365 WCHAR *str = get_text( &cursor.pRun->member.run, 0 );
5366 int run_len = cursor.pRun->member.run.len;
5368 nChars -= run_len - cursor.nOffset;
5370 /* Find start of candidate */
5371 if (!candidateStarted)
5373 while (cursor.nOffset < run_len)
5375 c = str[cursor.nOffset];
5376 if (!iswspace( c ) && !isurlneutral( c ))
5378 *candidate_min = cursor;
5379 candidateStarted = TRUE;
5380 neutral_end.pPara = NULL;
5381 space_end.pPara = NULL;
5382 cursor.nOffset++;
5383 break;
5385 quoted = (c == '<');
5386 cursor.nOffset++;
5390 /* Find end of candidate */
5391 if (candidateStarted)
5393 while (cursor.nOffset < run_len)
5395 c = str[cursor.nOffset];
5396 if (iswspace( c ))
5398 if (quoted && c != '\r')
5400 if (!space_end.pPara)
5402 if (neutral_end.pPara)
5403 space_end = neutral_end;
5404 else
5405 space_end = cursor;
5408 else
5409 goto done;
5411 else if (isurlneutral( c ))
5413 if (quoted && c == '>')
5415 neutral_end.pPara = NULL;
5416 space_end.pPara = NULL;
5417 goto done;
5419 if (!neutral_end.pPara)
5420 neutral_end = cursor;
5422 else
5423 neutral_end.pPara = NULL;
5425 cursor.nOffset++;
5429 cursor.nOffset = 0;
5430 if (!ME_NextRun(&cursor.pPara, &cursor.pRun, TRUE))
5431 goto done;
5434 done:
5435 if (candidateStarted)
5437 if (space_end.pPara)
5438 *candidate_max = space_end;
5439 else if (neutral_end.pPara)
5440 *candidate_max = neutral_end;
5441 else
5442 *candidate_max = cursor;
5443 return TRUE;
5445 *candidate_max = *candidate_min = cursor;
5446 return FALSE;
5450 * This proc evaluates the selection and returns TRUE if it can be considered an URL
5452 static BOOL ME_IsCandidateAnURL(ME_TextEditor *editor, const ME_Cursor *start, int nChars)
5454 #define MAX_PREFIX_LEN 9
5455 struct prefix_s {
5456 const WCHAR text[MAX_PREFIX_LEN];
5457 int length;
5458 }prefixes[] = {
5459 {{'p','r','o','s','p','e','r','o',':'}, 9},
5460 {{'t','e','l','n','e','t',':'}, 7},
5461 {{'g','o','p','h','e','r',':'}, 7},
5462 {{'m','a','i','l','t','o',':'}, 7},
5463 {{'h','t','t','p','s',':'}, 6},
5464 {{'f','i','l','e',':'}, 5},
5465 {{'n','e','w','s',':'}, 5},
5466 {{'w','a','i','s',':'}, 5},
5467 {{'n','n','t','p',':'}, 5},
5468 {{'h','t','t','p',':'}, 5},
5469 {{'w','w','w','.'}, 4},
5470 {{'f','t','p',':'}, 4},
5472 WCHAR bufferW[MAX_PREFIX_LEN + 1];
5473 unsigned int i;
5475 ME_GetTextW(editor, bufferW, MAX_PREFIX_LEN, start, nChars, FALSE, FALSE);
5476 for (i = 0; i < ARRAY_SIZE(prefixes); i++)
5478 if (nChars < prefixes[i].length) continue;
5479 if (!memcmp(prefixes[i].text, bufferW, prefixes[i].length * sizeof(WCHAR)))
5480 return TRUE;
5482 return FALSE;
5483 #undef MAX_PREFIX_LEN
5487 * This proc walks through the indicated selection and evaluates whether each
5488 * section identified by ME_FindNextURLCandidate and in-between sections have
5489 * their proper CFE_LINK attributes set or unset. If the CFE_LINK attribute is
5490 * not what it is supposed to be, this proc sets or unsets it as appropriate.
5492 * Since this function can cause runs to be split, do not depend on the value
5493 * of the start cursor at the end of the function.
5495 * nChars may be set to INT_MAX to update to the end of the text.
5497 * Returns TRUE if at least one section was modified.
5499 static BOOL ME_UpdateLinkAttribute(ME_TextEditor *editor, ME_Cursor *start, int nChars)
5501 BOOL modified = FALSE;
5502 ME_Cursor startCur = *start;
5504 if (!editor->AutoURLDetect_bEnable) return FALSE;
5508 CHARFORMAT2W link;
5509 ME_Cursor candidateStart, candidateEnd;
5511 if (ME_FindNextURLCandidate(editor, &startCur, nChars,
5512 &candidateStart, &candidateEnd))
5514 /* Section before candidate is not an URL */
5515 int cMin = ME_GetCursorOfs(&candidateStart);
5516 int cMax = ME_GetCursorOfs(&candidateEnd);
5518 if (!ME_IsCandidateAnURL(editor, &candidateStart, cMax - cMin))
5519 candidateStart = candidateEnd;
5520 nChars -= cMax - ME_GetCursorOfs(&startCur);
5522 else
5524 /* No more candidates until end of selection */
5525 nChars = 0;
5528 if (startCur.pRun != candidateStart.pRun ||
5529 startCur.nOffset != candidateStart.nOffset)
5531 /* CFE_LINK effect should be consistently unset */
5532 link.cbSize = sizeof(link);
5533 ME_GetCharFormat(editor, &startCur, &candidateStart, &link);
5534 if (!(link.dwMask & CFM_LINK) || (link.dwEffects & CFE_LINK))
5536 /* CFE_LINK must be unset from this range */
5537 memset(&link, 0, sizeof(CHARFORMAT2W));
5538 link.cbSize = sizeof(link);
5539 link.dwMask = CFM_LINK;
5540 link.dwEffects = 0;
5541 ME_SetCharFormat(editor, &startCur, &candidateStart, &link);
5542 /* Update candidateEnd since setting character formats may split
5543 * runs, which can cause a cursor to be at an invalid offset within
5544 * a split run. */
5545 while (candidateEnd.nOffset >= candidateEnd.pRun->member.run.len)
5547 candidateEnd.nOffset -= candidateEnd.pRun->member.run.len;
5548 candidateEnd.pRun = ME_FindItemFwd(candidateEnd.pRun, diRun);
5550 modified = TRUE;
5553 if (candidateStart.pRun != candidateEnd.pRun ||
5554 candidateStart.nOffset != candidateEnd.nOffset)
5556 /* CFE_LINK effect should be consistently set */
5557 link.cbSize = sizeof(link);
5558 ME_GetCharFormat(editor, &candidateStart, &candidateEnd, &link);
5559 if (!(link.dwMask & CFM_LINK) || !(link.dwEffects & CFE_LINK))
5561 /* CFE_LINK must be set on this range */
5562 memset(&link, 0, sizeof(CHARFORMAT2W));
5563 link.cbSize = sizeof(link);
5564 link.dwMask = CFM_LINK;
5565 link.dwEffects = CFE_LINK;
5566 ME_SetCharFormat(editor, &candidateStart, &candidateEnd, &link);
5567 modified = TRUE;
5570 startCur = candidateEnd;
5571 } while (nChars > 0);
5572 return modified;