riched20: Use a run ptr in the rtf row handler.
[wine.git] / dlls / riched20 / editor.c
blob1f37258f3454d61004bafb4b6dc77ccf971a527d
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 editor_get_selection_para_fmt( 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 editor_get_selection_para_fmt( 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_Run *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 = para_first_run( cell_first_para( cell ) );
1046 if (&info->editor->pCursors[0].pRun->member.run != 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_get_di( run );
1052 info->editor->pCursors[1].pPara = para_get_di( run->para );
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 */
1612 ME_Paragraph *para = &editor->pCursors->pPara->member.para;
1613 if (para->nFlags & (MEPF_ROWSTART | MEPF_ROWEND))
1615 para = para_next( para );
1616 editor->pCursors[0].pPara = para_get_di( para );
1617 editor->pCursors[0].pRun = run_get_di( para_first_run( para ) );
1618 editor->pCursors[0].nOffset = 0;
1620 editor->pCursors[1] = editor->pCursors[0];
1622 else /* v1.0 - 3.0 */
1624 if (editor->pCursors[0].pRun->member.run.nFlags & MERF_ENDPARA &&
1625 para_in_table( &editor->pCursors[0].pPara->member.para ))
1626 return 0;
1629 else
1631 style = editor->pBuffer->pDefaultStyle;
1632 ME_AddRefStyle(style);
1633 set_selection_cursors(editor, 0, 0);
1634 ME_InternalDeleteText(editor, &editor->pCursors[1],
1635 ME_GetTextLength(editor), FALSE);
1636 from = to = 0;
1637 ME_ClearTempStyle(editor);
1638 editor_set_default_para_fmt( editor, &editor->pCursors[0].pPara->member.para.fmt );
1642 /* Back up undo mode to a local variable */
1643 nUndoMode = editor->nUndoMode;
1645 /* Only create an undo if SFF_SELECTION is set */
1646 if (!(format & SFF_SELECTION))
1647 editor->nUndoMode = umIgnore;
1649 inStream.editstream = stream;
1650 inStream.editstream->dwError = 0;
1651 inStream.dwSize = 0;
1652 inStream.dwUsed = 0;
1654 if (format & SF_RTF)
1656 /* Check if it's really RTF, and if it is not, use plain text */
1657 ME_StreamInFill(&inStream);
1658 if (!inStream.editstream->dwError)
1660 if ((!editor->bEmulateVersion10 && strncmp(inStream.buffer, "{\\rtf", 5) && strncmp(inStream.buffer, "{\\urtf", 6))
1661 || (editor->bEmulateVersion10 && *inStream.buffer != '{'))
1663 invalidRTF = TRUE;
1664 inStream.editstream->dwError = -16;
1669 if (!invalidRTF && !inStream.editstream->dwError)
1671 ME_Cursor start;
1672 from = ME_GetCursorOfs(&editor->pCursors[0]);
1673 if (format & SF_RTF) {
1675 /* setup the RTF parser */
1676 memset(&parser, 0, sizeof parser);
1677 RTFSetEditStream(&parser, &inStream);
1678 parser.rtfFormat = format&(SF_TEXT|SF_RTF);
1679 parser.editor = editor;
1680 parser.style = style;
1681 WriterInit(&parser);
1682 RTFInit(&parser);
1683 RTFSetReadHook(&parser, ME_RTFReadHook);
1684 RTFSetDestinationCallback(&parser, rtfShpPict, ME_RTFReadShpPictGroup);
1685 RTFSetDestinationCallback(&parser, rtfPict, ME_RTFReadPictGroup);
1686 RTFSetDestinationCallback(&parser, rtfObject, ME_RTFReadObjectGroup);
1687 RTFSetDestinationCallback(&parser, rtfParNumbering, ME_RTFReadParnumGroup);
1688 if (!parser.editor->bEmulateVersion10) /* v4.1 */
1690 RTFSetDestinationCallback(&parser, rtfNoNestTables, RTFSkipGroup);
1691 RTFSetDestinationCallback(&parser, rtfNestTableProps, RTFReadGroup);
1693 BeginFile(&parser);
1695 /* do the parsing */
1696 RTFRead(&parser);
1697 RTFFlushOutputBuffer(&parser);
1698 if (!editor->bEmulateVersion10) /* v4.1 */
1700 if (parser.tableDef && parser.tableDef->row_start &&
1701 (parser.nestingLevel > 0 || parser.canInheritInTbl))
1703 /* Delete any incomplete table row at the end of the rich text. */
1704 int nOfs, nChars;
1705 ME_Paragraph *para;
1707 parser.rtfMinor = rtfRow;
1708 /* Complete the table row before deleting it.
1709 * By doing it this way we will have the current paragraph format set
1710 * properly to reflect that is not in the complete table, and undo items
1711 * will be added for this change to the current paragraph format. */
1712 if (parser.nestingLevel > 0)
1714 while (parser.nestingLevel > 1)
1715 ME_RTFSpecialCharHook(&parser); /* Decrements nestingLevel */
1716 para = parser.tableDef->row_start;
1717 ME_RTFSpecialCharHook(&parser);
1719 else
1721 para = parser.tableDef->row_start;
1722 ME_RTFSpecialCharHook(&parser);
1723 assert( para->nFlags & MEPF_ROWEND );
1724 para = para_next( para );
1727 editor->pCursors[1].pPara = para_get_di( para );
1728 editor->pCursors[1].pRun = run_get_di( para_first_run( para ) );
1729 editor->pCursors[1].nOffset = 0;
1730 nOfs = ME_GetCursorOfs(&editor->pCursors[1]);
1731 nChars = ME_GetCursorOfs(&editor->pCursors[0]) - nOfs;
1732 ME_InternalDeleteText(editor, &editor->pCursors[1], nChars, TRUE);
1733 if (parser.tableDef) parser.tableDef->row_start = NULL;
1736 RTFDestroy(&parser);
1738 if (parser.stackTop > 0)
1740 while (--parser.stackTop >= 0)
1742 ME_ReleaseStyle(parser.style);
1743 parser.style = parser.stack[parser.stackTop].style;
1745 if (!inStream.editstream->dwError)
1746 inStream.editstream->dwError = HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
1749 /* Remove last line break, as mandated by tests. This is not affected by
1750 CR/LF counters, since RTF streaming presents only \para tokens, which
1751 are converted according to the standard rules: \r for 2.0, \r\n for 1.0
1753 if (stripLastCR && !(format & SFF_SELECTION)) {
1754 int newto;
1755 ME_GetSelection(editor, &selStart, &selEnd);
1756 newto = ME_GetCursorOfs(selEnd);
1757 if (newto > to + (editor->bEmulateVersion10 ? 1 : 0)) {
1758 WCHAR lastchar[3] = {'\0', '\0'};
1759 int linebreakSize = editor->bEmulateVersion10 ? 2 : 1;
1760 ME_Cursor linebreakCursor = *selEnd, lastcharCursor = *selEnd;
1761 CHARFORMAT2W cf;
1763 /* Set the final eop to the char fmt of the last char */
1764 cf.cbSize = sizeof(cf);
1765 cf.dwMask = CFM_ALL2;
1766 ME_MoveCursorChars(editor, &lastcharCursor, -1, FALSE);
1767 ME_GetCharFormat(editor, &lastcharCursor, &linebreakCursor, &cf);
1768 set_selection_cursors(editor, newto, -1);
1769 ME_SetSelectionCharFormat(editor, &cf);
1770 set_selection_cursors(editor, newto, newto);
1772 ME_MoveCursorChars(editor, &linebreakCursor, -linebreakSize, FALSE);
1773 ME_GetTextW(editor, lastchar, 2, &linebreakCursor, linebreakSize, FALSE, FALSE);
1774 if (lastchar[0] == '\r' && (lastchar[1] == '\n' || lastchar[1] == '\0')) {
1775 ME_InternalDeleteText(editor, &linebreakCursor, linebreakSize, FALSE);
1779 to = ME_GetCursorOfs(&editor->pCursors[0]);
1780 num_read = to - from;
1782 style = parser.style;
1784 else if (format & SF_TEXT)
1786 num_read = ME_StreamInText(editor, format, &inStream, style);
1787 to = ME_GetCursorOfs(&editor->pCursors[0]);
1789 else
1790 ERR("EM_STREAMIN without SF_TEXT or SF_RTF\n");
1791 /* put the cursor at the top */
1792 if (!(format & SFF_SELECTION))
1793 set_selection_cursors(editor, 0, 0);
1794 cursor_from_char_ofs( editor, from, &start );
1795 ME_UpdateLinkAttribute(editor, &start, to - from);
1798 /* Restore saved undo mode */
1799 editor->nUndoMode = nUndoMode;
1801 /* even if we didn't add an undo, we need to commit anything on the stack */
1802 ME_CommitUndo(editor);
1804 /* If SFF_SELECTION isn't set, delete any undos from before we started too */
1805 if (!(format & SFF_SELECTION))
1806 ME_EmptyUndoStack(editor);
1808 ME_ReleaseStyle(style);
1809 editor->nEventMask = nEventMask;
1810 ME_UpdateRepaint(editor, FALSE);
1811 if (!(format & SFF_SELECTION)) {
1812 ME_ClearTempStyle(editor);
1814 update_caret(editor);
1815 ME_SendSelChange(editor);
1816 ME_SendRequestResize(editor, FALSE);
1818 return num_read;
1822 typedef struct tagME_RTFStringStreamStruct
1824 char *string;
1825 int pos;
1826 int length;
1827 } ME_RTFStringStreamStruct;
1829 static DWORD CALLBACK ME_ReadFromRTFString(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
1831 ME_RTFStringStreamStruct *pStruct = (ME_RTFStringStreamStruct *)dwCookie;
1832 int count;
1834 count = min(cb, pStruct->length - pStruct->pos);
1835 memmove(lpBuff, pStruct->string + pStruct->pos, count);
1836 pStruct->pos += count;
1837 *pcb = count;
1838 return 0;
1841 static void
1842 ME_StreamInRTFString(ME_TextEditor *editor, BOOL selection, char *string)
1844 EDITSTREAM es;
1845 ME_RTFStringStreamStruct data;
1847 data.string = string;
1848 data.length = strlen(string);
1849 data.pos = 0;
1850 es.dwCookie = (DWORD_PTR)&data;
1851 es.pfnCallback = ME_ReadFromRTFString;
1852 ME_StreamIn(editor, SF_RTF | (selection ? SFF_SELECTION : 0), &es, TRUE);
1856 static int
1857 ME_FindText(ME_TextEditor *editor, DWORD flags, const CHARRANGE *chrg, const WCHAR *text, CHARRANGE *chrgText)
1859 const int nLen = lstrlenW(text);
1860 const int nTextLen = ME_GetTextLength(editor);
1861 int nMin, nMax;
1862 ME_Cursor cursor;
1863 WCHAR wLastChar = ' ';
1865 TRACE("flags==0x%08x, chrg->cpMin==%d, chrg->cpMax==%d text==%s\n",
1866 flags, chrg->cpMin, chrg->cpMax, debugstr_w(text));
1868 if (flags & ~(FR_DOWN | FR_MATCHCASE | FR_WHOLEWORD))
1869 FIXME("Flags 0x%08x not implemented\n",
1870 flags & ~(FR_DOWN | FR_MATCHCASE | FR_WHOLEWORD));
1872 nMin = chrg->cpMin;
1873 if (chrg->cpMax == -1)
1874 nMax = nTextLen;
1875 else
1876 nMax = chrg->cpMax > nTextLen ? nTextLen : chrg->cpMax;
1878 /* In 1.0 emulation, if cpMax reaches end of text, add the FR_DOWN flag */
1879 if (editor->bEmulateVersion10 && nMax == nTextLen)
1881 flags |= FR_DOWN;
1884 /* In 1.0 emulation, cpMin must always be no greater than cpMax */
1885 if (editor->bEmulateVersion10 && nMax < nMin)
1887 if (chrgText)
1889 chrgText->cpMin = -1;
1890 chrgText->cpMax = -1;
1892 return -1;
1895 /* when searching up, if cpMin < cpMax, then instead of searching
1896 * on [cpMin,cpMax], we search on [0,cpMin], otherwise, search on
1897 * [cpMax, cpMin]. The exception is when cpMax is -1, in which
1898 * case, it is always bigger than cpMin.
1900 if (!editor->bEmulateVersion10 && !(flags & FR_DOWN))
1902 int nSwap = nMax;
1904 nMax = nMin > nTextLen ? nTextLen : nMin;
1905 if (nMin < nSwap || chrg->cpMax == -1)
1906 nMin = 0;
1907 else
1908 nMin = nSwap;
1911 if (!nLen || nMin < 0 || nMax < 0 || nMax < nMin)
1913 if (chrgText)
1914 chrgText->cpMin = chrgText->cpMax = -1;
1915 return -1;
1918 if (flags & FR_DOWN) /* Forward search */
1920 /* If possible, find the character before where the search starts */
1921 if ((flags & FR_WHOLEWORD) && nMin)
1923 cursor_from_char_ofs( editor, nMin - 1, &cursor );
1924 wLastChar = *get_text( &cursor.pRun->member.run, cursor.nOffset );
1925 ME_MoveCursorChars(editor, &cursor, 1, FALSE);
1927 else cursor_from_char_ofs( editor, nMin, &cursor );
1929 while (cursor.pRun && ME_GetCursorOfs(&cursor) + nLen <= nMax)
1931 ME_DisplayItem *pCurItem = cursor.pRun;
1932 int nCurStart = cursor.nOffset;
1933 int nMatched = 0;
1935 while (pCurItem && ME_CharCompare( *get_text( &pCurItem->member.run, nCurStart + nMatched ), text[nMatched], (flags & FR_MATCHCASE)))
1937 if ((flags & FR_WHOLEWORD) && iswalnum(wLastChar))
1938 break;
1940 nMatched++;
1941 if (nMatched == nLen)
1943 ME_DisplayItem *pNextItem = pCurItem;
1944 int nNextStart = nCurStart;
1945 WCHAR wNextChar;
1947 /* Check to see if next character is a whitespace */
1948 if (flags & FR_WHOLEWORD)
1950 if (nCurStart + nMatched == pCurItem->member.run.len)
1952 pNextItem = ME_FindItemFwd(pCurItem, diRun);
1953 nNextStart = -nMatched;
1956 if (pNextItem)
1957 wNextChar = *get_text( &pNextItem->member.run, nNextStart + nMatched );
1958 else
1959 wNextChar = ' ';
1961 if (iswalnum(wNextChar))
1962 break;
1965 cursor.nOffset += cursor.pPara->member.para.nCharOfs + cursor.pRun->member.run.nCharOfs;
1966 if (chrgText)
1968 chrgText->cpMin = cursor.nOffset;
1969 chrgText->cpMax = cursor.nOffset + nLen;
1971 TRACE("found at %d-%d\n", cursor.nOffset, cursor.nOffset + nLen);
1972 return cursor.nOffset;
1974 if (nCurStart + nMatched == pCurItem->member.run.len)
1976 pCurItem = ME_FindItemFwd(pCurItem, diRun);
1977 nCurStart = -nMatched;
1980 if (pCurItem)
1981 wLastChar = *get_text( &pCurItem->member.run, nCurStart + nMatched );
1982 else
1983 wLastChar = ' ';
1985 cursor.nOffset++;
1986 if (cursor.nOffset == cursor.pRun->member.run.len)
1988 ME_NextRun(&cursor.pPara, &cursor.pRun, TRUE);
1989 cursor.nOffset = 0;
1993 else /* Backward search */
1995 /* If possible, find the character after where the search ends */
1996 if ((flags & FR_WHOLEWORD) && nMax < nTextLen - 1)
1998 cursor_from_char_ofs( editor, nMax + 1, &cursor );
1999 wLastChar = *get_text( &cursor.pRun->member.run, cursor.nOffset );
2000 ME_MoveCursorChars(editor, &cursor, -1, FALSE);
2002 else cursor_from_char_ofs( editor, nMax, &cursor );
2004 while (cursor.pRun && ME_GetCursorOfs(&cursor) - nLen >= nMin)
2006 ME_DisplayItem *pCurItem = cursor.pRun;
2007 ME_DisplayItem *pCurPara = cursor.pPara;
2008 int nCurEnd = cursor.nOffset;
2009 int nMatched = 0;
2011 if (nCurEnd == 0)
2013 ME_PrevRun(&pCurPara, &pCurItem, TRUE);
2014 nCurEnd = pCurItem->member.run.len;
2017 while (pCurItem && ME_CharCompare( *get_text( &pCurItem->member.run, nCurEnd - nMatched - 1 ),
2018 text[nLen - nMatched - 1], (flags & FR_MATCHCASE) ))
2020 if ((flags & FR_WHOLEWORD) && iswalnum(wLastChar))
2021 break;
2023 nMatched++;
2024 if (nMatched == nLen)
2026 ME_DisplayItem *pPrevItem = pCurItem;
2027 int nPrevEnd = nCurEnd;
2028 WCHAR wPrevChar;
2029 int nStart;
2031 /* Check to see if previous character is a whitespace */
2032 if (flags & FR_WHOLEWORD)
2034 if (nPrevEnd - nMatched == 0)
2036 pPrevItem = ME_FindItemBack(pCurItem, diRun);
2037 if (pPrevItem)
2038 nPrevEnd = pPrevItem->member.run.len + nMatched;
2041 if (pPrevItem)
2042 wPrevChar = *get_text( &pPrevItem->member.run, nPrevEnd - nMatched - 1 );
2043 else
2044 wPrevChar = ' ';
2046 if (iswalnum(wPrevChar))
2047 break;
2050 nStart = pCurPara->member.para.nCharOfs
2051 + pCurItem->member.run.nCharOfs + nCurEnd - nMatched;
2052 if (chrgText)
2054 chrgText->cpMin = nStart;
2055 chrgText->cpMax = nStart + nLen;
2057 TRACE("found at %d-%d\n", nStart, nStart + nLen);
2058 return nStart;
2060 if (nCurEnd - nMatched == 0)
2062 ME_PrevRun(&pCurPara, &pCurItem, TRUE);
2063 /* Don't care about pCurItem becoming NULL here; it's already taken
2064 * care of in the exterior loop condition */
2065 nCurEnd = pCurItem->member.run.len + nMatched;
2068 if (pCurItem)
2069 wLastChar = *get_text( &pCurItem->member.run, nCurEnd - nMatched - 1 );
2070 else
2071 wLastChar = ' ';
2073 cursor.nOffset--;
2074 if (cursor.nOffset < 0)
2076 ME_PrevRun(&cursor.pPara, &cursor.pRun, TRUE);
2077 cursor.nOffset = cursor.pRun->member.run.len;
2081 TRACE("not found\n");
2082 if (chrgText)
2083 chrgText->cpMin = chrgText->cpMax = -1;
2084 return -1;
2087 static int ME_GetTextEx(ME_TextEditor *editor, GETTEXTEX *ex, LPARAM pText)
2089 int nChars;
2090 ME_Cursor start;
2092 if (!ex->cb || !pText) return 0;
2094 if (ex->flags & ~(GT_SELECTION | GT_USECRLF))
2095 FIXME("GETTEXTEX flags 0x%08x not supported\n", ex->flags & ~(GT_SELECTION | GT_USECRLF));
2097 if (ex->flags & GT_SELECTION)
2099 int from, to;
2100 int nStartCur = ME_GetSelectionOfs(editor, &from, &to);
2101 start = editor->pCursors[nStartCur];
2102 nChars = to - from;
2104 else
2106 ME_SetCursorToStart(editor, &start);
2107 nChars = INT_MAX;
2109 if (ex->codepage == CP_UNICODE)
2111 return ME_GetTextW(editor, (LPWSTR)pText, ex->cb / sizeof(WCHAR) - 1,
2112 &start, nChars, ex->flags & GT_USECRLF, FALSE);
2114 else
2116 /* potentially each char may be a CR, why calculate the exact value with O(N) when
2117 we can just take a bigger buffer? :)
2118 The above assumption still holds with CR/LF counters, since CR->CRLF expansion
2119 occurs only in richedit 2.0 mode, in which line breaks have only one CR
2121 int crlfmul = (ex->flags & GT_USECRLF) ? 2 : 1;
2122 DWORD buflen;
2123 LPWSTR buffer;
2124 LRESULT rc;
2126 buflen = min(crlfmul * nChars, ex->cb - 1);
2127 buffer = heap_alloc((buflen + 1) * sizeof(WCHAR));
2129 nChars = ME_GetTextW(editor, buffer, buflen, &start, nChars, ex->flags & GT_USECRLF, FALSE);
2130 rc = WideCharToMultiByte(ex->codepage, 0, buffer, nChars + 1,
2131 (LPSTR)pText, ex->cb, ex->lpDefaultChar, ex->lpUsedDefChar);
2132 if (rc) rc--; /* do not count 0 terminator */
2134 heap_free(buffer);
2135 return rc;
2139 static int ME_GetTextRange(ME_TextEditor *editor, WCHAR *strText,
2140 const ME_Cursor *start, int nLen, BOOL unicode)
2142 if (!strText) return 0;
2143 if (unicode) {
2144 return ME_GetTextW(editor, strText, INT_MAX, start, nLen, FALSE, FALSE);
2145 } else {
2146 int nChars;
2147 WCHAR *p = heap_alloc((nLen+1) * sizeof(*p));
2148 if (!p) return 0;
2149 nChars = ME_GetTextW(editor, p, nLen, start, nLen, FALSE, FALSE);
2150 WideCharToMultiByte(CP_ACP, 0, p, nChars+1, (char *)strText,
2151 nLen+1, NULL, NULL);
2152 heap_free(p);
2153 return nChars;
2157 int set_selection( ME_TextEditor *editor, int to, int from )
2159 int end;
2161 TRACE("%d - %d\n", to, from );
2163 if (!editor->bHideSelection) ME_InvalidateSelection( editor );
2164 end = set_selection_cursors( editor, to, from );
2165 if (!editor->bHideSelection) ME_InvalidateSelection( editor );
2166 update_caret( editor );
2167 ME_SendSelChange( editor );
2169 return end;
2172 typedef struct tagME_GlobalDestStruct
2174 HGLOBAL hData;
2175 int nLength;
2176 } ME_GlobalDestStruct;
2178 static DWORD CALLBACK ME_ReadFromHGLOBALUnicode(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
2180 ME_GlobalDestStruct *pData = (ME_GlobalDestStruct *)dwCookie;
2181 int i;
2182 WORD *pSrc, *pDest;
2184 cb = cb >> 1;
2185 pDest = (WORD *)lpBuff;
2186 pSrc = GlobalLock(pData->hData);
2187 for (i = 0; i<cb && pSrc[pData->nLength+i]; i++) {
2188 pDest[i] = pSrc[pData->nLength+i];
2190 pData->nLength += i;
2191 *pcb = 2*i;
2192 GlobalUnlock(pData->hData);
2193 return 0;
2196 static DWORD CALLBACK ME_ReadFromHGLOBALRTF(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
2198 ME_GlobalDestStruct *pData = (ME_GlobalDestStruct *)dwCookie;
2199 int i;
2200 BYTE *pSrc, *pDest;
2202 pDest = lpBuff;
2203 pSrc = GlobalLock(pData->hData);
2204 for (i = 0; i<cb && pSrc[pData->nLength+i]; i++) {
2205 pDest[i] = pSrc[pData->nLength+i];
2207 pData->nLength += i;
2208 *pcb = i;
2209 GlobalUnlock(pData->hData);
2210 return 0;
2213 static const WCHAR rtfW[] = {'R','i','c','h',' ','T','e','x','t',' ','F','o','r','m','a','t',0};
2215 static HRESULT paste_rtf(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2217 EDITSTREAM es;
2218 ME_GlobalDestStruct gds;
2219 HRESULT hr;
2221 gds.hData = med->u.hGlobal;
2222 gds.nLength = 0;
2223 es.dwCookie = (DWORD_PTR)&gds;
2224 es.pfnCallback = ME_ReadFromHGLOBALRTF;
2225 hr = ME_StreamIn( editor, SF_RTF | SFF_SELECTION, &es, FALSE ) == 0 ? E_FAIL : S_OK;
2226 ReleaseStgMedium( med );
2227 return hr;
2230 static HRESULT paste_text(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2232 EDITSTREAM es;
2233 ME_GlobalDestStruct gds;
2234 HRESULT hr;
2236 gds.hData = med->u.hGlobal;
2237 gds.nLength = 0;
2238 es.dwCookie = (DWORD_PTR)&gds;
2239 es.pfnCallback = ME_ReadFromHGLOBALUnicode;
2240 hr = ME_StreamIn( editor, SF_TEXT | SF_UNICODE | SFF_SELECTION, &es, FALSE ) == 0 ? E_FAIL : S_OK;
2241 ReleaseStgMedium( med );
2242 return hr;
2245 static HRESULT paste_emf(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2247 HRESULT hr;
2248 SIZEL sz = {0, 0};
2250 hr = insert_static_object( editor, med->u.hEnhMetaFile, NULL, &sz );
2251 if (SUCCEEDED(hr))
2253 ME_CommitUndo( editor );
2254 ME_UpdateRepaint( editor, FALSE );
2256 else
2257 ReleaseStgMedium( med );
2259 return hr;
2262 static struct paste_format
2264 FORMATETC fmt;
2265 HRESULT (*paste)(ME_TextEditor *, FORMATETC *, STGMEDIUM *);
2266 const WCHAR *name;
2267 } paste_formats[] =
2269 {{ -1, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, paste_rtf, rtfW },
2270 {{ CF_UNICODETEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, paste_text },
2271 {{ CF_ENHMETAFILE, NULL, DVASPECT_CONTENT, -1, TYMED_ENHMF }, paste_emf },
2272 {{ 0 }}
2275 static void init_paste_formats(void)
2277 struct paste_format *format;
2278 static int done;
2280 if (!done)
2282 for (format = paste_formats; format->fmt.cfFormat; format++)
2284 if (format->name)
2285 format->fmt.cfFormat = RegisterClipboardFormatW( format->name );
2287 done = 1;
2291 static BOOL paste_special(ME_TextEditor *editor, UINT cf, REPASTESPECIAL *ps, BOOL check_only)
2293 HRESULT hr;
2294 STGMEDIUM med;
2295 struct paste_format *format;
2296 IDataObject *data;
2298 /* Protect read-only edit control from modification */
2299 if (editor->styleFlags & ES_READONLY)
2301 if (!check_only)
2302 MessageBeep(MB_ICONERROR);
2303 return FALSE;
2306 init_paste_formats();
2308 if (ps && ps->dwAspect != DVASPECT_CONTENT)
2309 FIXME("Ignoring aspect %x\n", ps->dwAspect);
2311 hr = OleGetClipboard( &data );
2312 if (hr != S_OK) return FALSE;
2314 if (cf == CF_TEXT) cf = CF_UNICODETEXT;
2316 hr = S_FALSE;
2317 for (format = paste_formats; format->fmt.cfFormat; format++)
2319 if (cf && cf != format->fmt.cfFormat) continue;
2320 hr = IDataObject_QueryGetData( data, &format->fmt );
2321 if (hr == S_OK)
2323 if (!check_only)
2325 hr = IDataObject_GetData( data, &format->fmt, &med );
2326 if (hr != S_OK) goto done;
2327 hr = format->paste( editor, &format->fmt, &med );
2329 break;
2333 done:
2334 IDataObject_Release( data );
2336 return hr == S_OK;
2339 static HRESULT editor_copy( ME_TextEditor *editor, ME_Cursor *start, int chars, IDataObject **data_out )
2341 IDataObject *data = NULL;
2342 HRESULT hr = S_OK;
2344 if (editor->lpOleCallback)
2346 CHARRANGE range;
2347 range.cpMin = ME_GetCursorOfs( start );
2348 range.cpMax = range.cpMin + chars;
2349 hr = IRichEditOleCallback_GetClipboardData( editor->lpOleCallback, &range, RECO_COPY, &data );
2352 if (FAILED( hr ) || !data)
2353 hr = ME_GetDataObject( editor, start, chars, &data );
2355 if (SUCCEEDED( hr ))
2357 if (data_out)
2358 *data_out = data;
2359 else
2361 hr = OleSetClipboard( data );
2362 IDataObject_Release( data );
2366 return hr;
2369 HRESULT editor_copy_or_cut( ME_TextEditor *editor, BOOL cut, ME_Cursor *start, int count,
2370 IDataObject **data_out )
2372 HRESULT hr;
2374 if (cut && (editor->styleFlags & ES_READONLY))
2376 return E_ACCESSDENIED;
2379 hr = editor_copy( editor, start, count, data_out );
2380 if (SUCCEEDED(hr) && cut)
2382 ME_InternalDeleteText( editor, start, count, FALSE );
2383 ME_CommitUndo( editor );
2384 ME_UpdateRepaint( editor, TRUE );
2386 return hr;
2389 static BOOL copy_or_cut( ME_TextEditor *editor, BOOL cut )
2391 HRESULT hr;
2392 int offs, count;
2393 int start_cursor = ME_GetSelectionOfs( editor, &offs, &count );
2394 ME_Cursor *sel_start = &editor->pCursors[start_cursor];
2396 if (editor->cPasswordMask) return FALSE;
2398 count -= offs;
2399 hr = editor_copy_or_cut( editor, cut, sel_start, count, NULL );
2400 if (FAILED( hr )) MessageBeep( MB_ICONERROR );
2402 return SUCCEEDED( hr );
2405 /* helper to send a msg filter notification */
2406 static BOOL
2407 ME_FilterEvent(ME_TextEditor *editor, UINT msg, WPARAM* wParam, LPARAM* lParam)
2409 MSGFILTER msgf;
2411 if (!editor->hWnd || !editor->hwndParent) return FALSE;
2412 msgf.nmhdr.hwndFrom = editor->hWnd;
2413 msgf.nmhdr.idFrom = GetWindowLongW(editor->hWnd, GWLP_ID);
2414 msgf.nmhdr.code = EN_MSGFILTER;
2415 msgf.msg = msg;
2416 msgf.wParam = *wParam;
2417 msgf.lParam = *lParam;
2418 if (SendMessageW(editor->hwndParent, WM_NOTIFY, msgf.nmhdr.idFrom, (LPARAM)&msgf))
2419 return FALSE;
2420 *wParam = msgf.wParam;
2421 *lParam = msgf.lParam;
2422 msgf.wParam = *wParam;
2424 return TRUE;
2427 static void ME_UpdateSelectionLinkAttribute(ME_TextEditor *editor)
2429 ME_Paragraph *start_para, *end_para;
2430 ME_Cursor *from, *to, start;
2431 int num_chars;
2433 if (!editor->AutoURLDetect_bEnable) return;
2435 ME_GetSelection(editor, &from, &to);
2437 /* Find paragraph previous to the one that contains start cursor */
2438 start_para = &from->pPara->member.para;
2439 if (para_prev( start_para )) start_para = para_prev( start_para );
2441 /* Find paragraph that contains end cursor */
2442 end_para = para_next( &to->pPara->member.para );
2444 start.pPara = para_get_di( start_para );
2445 start.pRun = run_get_di( para_first_run( start_para ) );
2446 start.nOffset = 0;
2447 num_chars = end_para->nCharOfs - start_para->nCharOfs;
2449 ME_UpdateLinkAttribute( editor, &start, num_chars );
2452 static BOOL handle_enter(ME_TextEditor *editor)
2454 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2455 BOOL shift_is_down = GetKeyState(VK_SHIFT) & 0x8000;
2457 if (editor->bDialogMode)
2459 if (ctrl_is_down)
2460 return TRUE;
2462 if (!(editor->styleFlags & ES_WANTRETURN))
2464 if (editor->hwndParent)
2466 DWORD dw;
2467 dw = SendMessageW(editor->hwndParent, DM_GETDEFID, 0, 0);
2468 if (HIWORD(dw) == DC_HASDEFID)
2470 HWND hwDefCtrl = GetDlgItem(editor->hwndParent, LOWORD(dw));
2471 if (hwDefCtrl)
2473 SendMessageW(editor->hwndParent, WM_NEXTDLGCTL, (WPARAM)hwDefCtrl, TRUE);
2474 PostMessageW(hwDefCtrl, WM_KEYDOWN, VK_RETURN, 0);
2478 return TRUE;
2482 if (editor->styleFlags & ES_MULTILINE)
2484 static const WCHAR endl = '\r';
2485 static const WCHAR endlv10[] = {'\r','\n'};
2486 ME_Cursor cursor = editor->pCursors[0];
2487 ME_Paragraph *para = &cursor.pPara->member.para;
2488 int from, to;
2489 ME_Style *style, *eop_style;
2491 if (editor->styleFlags & ES_READONLY)
2493 MessageBeep(MB_ICONERROR);
2494 return TRUE;
2497 ME_GetSelectionOfs(editor, &from, &to);
2498 if (editor->nTextLimit > ME_GetTextLength(editor) - (to-from))
2500 if (!editor->bEmulateVersion10) /* v4.1 */
2502 if (para->nFlags & MEPF_ROWEND)
2504 /* Add a new table row after this row. */
2505 para = table_append_row( editor, para );
2506 para = para_next( para );
2507 editor->pCursors[0].pPara = para_get_di( para );
2508 editor->pCursors[0].pRun = run_get_di( para_first_run( para ) );
2509 editor->pCursors[0].nOffset = 0;
2510 editor->pCursors[1] = editor->pCursors[0];
2511 ME_CommitUndo(editor);
2512 ME_UpdateRepaint(editor, FALSE);
2513 return TRUE;
2515 else if (para == &editor->pCursors[1].pPara->member.para &&
2516 cursor.nOffset + cursor.pRun->member.run.nCharOfs == 0 &&
2517 para_prev( para ) && para_prev( para )->nFlags & MEPF_ROWSTART &&
2518 !para_prev( para )->nCharOfs)
2520 /* Insert a newline before the table. */
2521 para = para_prev( para );
2522 para->nFlags &= ~MEPF_ROWSTART;
2523 editor->pCursors[0].pPara = para_get_di( para );
2524 editor->pCursors[0].pRun = run_get_di( para_first_run( para ) );
2525 editor->pCursors[1] = editor->pCursors[0];
2526 ME_InsertTextFromCursor(editor, 0, &endl, 1,
2527 editor->pCursors[0].pRun->member.run.style);
2528 para = editor_first_para( editor );
2529 editor_set_default_para_fmt( editor, &para->fmt );
2530 para->nFlags = 0;
2531 para_mark_rewrap( editor, para );
2532 editor->pCursors[0].pPara = para_get_di( para );
2533 editor->pCursors[0].pRun = run_get_di( para_first_run( para ) );
2534 editor->pCursors[1] = editor->pCursors[0];
2535 para_next( para )->nFlags |= MEPF_ROWSTART;
2536 ME_CommitCoalescingUndo(editor);
2537 ME_UpdateRepaint(editor, FALSE);
2538 return TRUE;
2541 else /* v1.0 - 3.0 */
2543 ME_Paragraph *para = &cursor.pPara->member.para;
2544 if (para_in_table( para ))
2546 if (cursor.pRun->member.run.nFlags & MERF_ENDPARA)
2548 if (from == to)
2550 ME_ContinueCoalescingTransaction(editor);
2551 para = table_append_row( editor, para );
2552 editor->pCursors[0].pPara = para_get_di( para );
2553 editor->pCursors[0].pRun = run_get_di( para_first_run( para ) );
2554 editor->pCursors[0].nOffset = 0;
2555 editor->pCursors[1] = editor->pCursors[0];
2556 ME_CommitCoalescingUndo(editor);
2557 ME_UpdateRepaint(editor, FALSE);
2558 return TRUE;
2561 else
2563 ME_ContinueCoalescingTransaction(editor);
2564 if (cursor.pRun->member.run.nCharOfs + cursor.nOffset == 0 &&
2565 para_prev( para ) && !para_in_table( para_prev( para ) ))
2567 /* Insert newline before table */
2568 cursor.pRun = run_get_di( para_end_run( para_prev( para ) ) );
2569 if (cursor.pRun)
2571 editor->pCursors[0].pRun = cursor.pRun;
2572 editor->pCursors[0].pPara = para_get_di( para_prev( para ) );
2574 editor->pCursors[0].nOffset = 0;
2575 editor->pCursors[1] = editor->pCursors[0];
2576 ME_InsertTextFromCursor(editor, 0, &endl, 1,
2577 editor->pCursors[0].pRun->member.run.style);
2579 else
2581 editor->pCursors[1] = editor->pCursors[0];
2582 para = table_append_row( editor, para );
2583 editor->pCursors[0].pPara = para_get_di( para );
2584 editor->pCursors[0].pRun = run_get_di( para_first_run( para ) );
2585 editor->pCursors[0].nOffset = 0;
2586 editor->pCursors[1] = editor->pCursors[0];
2588 ME_CommitCoalescingUndo(editor);
2589 ME_UpdateRepaint(editor, FALSE);
2590 return TRUE;
2595 style = style_get_insert_style( editor, editor->pCursors );
2597 /* Normally the new eop style is the insert style, however in a list it is copied from the existing
2598 eop style (this prevents the list label style changing when the new eop is inserted).
2599 No extra ref is taken here on eop_style. */
2600 if (para->fmt.wNumbering)
2601 eop_style = para->eop_run->style;
2602 else
2603 eop_style = style;
2604 ME_ContinueCoalescingTransaction(editor);
2605 if (shift_is_down)
2606 ME_InsertEndRowFromCursor(editor, 0);
2607 else
2608 if (!editor->bEmulateVersion10)
2609 ME_InsertTextFromCursor(editor, 0, &endl, 1, eop_style);
2610 else
2611 ME_InsertTextFromCursor(editor, 0, endlv10, 2, eop_style);
2612 ME_CommitCoalescingUndo(editor);
2613 SetCursor(NULL);
2615 ME_UpdateSelectionLinkAttribute(editor);
2616 ME_UpdateRepaint(editor, FALSE);
2617 ME_SaveTempStyle(editor, style); /* set the temp insert style for the new para */
2618 ME_ReleaseStyle(style);
2620 return TRUE;
2622 return FALSE;
2625 static BOOL
2626 ME_KeyDown(ME_TextEditor *editor, WORD nKey)
2628 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2629 BOOL shift_is_down = GetKeyState(VK_SHIFT) & 0x8000;
2631 if (editor->bMouseCaptured)
2632 return FALSE;
2633 if (nKey != VK_SHIFT && nKey != VK_CONTROL && nKey != VK_MENU)
2634 editor->nSelectionType = stPosition;
2636 switch (nKey)
2638 case VK_LEFT:
2639 case VK_RIGHT:
2640 case VK_HOME:
2641 case VK_END:
2642 editor->nUDArrowX = -1;
2643 /* fall through */
2644 case VK_UP:
2645 case VK_DOWN:
2646 case VK_PRIOR:
2647 case VK_NEXT:
2648 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
2649 ME_ArrowKey(editor, nKey, shift_is_down, ctrl_is_down);
2650 return TRUE;
2651 case VK_BACK:
2652 case VK_DELETE:
2653 editor->nUDArrowX = -1;
2654 /* FIXME backspace and delete aren't the same, they act different wrt paragraph style of the merged paragraph */
2655 if (editor->styleFlags & ES_READONLY)
2656 return FALSE;
2657 if (ME_IsSelection(editor))
2659 ME_DeleteSelection(editor);
2660 ME_CommitUndo(editor);
2662 else if (nKey == VK_DELETE)
2664 /* Delete stops group typing.
2665 * (See MSDN remarks on EM_STOPGROUPTYPING message) */
2666 ME_DeleteTextAtCursor(editor, 1, 1);
2667 ME_CommitUndo(editor);
2669 else if (ME_ArrowKey(editor, VK_LEFT, FALSE, FALSE))
2671 BOOL bDeletionSucceeded;
2672 /* Backspace can be grouped for a single undo */
2673 ME_ContinueCoalescingTransaction(editor);
2674 bDeletionSucceeded = ME_DeleteTextAtCursor(editor, 1, 1);
2675 if (!bDeletionSucceeded && !editor->bEmulateVersion10) { /* v4.1 */
2676 /* Deletion was prevented so the cursor is moved back to where it was.
2677 * (e.g. this happens when trying to delete cell boundaries)
2679 ME_ArrowKey(editor, VK_RIGHT, FALSE, FALSE);
2681 ME_CommitCoalescingUndo(editor);
2683 else
2684 return TRUE;
2685 table_move_from_row_start( editor );
2686 ME_UpdateSelectionLinkAttribute(editor);
2687 ME_UpdateRepaint(editor, FALSE);
2688 ME_SendRequestResize(editor, FALSE);
2689 return TRUE;
2690 case VK_RETURN:
2691 if (!editor->bEmulateVersion10)
2692 return handle_enter(editor);
2693 break;
2694 case VK_ESCAPE:
2695 if (editor->bDialogMode && editor->hwndParent)
2696 PostMessageW(editor->hwndParent, WM_CLOSE, 0, 0);
2697 return TRUE;
2698 case VK_TAB:
2699 if (editor->bDialogMode && editor->hwndParent)
2700 SendMessageW(editor->hwndParent, WM_NEXTDLGCTL, shift_is_down, 0);
2701 return TRUE;
2702 case 'A':
2703 if (ctrl_is_down)
2705 set_selection( editor, 0, -1 );
2706 return TRUE;
2708 break;
2709 case 'V':
2710 if (ctrl_is_down)
2711 return paste_special( editor, 0, NULL, FALSE );
2712 break;
2713 case 'C':
2714 case 'X':
2715 if (ctrl_is_down)
2716 return copy_or_cut(editor, nKey == 'X');
2717 break;
2718 case 'Z':
2719 if (ctrl_is_down)
2721 ME_Undo(editor);
2722 return TRUE;
2724 break;
2725 case 'Y':
2726 if (ctrl_is_down)
2728 ME_Redo(editor);
2729 return TRUE;
2731 break;
2733 default:
2734 if (nKey != VK_SHIFT && nKey != VK_CONTROL && nKey && nKey != VK_MENU)
2735 editor->nUDArrowX = -1;
2736 if (ctrl_is_down)
2738 if (nKey == 'W')
2740 CHARFORMAT2W chf;
2741 char buf[2048];
2742 chf.cbSize = sizeof(chf);
2744 ME_GetSelectionCharFormat(editor, &chf);
2745 ME_DumpStyleToBuf(&chf, buf);
2746 MessageBoxA(NULL, buf, "Style dump", MB_OK);
2748 if (nKey == 'Q')
2750 ME_CheckCharOffsets(editor);
2754 return FALSE;
2757 static LRESULT ME_Char(ME_TextEditor *editor, WPARAM charCode,
2758 LPARAM flags, BOOL unicode)
2760 WCHAR wstr;
2762 if (editor->bMouseCaptured)
2763 return 0;
2765 if (editor->styleFlags & ES_READONLY)
2767 MessageBeep(MB_ICONERROR);
2768 return 0; /* FIXME really 0 ? */
2771 if (unicode)
2772 wstr = (WCHAR)charCode;
2773 else
2775 CHAR charA = charCode;
2776 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &wstr, 1);
2779 if (editor->bEmulateVersion10 && wstr == '\r')
2780 handle_enter(editor);
2782 if ((unsigned)wstr >= ' ' || wstr == '\t')
2784 ME_Cursor cursor = editor->pCursors[0];
2785 ME_DisplayItem *para = cursor.pPara;
2786 int from, to;
2787 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2788 ME_GetSelectionOfs(editor, &from, &to);
2789 if (wstr == '\t' &&
2790 /* v4.1 allows tabs to be inserted with ctrl key down */
2791 !(ctrl_is_down && !editor->bEmulateVersion10))
2793 ME_DisplayItem *para;
2794 BOOL selected_row = FALSE;
2796 para = cursor.pPara;
2797 if (ME_IsSelection(editor) &&
2798 cursor.pRun->member.run.nCharOfs + cursor.nOffset == 0 &&
2799 to == ME_GetCursorOfs(&editor->pCursors[0]) &&
2800 para->member.para.prev_para->type == diParagraph)
2802 para = para->member.para.prev_para;
2803 selected_row = TRUE;
2805 if (ME_IsInTable(para))
2807 table_handle_tab( editor, selected_row );
2808 ME_CommitUndo(editor);
2809 return 0;
2811 } else if (!editor->bEmulateVersion10) { /* v4.1 */
2812 if (para->member.para.nFlags & MEPF_ROWEND) {
2813 if (from == to) {
2814 para = para->member.para.next_para;
2815 if (para->member.para.nFlags & MEPF_ROWSTART)
2816 para = para->member.para.next_para;
2817 editor->pCursors[0].pPara = para;
2818 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2819 editor->pCursors[0].nOffset = 0;
2820 editor->pCursors[1] = editor->pCursors[0];
2823 } else { /* v1.0 - 3.0 */
2824 if (ME_IsInTable(cursor.pRun) &&
2825 cursor.pRun->member.run.nFlags & MERF_ENDPARA &&
2826 from == to)
2828 /* Text should not be inserted at the end of the table. */
2829 MessageBeep(-1);
2830 return 0;
2833 /* FIXME maybe it would make sense to call EM_REPLACESEL instead ? */
2834 /* WM_CHAR is restricted to nTextLimit */
2835 if(editor->nTextLimit > ME_GetTextLength(editor) - (to-from))
2837 ME_Style *style = style_get_insert_style( editor, editor->pCursors );
2838 ME_ContinueCoalescingTransaction(editor);
2839 ME_InsertTextFromCursor(editor, 0, &wstr, 1, style);
2840 ME_ReleaseStyle(style);
2841 ME_CommitCoalescingUndo(editor);
2842 ITextHost_TxSetCursor(editor->texthost, NULL, FALSE);
2845 ME_UpdateSelectionLinkAttribute(editor);
2846 ME_UpdateRepaint(editor, FALSE);
2848 return 0;
2851 /* Process the message and calculate the new click count.
2853 * returns: The click count if it is mouse down event, else returns 0. */
2854 static int ME_CalculateClickCount(ME_TextEditor *editor, UINT msg, WPARAM wParam,
2855 LPARAM lParam)
2857 static int clickNum = 0;
2858 if (msg < WM_MOUSEFIRST || msg > WM_MOUSELAST)
2859 return 0;
2861 if ((msg == WM_LBUTTONDBLCLK) ||
2862 (msg == WM_RBUTTONDBLCLK) ||
2863 (msg == WM_MBUTTONDBLCLK) ||
2864 (msg == WM_XBUTTONDBLCLK))
2866 msg -= (WM_LBUTTONDBLCLK - WM_LBUTTONDOWN);
2869 if ((msg == WM_LBUTTONDOWN) ||
2870 (msg == WM_RBUTTONDOWN) ||
2871 (msg == WM_MBUTTONDOWN) ||
2872 (msg == WM_XBUTTONDOWN))
2874 static MSG prevClickMsg;
2875 MSG clickMsg;
2876 /* Compare the editor instead of the hwnd so that the this
2877 * can still be done for windowless richedit controls. */
2878 clickMsg.hwnd = (HWND)editor;
2879 clickMsg.message = msg;
2880 clickMsg.wParam = wParam;
2881 clickMsg.lParam = lParam;
2882 clickMsg.time = GetMessageTime();
2883 clickMsg.pt.x = (short)LOWORD(lParam);
2884 clickMsg.pt.y = (short)HIWORD(lParam);
2885 if ((clickNum != 0) &&
2886 (clickMsg.message == prevClickMsg.message) &&
2887 (clickMsg.hwnd == prevClickMsg.hwnd) &&
2888 (clickMsg.wParam == prevClickMsg.wParam) &&
2889 (clickMsg.time - prevClickMsg.time < GetDoubleClickTime()) &&
2890 (abs(clickMsg.pt.x - prevClickMsg.pt.x) < GetSystemMetrics(SM_CXDOUBLECLK)/2) &&
2891 (abs(clickMsg.pt.y - prevClickMsg.pt.y) < GetSystemMetrics(SM_CYDOUBLECLK)/2))
2893 clickNum++;
2894 } else {
2895 clickNum = 1;
2897 prevClickMsg = clickMsg;
2898 } else {
2899 return 0;
2901 return clickNum;
2904 static BOOL is_link( ME_Run *run )
2906 return (run->style->fmt.dwMask & CFM_LINK) && (run->style->fmt.dwEffects & CFE_LINK);
2909 static BOOL ME_SetCursor(ME_TextEditor *editor)
2911 ME_Cursor cursor;
2912 POINT pt;
2913 BOOL isExact;
2914 SCROLLBARINFO sbi;
2915 DWORD messagePos = GetMessagePos();
2916 pt.x = (short)LOWORD(messagePos);
2917 pt.y = (short)HIWORD(messagePos);
2919 if (editor->hWnd)
2921 sbi.cbSize = sizeof(sbi);
2922 GetScrollBarInfo(editor->hWnd, OBJID_HSCROLL, &sbi);
2923 if (!(sbi.rgstate[0] & (STATE_SYSTEM_INVISIBLE|STATE_SYSTEM_OFFSCREEN)) &&
2924 PtInRect(&sbi.rcScrollBar, pt))
2926 ITextHost_TxSetCursor(editor->texthost,
2927 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2928 return TRUE;
2930 sbi.cbSize = sizeof(sbi);
2931 GetScrollBarInfo(editor->hWnd, OBJID_VSCROLL, &sbi);
2932 if (!(sbi.rgstate[0] & (STATE_SYSTEM_INVISIBLE|STATE_SYSTEM_OFFSCREEN)) &&
2933 PtInRect(&sbi.rcScrollBar, pt))
2935 ITextHost_TxSetCursor(editor->texthost,
2936 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2937 return TRUE;
2940 ITextHost_TxScreenToClient(editor->texthost, &pt);
2942 if (editor->nSelectionType == stLine && editor->bMouseCaptured) {
2943 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2944 return TRUE;
2946 if (!editor->bEmulateVersion10 /* v4.1 */ &&
2947 pt.y < editor->rcFormat.top &&
2948 pt.x < editor->rcFormat.left)
2950 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2951 return TRUE;
2953 if (pt.y < editor->rcFormat.top || pt.y > editor->rcFormat.bottom)
2955 if (editor->bEmulateVersion10) /* v1.0 - 3.0 */
2956 ITextHost_TxSetCursor(editor->texthost,
2957 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2958 else /* v4.1 */
2959 ITextHost_TxSetCursor(editor->texthost,
2960 LoadCursorW(NULL, (WCHAR*)IDC_IBEAM), TRUE);
2961 return TRUE;
2963 if (pt.x < editor->rcFormat.left)
2965 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2966 return TRUE;
2968 ME_CharFromPos(editor, pt.x, pt.y, &cursor, &isExact);
2969 if (isExact)
2971 ME_Run *run;
2973 run = &cursor.pRun->member.run;
2974 if (is_link( run ))
2976 ITextHost_TxSetCursor(editor->texthost,
2977 LoadCursorW(NULL, (WCHAR*)IDC_HAND),
2978 FALSE);
2979 return TRUE;
2982 if (ME_IsSelection(editor))
2984 int selStart, selEnd;
2985 int offset = ME_GetCursorOfs(&cursor);
2987 ME_GetSelectionOfs(editor, &selStart, &selEnd);
2988 if (selStart <= offset && selEnd >= offset) {
2989 ITextHost_TxSetCursor(editor->texthost,
2990 LoadCursorW(NULL, (WCHAR*)IDC_ARROW),
2991 FALSE);
2992 return TRUE;
2996 ITextHost_TxSetCursor(editor->texthost,
2997 LoadCursorW(NULL, (WCHAR*)IDC_IBEAM), TRUE);
2998 return TRUE;
3001 static void ME_SetDefaultFormatRect(ME_TextEditor *editor)
3003 ITextHost_TxGetClientRect(editor->texthost, &editor->rcFormat);
3004 editor->rcFormat.top += editor->exStyleFlags & WS_EX_CLIENTEDGE ? 1 : 0;
3005 editor->rcFormat.left += 1 + editor->selofs;
3006 editor->rcFormat.right -= 1;
3009 static LONG ME_GetSelectionType(ME_TextEditor *editor)
3011 LONG sel_type = SEL_EMPTY;
3012 LONG start, end;
3014 ME_GetSelectionOfs(editor, &start, &end);
3015 if (start == end)
3016 sel_type = SEL_EMPTY;
3017 else
3019 LONG object_count = 0, character_count = 0;
3020 int i;
3022 for (i = 0; i < end - start; i++)
3024 ME_Cursor cursor;
3026 cursor_from_char_ofs( editor, start + i, &cursor );
3027 if (cursor.pRun->member.run.reobj)
3028 object_count++;
3029 else
3030 character_count++;
3031 if (character_count >= 2 && object_count >= 2)
3032 return (SEL_TEXT | SEL_MULTICHAR | SEL_OBJECT | SEL_MULTIOBJECT);
3034 if (character_count)
3036 sel_type |= SEL_TEXT;
3037 if (character_count >= 2)
3038 sel_type |= SEL_MULTICHAR;
3040 if (object_count)
3042 sel_type |= SEL_OBJECT;
3043 if (object_count >= 2)
3044 sel_type |= SEL_MULTIOBJECT;
3047 return sel_type;
3050 static BOOL ME_ShowContextMenu(ME_TextEditor *editor, int x, int y)
3052 CHARRANGE selrange;
3053 HMENU menu;
3054 int seltype;
3056 if(!editor->lpOleCallback || !editor->hWnd)
3057 return FALSE;
3058 ME_GetSelectionOfs(editor, &selrange.cpMin, &selrange.cpMax);
3059 seltype = ME_GetSelectionType(editor);
3060 if(SUCCEEDED(IRichEditOleCallback_GetContextMenu(editor->lpOleCallback, seltype, NULL, &selrange, &menu)))
3062 TrackPopupMenu(menu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, x, y, 0, editor->hwndParent, NULL);
3063 DestroyMenu(menu);
3065 return TRUE;
3068 ME_TextEditor *ME_MakeEditor(ITextHost *texthost, BOOL bEmulateVersion10)
3070 ME_TextEditor *ed = heap_alloc(sizeof(*ed));
3071 int i;
3072 DWORD props;
3073 LONG selbarwidth;
3075 ed->hWnd = NULL;
3076 ed->hwndParent = NULL;
3077 ed->sizeWindow.cx = ed->sizeWindow.cy = 0;
3078 ed->texthost = texthost;
3079 ed->reOle = NULL;
3080 ed->bEmulateVersion10 = bEmulateVersion10;
3081 ed->styleFlags = 0;
3082 ed->exStyleFlags = 0;
3083 ed->total_rows = 0;
3084 ITextHost_TxGetPropertyBits(texthost,
3085 (TXTBIT_RICHTEXT|TXTBIT_MULTILINE|
3086 TXTBIT_READONLY|TXTBIT_USEPASSWORD|
3087 TXTBIT_HIDESELECTION|TXTBIT_SAVESELECTION|
3088 TXTBIT_AUTOWORDSEL|TXTBIT_VERTICAL|
3089 TXTBIT_WORDWRAP|TXTBIT_DISABLEDRAG),
3090 &props);
3091 ITextHost_TxGetScrollBars(texthost, &ed->styleFlags);
3092 ed->styleFlags &= (WS_VSCROLL|WS_HSCROLL|ES_AUTOVSCROLL|
3093 ES_AUTOHSCROLL|ES_DISABLENOSCROLL);
3094 ed->pBuffer = ME_MakeText();
3095 ed->nZoomNumerator = ed->nZoomDenominator = 0;
3096 ed->nAvailWidth = 0; /* wrap to client area */
3097 list_init( &ed->style_list );
3098 ME_MakeFirstParagraph(ed);
3099 /* The four cursors are for:
3100 * 0 - The position where the caret is shown
3101 * 1 - The anchored end of the selection (for normal selection)
3102 * 2 & 3 - The anchored start and end respectively for word, line,
3103 * or paragraph selection.
3105 ed->nCursors = 4;
3106 ed->pCursors = heap_alloc(ed->nCursors * sizeof(*ed->pCursors));
3107 ME_SetCursorToStart(ed, &ed->pCursors[0]);
3108 ed->pCursors[1] = ed->pCursors[0];
3109 ed->pCursors[2] = ed->pCursors[0];
3110 ed->pCursors[3] = ed->pCursors[1];
3111 ed->nLastTotalLength = ed->nTotalLength = 0;
3112 ed->nLastTotalWidth = ed->nTotalWidth = 0;
3113 ed->nUDArrowX = -1;
3114 ed->rgbBackColor = -1;
3115 ed->hbrBackground = GetSysColorBrush(COLOR_WINDOW);
3116 ed->nEventMask = 0;
3117 ed->nModifyStep = 0;
3118 ed->nTextLimit = TEXT_LIMIT_DEFAULT;
3119 list_init( &ed->undo_stack );
3120 list_init( &ed->redo_stack );
3121 ed->nUndoStackSize = 0;
3122 ed->nUndoLimit = STACK_SIZE_DEFAULT;
3123 ed->nUndoMode = umAddToUndo;
3124 ed->nParagraphs = 1;
3125 ed->nLastSelStart = ed->nLastSelEnd = 0;
3126 ed->last_sel_start_para = ed->last_sel_end_para = &ed->pCursors[0].pPara->member.para;
3127 ed->bHideSelection = FALSE;
3128 ed->pfnWordBreak = NULL;
3129 ed->lpOleCallback = NULL;
3130 ed->mode = TM_MULTILEVELUNDO | TM_MULTICODEPAGE;
3131 ed->mode |= (props & TXTBIT_RICHTEXT) ? TM_RICHTEXT : TM_PLAINTEXT;
3132 ed->AutoURLDetect_bEnable = FALSE;
3133 ed->bHaveFocus = FALSE;
3134 ed->bDialogMode = FALSE;
3135 ed->bMouseCaptured = FALSE;
3136 ed->caret_hidden = FALSE;
3137 ed->caret_height = 0;
3138 for (i=0; i<HFONT_CACHE_SIZE; i++)
3140 ed->pFontCache[i].nRefs = 0;
3141 ed->pFontCache[i].nAge = 0;
3142 ed->pFontCache[i].hFont = NULL;
3145 ME_CheckCharOffsets(ed);
3146 SetRectEmpty(&ed->rcFormat);
3147 ed->bDefaultFormatRect = TRUE;
3148 ITextHost_TxGetSelectionBarWidth(ed->texthost, &selbarwidth);
3149 if (selbarwidth) {
3150 /* FIXME: Convert selbarwidth from HIMETRIC to pixels */
3151 ed->selofs = SELECTIONBAR_WIDTH;
3152 ed->styleFlags |= ES_SELECTIONBAR;
3153 } else {
3154 ed->selofs = 0;
3156 ed->nSelectionType = stPosition;
3158 ed->cPasswordMask = 0;
3159 if (props & TXTBIT_USEPASSWORD)
3160 ITextHost_TxGetPasswordChar(texthost, &ed->cPasswordMask);
3162 if (props & TXTBIT_AUTOWORDSEL)
3163 ed->styleFlags |= ECO_AUTOWORDSELECTION;
3164 if (props & TXTBIT_MULTILINE) {
3165 ed->styleFlags |= ES_MULTILINE;
3166 ed->bWordWrap = (props & TXTBIT_WORDWRAP) != 0;
3167 } else {
3168 ed->bWordWrap = FALSE;
3170 if (props & TXTBIT_READONLY)
3171 ed->styleFlags |= ES_READONLY;
3172 if (!(props & TXTBIT_HIDESELECTION))
3173 ed->styleFlags |= ES_NOHIDESEL;
3174 if (props & TXTBIT_SAVESELECTION)
3175 ed->styleFlags |= ES_SAVESEL;
3176 if (props & TXTBIT_VERTICAL)
3177 ed->styleFlags |= ES_VERTICAL;
3178 if (props & TXTBIT_DISABLEDRAG)
3179 ed->styleFlags |= ES_NOOLEDRAGDROP;
3181 ed->notified_cr.cpMin = ed->notified_cr.cpMax = 0;
3183 /* Default scrollbar information */
3184 ed->vert_si.cbSize = sizeof(SCROLLINFO);
3185 ed->vert_si.nMin = 0;
3186 ed->vert_si.nMax = 0;
3187 ed->vert_si.nPage = 0;
3188 ed->vert_si.nPos = 0;
3190 ed->horz_si.cbSize = sizeof(SCROLLINFO);
3191 ed->horz_si.nMin = 0;
3192 ed->horz_si.nMax = 0;
3193 ed->horz_si.nPage = 0;
3194 ed->horz_si.nPos = 0;
3196 ed->wheel_remain = 0;
3198 list_init( &ed->reobj_list );
3199 OleInitialize(NULL);
3201 return ed;
3204 void ME_DestroyEditor(ME_TextEditor *editor)
3206 ME_DisplayItem *p = editor->pBuffer->pFirst, *pNext = NULL;
3207 ME_Style *s, *cursor2;
3208 int i;
3210 ME_ClearTempStyle(editor);
3211 ME_EmptyUndoStack(editor);
3212 editor->pBuffer->pFirst = NULL;
3213 while(p)
3215 pNext = p->next;
3216 if (p->type == diParagraph)
3217 para_destroy( editor, &p->member.para );
3218 else
3219 ME_DestroyDisplayItem(p);
3220 p = pNext;
3223 LIST_FOR_EACH_ENTRY_SAFE( s, cursor2, &editor->style_list, ME_Style, entry )
3224 ME_DestroyStyle( s );
3226 ME_ReleaseStyle(editor->pBuffer->pDefaultStyle);
3227 for (i=0; i<HFONT_CACHE_SIZE; i++)
3229 if (editor->pFontCache[i].hFont)
3230 DeleteObject(editor->pFontCache[i].hFont);
3232 if (editor->rgbBackColor != -1)
3233 DeleteObject(editor->hbrBackground);
3234 if(editor->lpOleCallback)
3235 IRichEditOleCallback_Release(editor->lpOleCallback);
3236 ITextHost_Release(editor->texthost);
3237 if (editor->reOle)
3239 IUnknown_Release(editor->reOle);
3240 editor->reOle = NULL;
3242 OleUninitialize();
3244 heap_free(editor->pBuffer);
3245 heap_free(editor->pCursors);
3246 heap_free(editor);
3249 BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
3251 TRACE("\n");
3252 switch (fdwReason)
3254 case DLL_PROCESS_ATTACH:
3255 DisableThreadLibraryCalls(hinstDLL);
3256 me_heap = HeapCreate (0, 0x10000, 0);
3257 if (!ME_RegisterEditorClass(hinstDLL)) return FALSE;
3258 hLeft = LoadCursorW(hinstDLL, MAKEINTRESOURCEW(OCR_REVERSE));
3259 LookupInit();
3260 break;
3262 case DLL_PROCESS_DETACH:
3263 if (lpvReserved) break;
3264 UnregisterClassW(RICHEDIT_CLASS20W, 0);
3265 UnregisterClassW(MSFTEDIT_CLASS, 0);
3266 UnregisterClassA(RICHEDIT_CLASS20A, 0);
3267 UnregisterClassA("RichEdit50A", 0);
3268 if (ME_ListBoxRegistered)
3269 UnregisterClassW(REListBox20W, 0);
3270 if (ME_ComboBoxRegistered)
3271 UnregisterClassW(REComboBox20W, 0);
3272 LookupCleanup();
3273 HeapDestroy (me_heap);
3274 release_typelib();
3275 break;
3277 return TRUE;
3280 static inline int get_default_line_height( ME_TextEditor *editor )
3282 int height = 0;
3284 if (editor->pBuffer && editor->pBuffer->pDefaultStyle)
3285 height = editor->pBuffer->pDefaultStyle->tm.tmHeight;
3286 if (height <= 0) height = 24;
3288 return height;
3291 static inline int calc_wheel_change( int *remain, int amount_per_click )
3293 int change = amount_per_click * (float)*remain / WHEEL_DELTA;
3294 *remain -= WHEEL_DELTA * change / amount_per_click;
3295 return change;
3298 static const char * const edit_messages[] = {
3299 "EM_GETSEL",
3300 "EM_SETSEL",
3301 "EM_GETRECT",
3302 "EM_SETRECT",
3303 "EM_SETRECTNP",
3304 "EM_SCROLL",
3305 "EM_LINESCROLL",
3306 "EM_SCROLLCARET",
3307 "EM_GETMODIFY",
3308 "EM_SETMODIFY",
3309 "EM_GETLINECOUNT",
3310 "EM_LINEINDEX",
3311 "EM_SETHANDLE",
3312 "EM_GETHANDLE",
3313 "EM_GETTHUMB",
3314 "EM_UNKNOWN_BF",
3315 "EM_UNKNOWN_C0",
3316 "EM_LINELENGTH",
3317 "EM_REPLACESEL",
3318 "EM_UNKNOWN_C3",
3319 "EM_GETLINE",
3320 "EM_LIMITTEXT",
3321 "EM_CANUNDO",
3322 "EM_UNDO",
3323 "EM_FMTLINES",
3324 "EM_LINEFROMCHAR",
3325 "EM_UNKNOWN_CA",
3326 "EM_SETTABSTOPS",
3327 "EM_SETPASSWORDCHAR",
3328 "EM_EMPTYUNDOBUFFER",
3329 "EM_GETFIRSTVISIBLELINE",
3330 "EM_SETREADONLY",
3331 "EM_SETWORDBREAKPROC",
3332 "EM_GETWORDBREAKPROC",
3333 "EM_GETPASSWORDCHAR",
3334 "EM_SETMARGINS",
3335 "EM_GETMARGINS",
3336 "EM_GETLIMITTEXT",
3337 "EM_POSFROMCHAR",
3338 "EM_CHARFROMPOS",
3339 "EM_SETIMESTATUS",
3340 "EM_GETIMESTATUS"
3343 static const char * const richedit_messages[] = {
3344 "EM_CANPASTE",
3345 "EM_DISPLAYBAND",
3346 "EM_EXGETSEL",
3347 "EM_EXLIMITTEXT",
3348 "EM_EXLINEFROMCHAR",
3349 "EM_EXSETSEL",
3350 "EM_FINDTEXT",
3351 "EM_FORMATRANGE",
3352 "EM_GETCHARFORMAT",
3353 "EM_GETEVENTMASK",
3354 "EM_GETOLEINTERFACE",
3355 "EM_GETPARAFORMAT",
3356 "EM_GETSELTEXT",
3357 "EM_HIDESELECTION",
3358 "EM_PASTESPECIAL",
3359 "EM_REQUESTRESIZE",
3360 "EM_SELECTIONTYPE",
3361 "EM_SETBKGNDCOLOR",
3362 "EM_SETCHARFORMAT",
3363 "EM_SETEVENTMASK",
3364 "EM_SETOLECALLBACK",
3365 "EM_SETPARAFORMAT",
3366 "EM_SETTARGETDEVICE",
3367 "EM_STREAMIN",
3368 "EM_STREAMOUT",
3369 "EM_GETTEXTRANGE",
3370 "EM_FINDWORDBREAK",
3371 "EM_SETOPTIONS",
3372 "EM_GETOPTIONS",
3373 "EM_FINDTEXTEX",
3374 "EM_GETWORDBREAKPROCEX",
3375 "EM_SETWORDBREAKPROCEX",
3376 "EM_SETUNDOLIMIT",
3377 "EM_UNKNOWN_USER_83",
3378 "EM_REDO",
3379 "EM_CANREDO",
3380 "EM_GETUNDONAME",
3381 "EM_GETREDONAME",
3382 "EM_STOPGROUPTYPING",
3383 "EM_SETTEXTMODE",
3384 "EM_GETTEXTMODE",
3385 "EM_AUTOURLDETECT",
3386 "EM_GETAUTOURLDETECT",
3387 "EM_SETPALETTE",
3388 "EM_GETTEXTEX",
3389 "EM_GETTEXTLENGTHEX",
3390 "EM_SHOWSCROLLBAR",
3391 "EM_SETTEXTEX",
3392 "EM_UNKNOWN_USER_98",
3393 "EM_UNKNOWN_USER_99",
3394 "EM_SETPUNCTUATION",
3395 "EM_GETPUNCTUATION",
3396 "EM_SETWORDWRAPMODE",
3397 "EM_GETWORDWRAPMODE",
3398 "EM_SETIMECOLOR",
3399 "EM_GETIMECOLOR",
3400 "EM_SETIMEOPTIONS",
3401 "EM_GETIMEOPTIONS",
3402 "EM_CONVPOSITION",
3403 "EM_UNKNOWN_USER_109",
3404 "EM_UNKNOWN_USER_110",
3405 "EM_UNKNOWN_USER_111",
3406 "EM_UNKNOWN_USER_112",
3407 "EM_UNKNOWN_USER_113",
3408 "EM_UNKNOWN_USER_114",
3409 "EM_UNKNOWN_USER_115",
3410 "EM_UNKNOWN_USER_116",
3411 "EM_UNKNOWN_USER_117",
3412 "EM_UNKNOWN_USER_118",
3413 "EM_UNKNOWN_USER_119",
3414 "EM_SETLANGOPTIONS",
3415 "EM_GETLANGOPTIONS",
3416 "EM_GETIMECOMPMODE",
3417 "EM_FINDTEXTW",
3418 "EM_FINDTEXTEXW",
3419 "EM_RECONVERSION",
3420 "EM_SETIMEMODEBIAS",
3421 "EM_GETIMEMODEBIAS"
3424 static const char *
3425 get_msg_name(UINT msg)
3427 if (msg >= EM_GETSEL && msg <= EM_CHARFROMPOS)
3428 return edit_messages[msg - EM_GETSEL];
3429 if (msg >= EM_CANPASTE && msg <= EM_GETIMEMODEBIAS)
3430 return richedit_messages[msg - EM_CANPASTE];
3431 return "";
3434 static void ME_LinkNotify(ME_TextEditor *editor, UINT msg, WPARAM wParam, LPARAM lParam)
3436 int x,y;
3437 BOOL isExact;
3438 ME_Cursor cursor; /* The start of the clicked text. */
3440 ENLINK info;
3441 x = (short)LOWORD(lParam);
3442 y = (short)HIWORD(lParam);
3443 ME_CharFromPos(editor, x, y, &cursor, &isExact);
3444 if (!isExact) return;
3446 if (is_link( &cursor.pRun->member.run ))
3447 { /* The clicked run has CFE_LINK set */
3448 ME_DisplayItem *di;
3450 info.nmhdr.hwndFrom = NULL;
3451 info.nmhdr.idFrom = 0;
3452 info.nmhdr.code = EN_LINK;
3453 info.msg = msg;
3454 info.wParam = wParam;
3455 info.lParam = lParam;
3456 cursor.nOffset = 0;
3458 /* find the first contiguous run with CFE_LINK set */
3459 info.chrg.cpMin = ME_GetCursorOfs(&cursor);
3460 di = cursor.pRun;
3461 while (ME_PrevRun( NULL, &di, FALSE ) && is_link( &di->member.run ))
3462 info.chrg.cpMin -= di->member.run.len;
3464 /* find the last contiguous run with CFE_LINK set */
3465 info.chrg.cpMax = ME_GetCursorOfs(&cursor) + cursor.pRun->member.run.len;
3466 di = cursor.pRun;
3467 while (ME_NextRun( NULL, &di, FALSE ) && is_link( &di->member.run ))
3468 info.chrg.cpMax += di->member.run.len;
3470 ITextHost_TxNotify(editor->texthost, info.nmhdr.code, &info);
3474 void ME_ReplaceSel(ME_TextEditor *editor, BOOL can_undo, const WCHAR *str, int len)
3476 int from, to, nStartCursor;
3477 ME_Style *style;
3479 nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3480 style = ME_GetSelectionInsertStyle(editor);
3481 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to-from, FALSE);
3482 ME_InsertTextFromCursor(editor, 0, str, len, style);
3483 ME_ReleaseStyle(style);
3484 /* drop temporary style if line end */
3486 * FIXME question: does abc\n mean: put abc,
3487 * clear temp style, put \n? (would require a change)
3489 if (len>0 && str[len-1] == '\n')
3490 ME_ClearTempStyle(editor);
3491 ME_CommitUndo(editor);
3492 ME_UpdateSelectionLinkAttribute(editor);
3493 if (!can_undo)
3494 ME_EmptyUndoStack(editor);
3495 ME_UpdateRepaint(editor, FALSE);
3498 static void ME_SetText(ME_TextEditor *editor, void *text, BOOL unicode)
3500 LONG codepage = unicode ? CP_UNICODE : CP_ACP;
3501 int textLen;
3503 LPWSTR wszText = ME_ToUnicode(codepage, text, &textLen);
3504 ME_InsertTextFromCursor(editor, 0, wszText, textLen, editor->pBuffer->pDefaultStyle);
3505 ME_EndToUnicode(codepage, wszText);
3508 static LRESULT ME_WmCreate(ME_TextEditor *editor, LPARAM lParam, BOOL unicode)
3510 CREATESTRUCTW *createW = (CREATESTRUCTW*)lParam;
3511 CREATESTRUCTA *createA = (CREATESTRUCTA*)lParam;
3512 void *text = NULL;
3513 INT max;
3515 if (lParam)
3516 text = unicode ? (void*)createW->lpszName : (void*)createA->lpszName;
3518 ME_SetDefaultFormatRect(editor);
3520 max = (editor->styleFlags & ES_DISABLENOSCROLL) ? 1 : 0;
3521 if (~editor->styleFlags & ES_DISABLENOSCROLL || editor->styleFlags & WS_VSCROLL)
3522 ITextHost_TxSetScrollRange(editor->texthost, SB_VERT, 0, max, TRUE);
3524 if (~editor->styleFlags & ES_DISABLENOSCROLL || editor->styleFlags & WS_HSCROLL)
3525 ITextHost_TxSetScrollRange(editor->texthost, SB_HORZ, 0, max, TRUE);
3527 if (editor->styleFlags & ES_DISABLENOSCROLL)
3529 if (editor->styleFlags & WS_VSCROLL)
3531 ITextHost_TxEnableScrollBar(editor->texthost, SB_VERT, ESB_DISABLE_BOTH);
3532 ITextHost_TxShowScrollBar(editor->texthost, SB_VERT, TRUE);
3534 if (editor->styleFlags & WS_HSCROLL)
3536 ITextHost_TxEnableScrollBar(editor->texthost, SB_HORZ, ESB_DISABLE_BOTH);
3537 ITextHost_TxShowScrollBar(editor->texthost, SB_HORZ, TRUE);
3541 if (text)
3543 ME_SetText(editor, text, unicode);
3544 ME_SetCursorToStart(editor, &editor->pCursors[0]);
3545 ME_SetCursorToStart(editor, &editor->pCursors[1]);
3548 ME_CommitUndo(editor);
3549 ME_WrapMarkedParagraphs(editor);
3550 update_caret(editor);
3551 return 0;
3554 static LRESULT handle_EM_SETCHARFORMAT( ME_TextEditor *editor, WPARAM flags, const CHARFORMAT2W *fmt_in )
3556 CHARFORMAT2W fmt;
3557 BOOL changed = TRUE;
3558 ME_Cursor start, end;
3560 if (!cfany_to_cf2w( &fmt, fmt_in )) return 0;
3562 if (flags & SCF_ALL)
3564 if (editor->mode & TM_PLAINTEXT)
3566 ME_SetDefaultCharFormat( editor, &fmt );
3568 else
3570 ME_SetCursorToStart( editor, &start );
3571 ME_SetCharFormat( editor, &start, NULL, &fmt );
3572 editor->nModifyStep = 1;
3575 else if (flags & SCF_SELECTION)
3577 if (editor->mode & TM_PLAINTEXT) return 0;
3578 if (flags & SCF_WORD)
3580 end = editor->pCursors[0];
3581 ME_MoveCursorWords( editor, &end, +1 );
3582 start = end;
3583 ME_MoveCursorWords( editor, &start, -1 );
3584 ME_SetCharFormat( editor, &start, &end, &fmt );
3586 changed = ME_IsSelection( editor );
3587 ME_SetSelectionCharFormat( editor, &fmt );
3588 if (changed) editor->nModifyStep = 1;
3590 else /* SCF_DEFAULT */
3592 ME_SetDefaultCharFormat( editor, &fmt );
3595 ME_CommitUndo( editor );
3596 if (changed)
3598 ME_WrapMarkedParagraphs( editor );
3599 ME_UpdateScrollBar( editor );
3601 return 1;
3604 #define UNSUPPORTED_MSG(e) \
3605 case e: \
3606 FIXME(#e ": stub\n"); \
3607 *phresult = S_FALSE; \
3608 return 0;
3610 /* Handle messages for windowless and windowed richedit controls.
3612 * The LRESULT that is returned is a return value for window procs,
3613 * and the phresult parameter is the COM return code needed by the
3614 * text services interface. */
3615 LRESULT ME_HandleMessage(ME_TextEditor *editor, UINT msg, WPARAM wParam,
3616 LPARAM lParam, BOOL unicode, HRESULT* phresult)
3618 *phresult = S_OK;
3620 switch(msg) {
3622 UNSUPPORTED_MSG(EM_DISPLAYBAND)
3623 UNSUPPORTED_MSG(EM_FINDWORDBREAK)
3624 UNSUPPORTED_MSG(EM_FMTLINES)
3625 UNSUPPORTED_MSG(EM_FORMATRANGE)
3626 UNSUPPORTED_MSG(EM_GETBIDIOPTIONS)
3627 UNSUPPORTED_MSG(EM_GETEDITSTYLE)
3628 UNSUPPORTED_MSG(EM_GETIMECOMPMODE)
3629 UNSUPPORTED_MSG(EM_GETIMESTATUS)
3630 UNSUPPORTED_MSG(EM_SETIMESTATUS)
3631 UNSUPPORTED_MSG(EM_GETLANGOPTIONS)
3632 UNSUPPORTED_MSG(EM_GETREDONAME)
3633 UNSUPPORTED_MSG(EM_GETTYPOGRAPHYOPTIONS)
3634 UNSUPPORTED_MSG(EM_GETUNDONAME)
3635 UNSUPPORTED_MSG(EM_GETWORDBREAKPROCEX)
3636 UNSUPPORTED_MSG(EM_SETBIDIOPTIONS)
3637 UNSUPPORTED_MSG(EM_SETEDITSTYLE)
3638 UNSUPPORTED_MSG(EM_SETLANGOPTIONS)
3639 UNSUPPORTED_MSG(EM_SETMARGINS)
3640 UNSUPPORTED_MSG(EM_SETPALETTE)
3641 UNSUPPORTED_MSG(EM_SETTABSTOPS)
3642 UNSUPPORTED_MSG(EM_SETTYPOGRAPHYOPTIONS)
3643 UNSUPPORTED_MSG(EM_SETWORDBREAKPROCEX)
3645 /* Messages specific to Richedit controls */
3647 case EM_STREAMIN:
3648 return ME_StreamIn(editor, wParam, (EDITSTREAM*)lParam, TRUE);
3649 case EM_STREAMOUT:
3650 return ME_StreamOut(editor, wParam, (EDITSTREAM *)lParam);
3651 case WM_GETDLGCODE:
3653 UINT code = DLGC_WANTCHARS|DLGC_WANTTAB|DLGC_WANTARROWS;
3655 if (lParam)
3656 editor->bDialogMode = TRUE;
3657 if (editor->styleFlags & ES_MULTILINE)
3658 code |= DLGC_WANTMESSAGE;
3659 if (!(editor->styleFlags & ES_SAVESEL))
3660 code |= DLGC_HASSETSEL;
3661 return code;
3663 case EM_EMPTYUNDOBUFFER:
3664 ME_EmptyUndoStack(editor);
3665 return 0;
3666 case EM_GETSEL:
3668 /* Note: wParam/lParam can be NULL */
3669 UINT from, to;
3670 PUINT pfrom = wParam ? (PUINT)wParam : &from;
3671 PUINT pto = lParam ? (PUINT)lParam : &to;
3672 ME_GetSelectionOfs(editor, (int *)pfrom, (int *)pto);
3673 if ((*pfrom|*pto) & 0xFFFF0000)
3674 return -1;
3675 return MAKELONG(*pfrom,*pto);
3677 case EM_EXGETSEL:
3679 CHARRANGE *pRange = (CHARRANGE *)lParam;
3680 ME_GetSelectionOfs(editor, &pRange->cpMin, &pRange->cpMax);
3681 TRACE("EM_EXGETSEL = (%d,%d)\n", pRange->cpMin, pRange->cpMax);
3682 return 0;
3684 case EM_SETUNDOLIMIT:
3686 if ((int)wParam < 0)
3687 editor->nUndoLimit = STACK_SIZE_DEFAULT;
3688 else
3689 editor->nUndoLimit = min(wParam, STACK_SIZE_MAX);
3690 /* Setting a max stack size keeps wine from getting killed
3691 for hogging memory. Windows allocates all this memory at once, so
3692 no program would realistically set a value above our maximum. */
3693 return editor->nUndoLimit;
3695 case EM_CANUNDO:
3696 return !list_empty( &editor->undo_stack );
3697 case EM_CANREDO:
3698 return !list_empty( &editor->redo_stack );
3699 case WM_UNDO: /* FIXME: actually not the same */
3700 case EM_UNDO:
3701 return ME_Undo(editor);
3702 case EM_REDO:
3703 return ME_Redo(editor);
3704 case EM_GETOPTIONS:
3706 /* these flags are equivalent to the ES_* counterparts */
3707 DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
3708 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN | ECO_SELECTIONBAR;
3709 DWORD settings = editor->styleFlags & mask;
3711 return settings;
3713 case EM_SETFONTSIZE:
3715 CHARFORMAT2W cf;
3716 LONG tmp_size, size;
3717 BOOL is_increase = ((LONG)wParam > 0);
3719 if (editor->mode & TM_PLAINTEXT)
3720 return FALSE;
3722 cf.cbSize = sizeof(cf);
3723 cf.dwMask = CFM_SIZE;
3724 ME_GetSelectionCharFormat(editor, &cf);
3725 tmp_size = (cf.yHeight / 20) + wParam;
3727 if (tmp_size <= 1)
3728 size = 1;
3729 else if (tmp_size > 12 && tmp_size < 28 && tmp_size % 2)
3730 size = tmp_size + (is_increase ? 1 : -1);
3731 else if (tmp_size > 28 && tmp_size < 36)
3732 size = is_increase ? 36 : 28;
3733 else if (tmp_size > 36 && tmp_size < 48)
3734 size = is_increase ? 48 : 36;
3735 else if (tmp_size > 48 && tmp_size < 72)
3736 size = is_increase ? 72 : 48;
3737 else if (tmp_size > 72 && tmp_size < 80)
3738 size = is_increase ? 80 : 72;
3739 else if (tmp_size > 80 && tmp_size < 1638)
3740 size = 10 * (is_increase ? (tmp_size / 10 + 1) : (tmp_size / 10));
3741 else if (tmp_size >= 1638)
3742 size = 1638;
3743 else
3744 size = tmp_size;
3746 cf.yHeight = size * 20; /* convert twips to points */
3747 ME_SetSelectionCharFormat(editor, &cf);
3748 ME_CommitUndo(editor);
3749 ME_WrapMarkedParagraphs(editor);
3750 ME_UpdateScrollBar(editor);
3752 return TRUE;
3754 case EM_SETOPTIONS:
3756 /* these flags are equivalent to ES_* counterparts, except for
3757 * ECO_AUTOWORDSELECTION that doesn't have an ES_* counterpart,
3758 * but is still stored in editor->styleFlags. */
3759 const DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
3760 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN |
3761 ECO_SELECTIONBAR | ECO_AUTOWORDSELECTION;
3762 DWORD settings = mask & editor->styleFlags;
3763 DWORD oldSettings = settings;
3764 DWORD changedSettings;
3766 switch(wParam)
3768 case ECOOP_SET:
3769 settings = lParam;
3770 break;
3771 case ECOOP_OR:
3772 settings |= lParam;
3773 break;
3774 case ECOOP_AND:
3775 settings &= lParam;
3776 break;
3777 case ECOOP_XOR:
3778 settings ^= lParam;
3780 changedSettings = oldSettings ^ settings;
3782 if (changedSettings) {
3783 editor->styleFlags = (editor->styleFlags & ~mask) | (settings & mask);
3785 if (changedSettings & ECO_SELECTIONBAR)
3787 ITextHost_TxInvalidateRect(editor->texthost, &editor->rcFormat, TRUE);
3788 if (settings & ECO_SELECTIONBAR) {
3789 assert(!editor->selofs);
3790 editor->selofs = SELECTIONBAR_WIDTH;
3791 editor->rcFormat.left += editor->selofs;
3792 } else {
3793 editor->rcFormat.left -= editor->selofs;
3794 editor->selofs = 0;
3796 ME_RewrapRepaint(editor);
3799 if ((changedSettings & settings & ES_NOHIDESEL) && !editor->bHaveFocus)
3800 ME_InvalidateSelection( editor );
3802 if (changedSettings & settings & ECO_VERTICAL)
3803 FIXME("ECO_VERTICAL not implemented yet!\n");
3804 if (changedSettings & settings & ECO_AUTOHSCROLL)
3805 FIXME("ECO_AUTOHSCROLL not implemented yet!\n");
3806 if (changedSettings & settings & ECO_AUTOVSCROLL)
3807 FIXME("ECO_AUTOVSCROLL not implemented yet!\n");
3808 if (changedSettings & settings & ECO_WANTRETURN)
3809 FIXME("ECO_WANTRETURN not implemented yet!\n");
3810 if (changedSettings & settings & ECO_AUTOWORDSELECTION)
3811 FIXME("ECO_AUTOWORDSELECTION not implemented yet!\n");
3814 return settings;
3816 case EM_SETSEL:
3818 return set_selection( editor, wParam, lParam );
3820 case EM_SETSCROLLPOS:
3822 POINT *point = (POINT *)lParam;
3823 ME_ScrollAbs(editor, point->x, point->y);
3824 return 0;
3826 case EM_AUTOURLDETECT:
3828 if (wParam==1 || wParam ==0)
3830 editor->AutoURLDetect_bEnable = (BOOL)wParam;
3831 return 0;
3833 return E_INVALIDARG;
3835 case EM_GETAUTOURLDETECT:
3837 return editor->AutoURLDetect_bEnable;
3839 case EM_EXSETSEL:
3841 CHARRANGE range = *(CHARRANGE *)lParam;
3843 return set_selection( editor, range.cpMin, range.cpMax );
3845 case EM_SHOWSCROLLBAR:
3847 DWORD flags;
3849 switch (wParam)
3851 case SB_HORZ:
3852 flags = WS_HSCROLL;
3853 break;
3854 case SB_VERT:
3855 flags = WS_VSCROLL;
3856 break;
3857 case SB_BOTH:
3858 flags = WS_HSCROLL|WS_VSCROLL;
3859 break;
3860 default:
3861 return 0;
3864 if (lParam) {
3865 editor->styleFlags |= flags;
3866 if (flags & WS_HSCROLL)
3867 ITextHost_TxShowScrollBar(editor->texthost, SB_HORZ,
3868 editor->nTotalWidth > editor->sizeWindow.cx);
3869 if (flags & WS_VSCROLL)
3870 ITextHost_TxShowScrollBar(editor->texthost, SB_VERT,
3871 editor->nTotalLength > editor->sizeWindow.cy);
3872 } else {
3873 editor->styleFlags &= ~flags;
3874 ITextHost_TxShowScrollBar(editor->texthost, wParam, FALSE);
3876 return 0;
3878 case EM_SETTEXTEX:
3880 LPWSTR wszText;
3881 SETTEXTEX *pStruct = (SETTEXTEX *)wParam;
3882 int from, to, len;
3883 ME_Style *style;
3884 BOOL bRtf, bUnicode, bSelection, bUTF8;
3885 int oldModify = editor->nModifyStep;
3886 static const char utf8_bom[] = {0xef, 0xbb, 0xbf};
3888 if (!pStruct) return 0;
3890 /* If we detect ascii rtf at the start of the string,
3891 * we know it isn't unicode. */
3892 bRtf = (lParam && (!strncmp((char *)lParam, "{\\rtf", 5) ||
3893 !strncmp((char *)lParam, "{\\urtf", 6)));
3894 bUnicode = !bRtf && pStruct->codepage == CP_UNICODE;
3895 bUTF8 = (lParam && (!strncmp((char *)lParam, utf8_bom, 3)));
3897 TRACE("EM_SETTEXTEX - %s, flags %d, cp %d\n",
3898 bUnicode ? debugstr_w((LPCWSTR)lParam) : debugstr_a((LPCSTR)lParam),
3899 pStruct->flags, pStruct->codepage);
3901 bSelection = (pStruct->flags & ST_SELECTION) != 0;
3902 if (bSelection) {
3903 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3904 style = ME_GetSelectionInsertStyle(editor);
3905 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to - from, FALSE);
3906 } else {
3907 ME_Cursor start;
3908 ME_SetCursorToStart(editor, &start);
3909 ME_InternalDeleteText(editor, &start, ME_GetTextLength(editor), FALSE);
3910 style = editor->pBuffer->pDefaultStyle;
3913 if (bRtf) {
3914 ME_StreamInRTFString(editor, bSelection, (char *)lParam);
3915 if (bSelection) {
3916 /* FIXME: The length returned doesn't include the rtf control
3917 * characters, only the actual text. */
3918 len = lParam ? strlen((char *)lParam) : 0;
3920 } else {
3921 if (bUTF8 && !bUnicode) {
3922 wszText = ME_ToUnicode(CP_UTF8, (void *)(lParam+3), &len);
3923 ME_InsertTextFromCursor(editor, 0, wszText, len, style);
3924 ME_EndToUnicode(CP_UTF8, wszText);
3925 } else {
3926 wszText = ME_ToUnicode(pStruct->codepage, (void *)lParam, &len);
3927 ME_InsertTextFromCursor(editor, 0, wszText, len, style);
3928 ME_EndToUnicode(pStruct->codepage, wszText);
3932 if (bSelection) {
3933 ME_ReleaseStyle(style);
3934 ME_UpdateSelectionLinkAttribute(editor);
3935 } else {
3936 ME_Cursor cursor;
3937 len = 1;
3938 ME_SetCursorToStart(editor, &cursor);
3939 ME_UpdateLinkAttribute(editor, &cursor, INT_MAX);
3941 ME_CommitUndo(editor);
3942 if (!(pStruct->flags & ST_KEEPUNDO))
3944 editor->nModifyStep = oldModify;
3945 ME_EmptyUndoStack(editor);
3947 ME_UpdateRepaint(editor, FALSE);
3948 return len;
3950 case EM_SELECTIONTYPE:
3951 return ME_GetSelectionType(editor);
3952 case EM_SETBKGNDCOLOR:
3954 LRESULT lColor;
3955 if (editor->rgbBackColor != -1) {
3956 DeleteObject(editor->hbrBackground);
3957 lColor = editor->rgbBackColor;
3959 else lColor = ITextHost_TxGetSysColor(editor->texthost, COLOR_WINDOW);
3961 if (wParam)
3963 editor->rgbBackColor = -1;
3964 editor->hbrBackground = GetSysColorBrush(COLOR_WINDOW);
3966 else
3968 editor->rgbBackColor = lParam;
3969 editor->hbrBackground = CreateSolidBrush(editor->rgbBackColor);
3971 ITextHost_TxInvalidateRect(editor->texthost, NULL, TRUE);
3972 return lColor;
3974 case EM_GETMODIFY:
3975 return editor->nModifyStep == 0 ? 0 : -1;
3976 case EM_SETMODIFY:
3978 if (wParam)
3979 editor->nModifyStep = 1;
3980 else
3981 editor->nModifyStep = 0;
3983 return 0;
3985 case EM_SETREADONLY:
3987 if (wParam)
3988 editor->styleFlags |= ES_READONLY;
3989 else
3990 editor->styleFlags &= ~ES_READONLY;
3991 return 1;
3993 case EM_SETEVENTMASK:
3995 DWORD nOldMask = editor->nEventMask;
3997 editor->nEventMask = lParam;
3998 return nOldMask;
4000 case EM_GETEVENTMASK:
4001 return editor->nEventMask;
4002 case EM_SETCHARFORMAT:
4003 return handle_EM_SETCHARFORMAT( editor, wParam, (CHARFORMAT2W *)lParam );
4004 case EM_GETCHARFORMAT:
4006 CHARFORMAT2W tmp, *dst = (CHARFORMAT2W *)lParam;
4007 if (dst->cbSize != sizeof(CHARFORMATA) &&
4008 dst->cbSize != sizeof(CHARFORMATW) &&
4009 dst->cbSize != sizeof(CHARFORMAT2A) &&
4010 dst->cbSize != sizeof(CHARFORMAT2W))
4011 return 0;
4012 tmp.cbSize = sizeof(tmp);
4013 if (!wParam)
4014 ME_GetDefaultCharFormat(editor, &tmp);
4015 else
4016 ME_GetSelectionCharFormat(editor, &tmp);
4017 cf2w_to_cfany(dst, &tmp);
4018 return tmp.dwMask;
4020 case EM_SETPARAFORMAT:
4022 BOOL result = editor_set_selection_para_fmt( editor, (PARAFORMAT2 *)lParam );
4023 ME_WrapMarkedParagraphs(editor);
4024 ME_UpdateScrollBar(editor);
4025 ME_CommitUndo(editor);
4026 return result;
4028 case EM_GETPARAFORMAT:
4029 editor_get_selection_para_fmt( editor, (PARAFORMAT2 *)lParam );
4030 return ((PARAFORMAT2 *)lParam)->dwMask;
4031 case EM_GETFIRSTVISIBLELINE:
4033 ME_DisplayItem *p = editor->pBuffer->pFirst;
4034 int y = editor->vert_si.nPos;
4035 int ypara = 0;
4036 int count = 0;
4037 int ystart, yend;
4038 while(p) {
4039 p = ME_FindItemFwd(p, diStartRowOrParagraphOrEnd);
4040 if (p->type == diTextEnd)
4041 break;
4042 if (p->type == diParagraph) {
4043 ypara = p->member.para.pt.y;
4044 continue;
4046 ystart = ypara + p->member.row.pt.y;
4047 yend = ystart + p->member.row.nHeight;
4048 if (y < yend) {
4049 break;
4051 count++;
4053 return count;
4055 case EM_HIDESELECTION:
4057 editor->bHideSelection = (wParam != 0);
4058 ME_InvalidateSelection(editor);
4059 return 0;
4061 case EM_LINESCROLL:
4063 if (!(editor->styleFlags & ES_MULTILINE))
4064 return FALSE;
4065 ME_ScrollDown( editor, lParam * get_default_line_height( editor ) );
4066 return TRUE;
4068 case WM_CLEAR:
4070 int from, to;
4071 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
4072 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to-from, FALSE);
4073 ME_CommitUndo(editor);
4074 ME_UpdateRepaint(editor, TRUE);
4075 return 0;
4077 case EM_REPLACESEL:
4079 int len = 0;
4080 LONG codepage = unicode ? CP_UNICODE : CP_ACP;
4081 LPWSTR wszText = ME_ToUnicode(codepage, (void *)lParam, &len);
4083 TRACE("EM_REPLACESEL - %s\n", debugstr_w(wszText));
4085 ME_ReplaceSel(editor, !!wParam, wszText, len);
4086 ME_EndToUnicode(codepage, wszText);
4087 return len;
4089 case EM_SCROLLCARET:
4090 editor_ensure_visible( editor, &editor->pCursors[0] );
4091 return 0;
4092 case WM_SETFONT:
4094 LOGFONTW lf;
4095 CHARFORMAT2W fmt;
4096 HDC hDC;
4097 BOOL bRepaint = LOWORD(lParam);
4099 if (!wParam)
4100 wParam = (WPARAM)GetStockObject(SYSTEM_FONT);
4102 if (!GetObjectW((HGDIOBJ)wParam, sizeof(LOGFONTW), &lf))
4103 return 0;
4105 hDC = ITextHost_TxGetDC(editor->texthost);
4106 ME_CharFormatFromLogFont(hDC, &lf, &fmt);
4107 ITextHost_TxReleaseDC(editor->texthost, hDC);
4108 if (editor->mode & TM_RICHTEXT) {
4109 ME_Cursor start;
4110 ME_SetCursorToStart(editor, &start);
4111 ME_SetCharFormat(editor, &start, NULL, &fmt);
4113 ME_SetDefaultCharFormat(editor, &fmt);
4115 ME_CommitUndo(editor);
4116 ME_MarkAllForWrapping(editor);
4117 ME_WrapMarkedParagraphs(editor);
4118 ME_UpdateScrollBar(editor);
4119 if (bRepaint)
4120 ME_Repaint(editor);
4121 return 0;
4123 case WM_SETTEXT:
4125 ME_Cursor cursor;
4126 ME_SetCursorToStart(editor, &cursor);
4127 ME_InternalDeleteText(editor, &cursor, ME_GetTextLength(editor), FALSE);
4128 if (lParam)
4130 TRACE("WM_SETTEXT lParam==%lx\n",lParam);
4131 if (!strncmp((char *)lParam, "{\\rtf", 5) ||
4132 !strncmp((char *)lParam, "{\\urtf", 6))
4134 /* Undocumented: WM_SETTEXT supports RTF text */
4135 ME_StreamInRTFString(editor, 0, (char *)lParam);
4137 else
4138 ME_SetText(editor, (void*)lParam, unicode);
4140 else
4141 TRACE("WM_SETTEXT - NULL\n");
4142 ME_SetCursorToStart(editor, &cursor);
4143 ME_UpdateLinkAttribute(editor, &cursor, INT_MAX);
4144 set_selection_cursors(editor, 0, 0);
4145 editor->nModifyStep = 0;
4146 ME_CommitUndo(editor);
4147 ME_EmptyUndoStack(editor);
4148 ME_UpdateRepaint(editor, FALSE);
4149 return 1;
4151 case EM_CANPASTE:
4152 return paste_special( editor, 0, NULL, TRUE );
4153 case WM_PASTE:
4154 case WM_MBUTTONDOWN:
4155 wParam = 0;
4156 lParam = 0;
4157 /* fall through */
4158 case EM_PASTESPECIAL:
4159 paste_special( editor, wParam, (REPASTESPECIAL *)lParam, FALSE );
4160 return 0;
4161 case WM_CUT:
4162 case WM_COPY:
4163 copy_or_cut(editor, msg == WM_CUT);
4164 return 0;
4165 case WM_GETTEXTLENGTH:
4167 GETTEXTLENGTHEX how;
4169 /* CR/LF conversion required in 2.0 mode, verbatim in 1.0 mode */
4170 how.flags = GTL_CLOSE | (editor->bEmulateVersion10 ? 0 : GTL_USECRLF) | GTL_NUMCHARS;
4171 how.codepage = unicode ? CP_UNICODE : CP_ACP;
4172 return ME_GetTextLengthEx(editor, &how);
4174 case EM_GETTEXTLENGTHEX:
4175 return ME_GetTextLengthEx(editor, (GETTEXTLENGTHEX *)wParam);
4176 case WM_GETTEXT:
4178 GETTEXTEX ex;
4179 ex.cb = wParam * (unicode ? sizeof(WCHAR) : sizeof(CHAR));
4180 ex.flags = GT_USECRLF;
4181 ex.codepage = unicode ? CP_UNICODE : CP_ACP;
4182 ex.lpDefaultChar = NULL;
4183 ex.lpUsedDefChar = NULL;
4184 return ME_GetTextEx(editor, &ex, lParam);
4186 case EM_GETTEXTEX:
4187 return ME_GetTextEx(editor, (GETTEXTEX*)wParam, lParam);
4188 case EM_GETSELTEXT:
4190 int nFrom, nTo, nStartCur = ME_GetSelectionOfs(editor, &nFrom, &nTo);
4191 ME_Cursor *from = &editor->pCursors[nStartCur];
4192 return ME_GetTextRange(editor, (WCHAR *)lParam, from,
4193 nTo - nFrom, unicode);
4195 case EM_GETSCROLLPOS:
4197 POINT *point = (POINT *)lParam;
4198 point->x = editor->horz_si.nPos;
4199 point->y = editor->vert_si.nPos;
4200 /* 16-bit scaled value is returned as stored in scrollinfo */
4201 if (editor->horz_si.nMax > 0xffff)
4202 point->x = MulDiv(point->x, 0xffff, editor->horz_si.nMax);
4203 if (editor->vert_si.nMax > 0xffff)
4204 point->y = MulDiv(point->y, 0xffff, editor->vert_si.nMax);
4205 return 1;
4207 case EM_GETTEXTRANGE:
4209 TEXTRANGEW *rng = (TEXTRANGEW *)lParam;
4210 ME_Cursor start;
4211 int nStart = rng->chrg.cpMin;
4212 int nEnd = rng->chrg.cpMax;
4213 int textlength = ME_GetTextLength(editor);
4215 TRACE("EM_GETTEXTRANGE min=%d max=%d unicode=%d textlength=%d\n",
4216 rng->chrg.cpMin, rng->chrg.cpMax, unicode, textlength);
4217 if (nStart < 0) return 0;
4218 if ((nStart == 0 && nEnd == -1) || nEnd > textlength)
4219 nEnd = textlength;
4220 if (nStart >= nEnd) return 0;
4222 cursor_from_char_ofs( editor, nStart, &start );
4223 return ME_GetTextRange(editor, rng->lpstrText, &start, nEnd - nStart, unicode);
4225 case EM_GETLINE:
4227 ME_DisplayItem *run;
4228 const unsigned int nMaxChars = *(WORD *) lParam;
4229 unsigned int nCharsLeft = nMaxChars;
4230 char *dest = (char *) lParam;
4231 BOOL wroteNull = FALSE;
4233 TRACE("EM_GETLINE: row=%d, nMaxChars=%d (%s)\n", (int) wParam, nMaxChars,
4234 unicode ? "Unicode" : "Ansi");
4236 run = ME_FindRowWithNumber(editor, wParam);
4237 if (run == NULL)
4238 return 0;
4240 while (nCharsLeft && (run = ME_FindItemFwd(run, diRunOrStartRow))
4241 && run->type == diRun)
4243 WCHAR *str = get_text( &run->member.run, 0 );
4244 unsigned int nCopy;
4246 nCopy = min(nCharsLeft, run->member.run.len);
4248 if (unicode)
4249 memcpy(dest, str, nCopy * sizeof(WCHAR));
4250 else
4251 nCopy = WideCharToMultiByte(CP_ACP, 0, str, nCopy, dest,
4252 nCharsLeft, NULL, NULL);
4253 dest += nCopy * (unicode ? sizeof(WCHAR) : 1);
4254 nCharsLeft -= nCopy;
4257 /* append line termination, space allowing */
4258 if (nCharsLeft > 0)
4260 if (unicode)
4261 *((WCHAR *)dest) = '\0';
4262 else
4263 *dest = '\0';
4264 nCharsLeft--;
4265 wroteNull = TRUE;
4268 TRACE("EM_GETLINE: got %u characters\n", nMaxChars - nCharsLeft);
4269 return nMaxChars - nCharsLeft - (wroteNull ? 1 : 0);
4271 case EM_GETLINECOUNT:
4273 ME_DisplayItem *item = editor->pBuffer->pLast;
4274 int nRows = editor->total_rows;
4275 ME_DisplayItem *prev_para = NULL, *last_para = NULL;
4277 last_para = ME_FindItemBack(item, diRun);
4278 prev_para = ME_FindItemBack(last_para, diRun);
4279 assert(last_para);
4280 assert(last_para->member.run.nFlags & MERF_ENDPARA);
4281 if (editor->bEmulateVersion10 && prev_para &&
4282 last_para->member.run.nCharOfs == 0 &&
4283 prev_para->member.run.len == 1 &&
4284 *get_text( &prev_para->member.run, 0 ) == '\r')
4286 /* In 1.0 emulation, the last solitary \r at the very end of the text
4287 (if one exists) is NOT a line break.
4288 FIXME: this is an ugly hack. This should have a more regular model. */
4289 nRows--;
4292 TRACE("EM_GETLINECOUNT: nRows==%d\n", nRows);
4293 return max(1, nRows);
4295 case EM_LINEFROMCHAR:
4297 if (wParam == -1)
4298 return ME_RowNumberFromCharOfs(editor, ME_GetCursorOfs(&editor->pCursors[1]));
4299 else
4300 return ME_RowNumberFromCharOfs(editor, wParam);
4302 case EM_EXLINEFROMCHAR:
4304 if (lParam == -1)
4305 return ME_RowNumberFromCharOfs(editor, ME_GetCursorOfs(&editor->pCursors[1]));
4306 else
4307 return ME_RowNumberFromCharOfs(editor, lParam);
4309 case EM_LINEINDEX:
4311 ME_DisplayItem *item, *para;
4312 int nCharOfs;
4314 if (wParam == -1)
4315 item = ME_FindItemBack(editor->pCursors[0].pRun, diStartRow);
4316 else
4317 item = ME_FindRowWithNumber(editor, wParam);
4318 if (!item)
4319 return -1;
4320 para = ME_GetParagraph(item);
4321 item = ME_FindItemFwd(item, diRun);
4322 nCharOfs = para->member.para.nCharOfs + item->member.run.nCharOfs;
4323 TRACE("EM_LINEINDEX: nCharOfs==%d\n", nCharOfs);
4324 return nCharOfs;
4326 case EM_LINELENGTH:
4328 ME_DisplayItem *item, *item_end;
4329 int nChars = 0, nThisLineOfs = 0, nNextLineOfs = 0;
4330 ME_Cursor cursor;
4332 if (wParam > ME_GetTextLength(editor))
4333 return 0;
4334 if (wParam == -1)
4336 FIXME("EM_LINELENGTH: returning number of unselected characters on lines with selection unsupported.\n");
4337 return 0;
4339 cursor_from_char_ofs( editor, wParam, &cursor );
4340 item = ME_RowStart( cursor.pRun );
4341 nThisLineOfs = run_char_ofs( &ME_FindItemFwd( item, diRun )->member.run, 0 );
4342 item_end = ME_FindItemFwd(item, diStartRowOrParagraphOrEnd);
4343 if (item_end->type == diStartRow)
4344 nNextLineOfs = run_char_ofs( &ME_FindItemFwd( item_end, diRun )->member.run, 0 );
4345 else
4347 ME_DisplayItem *endRun = ME_FindItemBack(item_end, diRun);
4348 assert(endRun && endRun->member.run.nFlags & MERF_ENDPARA);
4349 nNextLineOfs = run_char_ofs( &endRun->member.run, 0 );
4351 nChars = nNextLineOfs - nThisLineOfs;
4352 TRACE("EM_LINELENGTH(%ld)==%d\n", wParam, nChars);
4353 return nChars;
4355 case EM_EXLIMITTEXT:
4357 if ((int)lParam < 0)
4358 return 0;
4359 if (lParam == 0)
4360 editor->nTextLimit = 65536;
4361 else
4362 editor->nTextLimit = (int) lParam;
4363 return 0;
4365 case EM_LIMITTEXT:
4367 if (wParam == 0)
4368 editor->nTextLimit = 65536;
4369 else
4370 editor->nTextLimit = (int) wParam;
4371 return 0;
4373 case EM_GETLIMITTEXT:
4375 return editor->nTextLimit;
4377 case EM_FINDTEXT:
4379 LRESULT r;
4380 if(!unicode){
4381 FINDTEXTA *ft = (FINDTEXTA *)lParam;
4382 int nChars = MultiByteToWideChar(CP_ACP, 0, ft->lpstrText, -1, NULL, 0);
4383 WCHAR *tmp;
4385 if ((tmp = heap_alloc(nChars * sizeof(*tmp))) != NULL)
4386 MultiByteToWideChar(CP_ACP, 0, ft->lpstrText, -1, tmp, nChars);
4387 r = ME_FindText(editor, wParam, &ft->chrg, tmp, NULL);
4388 heap_free(tmp);
4389 }else{
4390 FINDTEXTW *ft = (FINDTEXTW *)lParam;
4391 r = ME_FindText(editor, wParam, &ft->chrg, ft->lpstrText, NULL);
4393 return r;
4395 case EM_FINDTEXTEX:
4397 LRESULT r;
4398 if(!unicode){
4399 FINDTEXTEXA *ex = (FINDTEXTEXA *)lParam;
4400 int nChars = MultiByteToWideChar(CP_ACP, 0, ex->lpstrText, -1, NULL, 0);
4401 WCHAR *tmp;
4403 if ((tmp = heap_alloc(nChars * sizeof(*tmp))) != NULL)
4404 MultiByteToWideChar(CP_ACP, 0, ex->lpstrText, -1, tmp, nChars);
4405 r = ME_FindText(editor, wParam, &ex->chrg, tmp, &ex->chrgText);
4406 heap_free(tmp);
4407 }else{
4408 FINDTEXTEXW *ex = (FINDTEXTEXW *)lParam;
4409 r = ME_FindText(editor, wParam, &ex->chrg, ex->lpstrText, &ex->chrgText);
4411 return r;
4413 case EM_FINDTEXTW:
4415 FINDTEXTW *ft = (FINDTEXTW *)lParam;
4416 return ME_FindText(editor, wParam, &ft->chrg, ft->lpstrText, NULL);
4418 case EM_FINDTEXTEXW:
4420 FINDTEXTEXW *ex = (FINDTEXTEXW *)lParam;
4421 return ME_FindText(editor, wParam, &ex->chrg, ex->lpstrText, &ex->chrgText);
4423 case EM_GETZOOM:
4424 if (!wParam || !lParam)
4425 return FALSE;
4426 *(int *)wParam = editor->nZoomNumerator;
4427 *(int *)lParam = editor->nZoomDenominator;
4428 return TRUE;
4429 case EM_SETZOOM:
4430 return ME_SetZoom(editor, wParam, lParam);
4431 case EM_CHARFROMPOS:
4433 ME_Cursor cursor;
4434 if (ME_CharFromPos(editor, ((POINTL *)lParam)->x, ((POINTL *)lParam)->y,
4435 &cursor, NULL))
4436 return ME_GetCursorOfs(&cursor);
4437 else
4438 return -1;
4440 case EM_POSFROMCHAR:
4442 ME_Cursor cursor;
4443 int nCharOfs, nLength;
4444 POINTL pt = {0,0};
4446 nCharOfs = wParam;
4447 /* detect which API version we're dealing with */
4448 if (wParam >= 0x40000)
4449 nCharOfs = lParam;
4450 nLength = ME_GetTextLength(editor);
4451 nCharOfs = min(nCharOfs, nLength);
4452 nCharOfs = max(nCharOfs, 0);
4454 cursor_from_char_ofs( editor, nCharOfs, &cursor );
4455 pt.y = cursor.pRun->member.run.pt.y;
4456 pt.x = cursor.pRun->member.run.pt.x +
4457 ME_PointFromChar( editor, &cursor.pRun->member.run, cursor.nOffset, TRUE );
4458 pt.y += cursor.pPara->member.para.pt.y + editor->rcFormat.top;
4459 pt.x += editor->rcFormat.left;
4461 pt.x -= editor->horz_si.nPos;
4462 pt.y -= editor->vert_si.nPos;
4464 if (wParam >= 0x40000) {
4465 *(POINTL *)wParam = pt;
4467 return (wParam >= 0x40000) ? 0 : MAKELONG( pt.x, pt.y );
4469 case WM_CREATE:
4470 return ME_WmCreate(editor, lParam, unicode);
4471 case WM_DESTROY:
4472 ME_DestroyEditor(editor);
4473 return 0;
4474 case WM_SETCURSOR:
4476 POINT cursor_pos;
4477 if (wParam == (WPARAM)editor->hWnd && GetCursorPos(&cursor_pos) &&
4478 ScreenToClient(editor->hWnd, &cursor_pos))
4479 ME_LinkNotify(editor, msg, 0, MAKELPARAM(cursor_pos.x, cursor_pos.y));
4480 return ME_SetCursor(editor);
4482 case WM_LBUTTONDBLCLK:
4483 case WM_LBUTTONDOWN:
4485 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4486 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4487 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4488 return 0;
4489 ITextHost_TxSetFocus(editor->texthost);
4490 ME_LButtonDown(editor, (short)LOWORD(lParam), (short)HIWORD(lParam),
4491 ME_CalculateClickCount(editor, msg, wParam, lParam));
4492 ITextHost_TxSetCapture(editor->texthost, TRUE);
4493 editor->bMouseCaptured = TRUE;
4494 ME_LinkNotify(editor, msg, wParam, lParam);
4495 if (!ME_SetCursor(editor)) goto do_default;
4496 break;
4498 case WM_MOUSEMOVE:
4499 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4500 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4501 return 0;
4502 if (editor->bMouseCaptured)
4503 ME_MouseMove(editor, (short)LOWORD(lParam), (short)HIWORD(lParam));
4504 else
4505 ME_LinkNotify(editor, msg, wParam, lParam);
4506 /* Set cursor if mouse is captured, since WM_SETCURSOR won't be received. */
4507 if (editor->bMouseCaptured)
4508 ME_SetCursor(editor);
4509 break;
4510 case WM_LBUTTONUP:
4511 if (editor->bMouseCaptured) {
4512 ITextHost_TxSetCapture(editor->texthost, FALSE);
4513 editor->bMouseCaptured = FALSE;
4515 if (editor->nSelectionType == stDocument)
4516 editor->nSelectionType = stPosition;
4517 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4518 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4519 return 0;
4520 else
4522 ME_SetCursor(editor);
4523 ME_LinkNotify(editor, msg, wParam, lParam);
4525 break;
4526 case WM_RBUTTONUP:
4527 case WM_RBUTTONDOWN:
4528 case WM_RBUTTONDBLCLK:
4529 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4530 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4531 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4532 return 0;
4533 ME_LinkNotify(editor, msg, wParam, lParam);
4534 goto do_default;
4535 case WM_CONTEXTMENU:
4536 if (!ME_ShowContextMenu(editor, (short)LOWORD(lParam), (short)HIWORD(lParam)))
4537 goto do_default;
4538 break;
4539 case WM_SETFOCUS:
4540 editor->bHaveFocus = TRUE;
4541 create_caret(editor);
4542 update_caret(editor);
4543 ME_SendOldNotify(editor, EN_SETFOCUS);
4544 if (!editor->bHideSelection && !(editor->styleFlags & ES_NOHIDESEL))
4545 ME_InvalidateSelection( editor );
4546 return 0;
4547 case WM_KILLFOCUS:
4548 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4549 editor->bHaveFocus = FALSE;
4550 editor->wheel_remain = 0;
4551 hide_caret(editor);
4552 DestroyCaret();
4553 ME_SendOldNotify(editor, EN_KILLFOCUS);
4554 if (!editor->bHideSelection && !(editor->styleFlags & ES_NOHIDESEL))
4555 ME_InvalidateSelection( editor );
4556 return 0;
4557 case WM_COMMAND:
4558 TRACE("editor wnd command = %d\n", LOWORD(wParam));
4559 return 0;
4560 case WM_KEYUP:
4561 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4562 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4563 return 0;
4564 goto do_default;
4565 case WM_KEYDOWN:
4566 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4567 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4568 return 0;
4569 if (ME_KeyDown(editor, LOWORD(wParam)))
4570 return 0;
4571 goto do_default;
4572 case WM_CHAR:
4573 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4574 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4575 return 0;
4576 return ME_Char(editor, wParam, lParam, unicode);
4577 case WM_UNICHAR:
4578 if (unicode)
4580 if(wParam == UNICODE_NOCHAR) return TRUE;
4581 if(wParam <= 0x000fffff)
4583 if(wParam > 0xffff) /* convert to surrogates */
4585 wParam -= 0x10000;
4586 ME_Char(editor, (wParam >> 10) + 0xd800, 0, TRUE);
4587 ME_Char(editor, (wParam & 0x03ff) + 0xdc00, 0, TRUE);
4588 } else {
4589 ME_Char(editor, wParam, 0, TRUE);
4592 return 0;
4594 break;
4595 case EM_STOPGROUPTYPING:
4596 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4597 return 0;
4598 case WM_HSCROLL:
4600 const int scrollUnit = 7;
4602 switch(LOWORD(wParam))
4604 case SB_LEFT:
4605 ME_ScrollAbs(editor, 0, 0);
4606 break;
4607 case SB_RIGHT:
4608 ME_ScrollAbs(editor,
4609 editor->horz_si.nMax - (int)editor->horz_si.nPage,
4610 editor->vert_si.nMax - (int)editor->vert_si.nPage);
4611 break;
4612 case SB_LINELEFT:
4613 ME_ScrollLeft(editor, scrollUnit);
4614 break;
4615 case SB_LINERIGHT:
4616 ME_ScrollRight(editor, scrollUnit);
4617 break;
4618 case SB_PAGELEFT:
4619 ME_ScrollLeft(editor, editor->sizeWindow.cx);
4620 break;
4621 case SB_PAGERIGHT:
4622 ME_ScrollRight(editor, editor->sizeWindow.cx);
4623 break;
4624 case SB_THUMBTRACK:
4625 case SB_THUMBPOSITION:
4627 int pos = HIWORD(wParam);
4628 if (editor->horz_si.nMax > 0xffff)
4629 pos = MulDiv(pos, editor->horz_si.nMax, 0xffff);
4630 ME_HScrollAbs(editor, pos);
4631 break;
4634 break;
4636 case EM_SCROLL: /* fall through */
4637 case WM_VSCROLL:
4639 int origNPos;
4640 int lineHeight = get_default_line_height( editor );
4642 origNPos = editor->vert_si.nPos;
4644 switch(LOWORD(wParam))
4646 case SB_TOP:
4647 ME_ScrollAbs(editor, 0, 0);
4648 break;
4649 case SB_BOTTOM:
4650 ME_ScrollAbs(editor,
4651 editor->horz_si.nMax - (int)editor->horz_si.nPage,
4652 editor->vert_si.nMax - (int)editor->vert_si.nPage);
4653 break;
4654 case SB_LINEUP:
4655 ME_ScrollUp(editor,lineHeight);
4656 break;
4657 case SB_LINEDOWN:
4658 ME_ScrollDown(editor,lineHeight);
4659 break;
4660 case SB_PAGEUP:
4661 ME_ScrollUp(editor,editor->sizeWindow.cy);
4662 break;
4663 case SB_PAGEDOWN:
4664 ME_ScrollDown(editor,editor->sizeWindow.cy);
4665 break;
4666 case SB_THUMBTRACK:
4667 case SB_THUMBPOSITION:
4669 int pos = HIWORD(wParam);
4670 if (editor->vert_si.nMax > 0xffff)
4671 pos = MulDiv(pos, editor->vert_si.nMax, 0xffff);
4672 ME_VScrollAbs(editor, pos);
4673 break;
4676 if (msg == EM_SCROLL)
4677 return 0x00010000 | (((editor->vert_si.nPos - origNPos)/lineHeight) & 0xffff);
4678 break;
4680 case WM_MOUSEWHEEL:
4682 int delta;
4683 BOOL ctrl_is_down;
4685 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4686 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4687 return 0;
4689 ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
4691 delta = GET_WHEEL_DELTA_WPARAM(wParam);
4693 /* if scrolling changes direction, ignore left overs */
4694 if ((delta < 0 && editor->wheel_remain < 0) ||
4695 (delta > 0 && editor->wheel_remain > 0))
4696 editor->wheel_remain += delta;
4697 else
4698 editor->wheel_remain = delta;
4700 if (editor->wheel_remain)
4702 if (ctrl_is_down) {
4703 int numerator;
4704 if (!editor->nZoomNumerator || !editor->nZoomDenominator)
4706 numerator = 100;
4707 } else {
4708 numerator = editor->nZoomNumerator * 100 / editor->nZoomDenominator;
4710 numerator += calc_wheel_change( &editor->wheel_remain, 10 );
4711 if (numerator >= 10 && numerator <= 500)
4712 ME_SetZoom(editor, numerator, 100);
4713 } else {
4714 UINT max_lines = 3;
4715 int lines = 0;
4717 SystemParametersInfoW( SPI_GETWHEELSCROLLLINES, 0, &max_lines, 0 );
4718 if (max_lines)
4719 lines = calc_wheel_change( &editor->wheel_remain, (int)max_lines );
4720 if (lines)
4721 ME_ScrollDown( editor, -lines * get_default_line_height( editor ) );
4724 break;
4726 case EM_GETRECT:
4728 *((RECT *)lParam) = editor->rcFormat;
4729 if (editor->bDefaultFormatRect)
4730 ((RECT *)lParam)->left -= editor->selofs;
4731 return 0;
4733 case EM_SETRECT:
4734 case EM_SETRECTNP:
4736 if (lParam)
4738 int border = 0;
4739 RECT clientRect;
4740 RECT *rc = (RECT *)lParam;
4742 border = editor->exStyleFlags & WS_EX_CLIENTEDGE ? 1 : 0;
4743 ITextHost_TxGetClientRect(editor->texthost, &clientRect);
4744 if (wParam == 0)
4746 editor->rcFormat.top = max(0, rc->top - border);
4747 editor->rcFormat.left = max(0, rc->left - border);
4748 editor->rcFormat.bottom = min(clientRect.bottom, rc->bottom);
4749 editor->rcFormat.right = min(clientRect.right, rc->right + border);
4750 } else if (wParam == 1) {
4751 /* MSDN incorrectly says a wParam value of 1 causes the
4752 * lParam rect to be used as a relative offset,
4753 * however, the tests show it just prevents min/max bound
4754 * checking. */
4755 editor->rcFormat.top = rc->top - border;
4756 editor->rcFormat.left = rc->left - border;
4757 editor->rcFormat.bottom = rc->bottom;
4758 editor->rcFormat.right = rc->right + border;
4759 } else {
4760 return 0;
4762 editor->bDefaultFormatRect = FALSE;
4764 else
4766 ME_SetDefaultFormatRect(editor);
4767 editor->bDefaultFormatRect = TRUE;
4769 ME_MarkAllForWrapping(editor);
4770 ME_WrapMarkedParagraphs(editor);
4771 ME_UpdateScrollBar(editor);
4772 if (msg != EM_SETRECTNP)
4773 ME_Repaint(editor);
4774 return 0;
4776 case EM_REQUESTRESIZE:
4777 ME_SendRequestResize(editor, TRUE);
4778 return 0;
4779 case WM_SETREDRAW:
4780 goto do_default;
4781 case WM_WINDOWPOSCHANGED:
4783 RECT clientRect;
4784 WINDOWPOS *winpos = (WINDOWPOS *)lParam;
4786 if (winpos->flags & SWP_NOCLIENTSIZE) goto do_default;
4787 ITextHost_TxGetClientRect(editor->texthost, &clientRect);
4788 if (editor->bDefaultFormatRect) {
4789 ME_SetDefaultFormatRect(editor);
4790 } else {
4791 editor->rcFormat.right += clientRect.right - editor->prevClientRect.right;
4792 editor->rcFormat.bottom += clientRect.bottom - editor->prevClientRect.bottom;
4794 editor->prevClientRect = clientRect;
4795 ME_RewrapRepaint(editor);
4796 goto do_default;
4798 /* IME messages to make richedit controls IME aware */
4799 case WM_IME_SETCONTEXT:
4800 case WM_IME_CONTROL:
4801 case WM_IME_SELECT:
4802 case WM_IME_COMPOSITIONFULL:
4803 return 0;
4804 case WM_IME_STARTCOMPOSITION:
4806 editor->imeStartIndex=ME_GetCursorOfs(&editor->pCursors[0]);
4807 ME_DeleteSelection(editor);
4808 ME_CommitUndo(editor);
4809 ME_UpdateRepaint(editor, FALSE);
4810 return 0;
4812 case WM_IME_COMPOSITION:
4814 HIMC hIMC;
4816 ME_Style *style = style_get_insert_style( editor, editor->pCursors );
4817 hIMC = ITextHost_TxImmGetContext(editor->texthost);
4818 ME_DeleteSelection(editor);
4819 ME_SaveTempStyle(editor, style);
4820 if (lParam & (GCS_RESULTSTR|GCS_COMPSTR))
4822 LPWSTR lpCompStr = NULL;
4823 DWORD dwBufLen;
4824 DWORD dwIndex = lParam & GCS_RESULTSTR;
4825 if (!dwIndex)
4826 dwIndex = GCS_COMPSTR;
4828 dwBufLen = ImmGetCompositionStringW(hIMC, dwIndex, NULL, 0);
4829 lpCompStr = HeapAlloc(GetProcessHeap(),0,dwBufLen + sizeof(WCHAR));
4830 ImmGetCompositionStringW(hIMC, dwIndex, lpCompStr, dwBufLen);
4831 lpCompStr[dwBufLen/sizeof(WCHAR)] = 0;
4832 ME_InsertTextFromCursor(editor,0,lpCompStr,dwBufLen/sizeof(WCHAR),style);
4833 HeapFree(GetProcessHeap(), 0, lpCompStr);
4835 if (dwIndex == GCS_COMPSTR)
4836 set_selection_cursors(editor,editor->imeStartIndex,
4837 editor->imeStartIndex + dwBufLen/sizeof(WCHAR));
4839 ME_ReleaseStyle(style);
4840 ME_CommitUndo(editor);
4841 ME_UpdateRepaint(editor, FALSE);
4842 return 0;
4844 case WM_IME_ENDCOMPOSITION:
4846 ME_DeleteSelection(editor);
4847 editor->imeStartIndex=-1;
4848 return 0;
4850 case EM_GETOLEINTERFACE:
4852 if (!editor->reOle)
4853 if (!CreateIRichEditOle(NULL, editor, (LPVOID *)&editor->reOle))
4854 return 0;
4855 if (IUnknown_QueryInterface(editor->reOle, &IID_IRichEditOle, (LPVOID *)lParam) == S_OK)
4856 return 1;
4857 return 0;
4859 case EM_GETPASSWORDCHAR:
4861 return editor->cPasswordMask;
4863 case EM_SETOLECALLBACK:
4864 if(editor->lpOleCallback)
4865 IRichEditOleCallback_Release(editor->lpOleCallback);
4866 editor->lpOleCallback = (IRichEditOleCallback*)lParam;
4867 if(editor->lpOleCallback)
4868 IRichEditOleCallback_AddRef(editor->lpOleCallback);
4869 return TRUE;
4870 case EM_GETWORDBREAKPROC:
4871 return (LRESULT)editor->pfnWordBreak;
4872 case EM_SETWORDBREAKPROC:
4874 EDITWORDBREAKPROCW pfnOld = editor->pfnWordBreak;
4876 editor->pfnWordBreak = (EDITWORDBREAKPROCW)lParam;
4877 return (LRESULT)pfnOld;
4879 case EM_GETTEXTMODE:
4880 return editor->mode;
4881 case EM_SETTEXTMODE:
4883 int mask = 0;
4884 int changes = 0;
4886 if (ME_GetTextLength(editor) ||
4887 !list_empty( &editor->undo_stack ) || !list_empty( &editor->redo_stack ))
4888 return E_UNEXPECTED;
4890 /* Check for mutually exclusive flags in adjacent bits of wParam */
4891 if ((wParam & (TM_RICHTEXT | TM_MULTILEVELUNDO | TM_MULTICODEPAGE)) &
4892 (wParam & (TM_PLAINTEXT | TM_SINGLELEVELUNDO | TM_SINGLECODEPAGE)) << 1)
4893 return E_INVALIDARG;
4895 if (wParam & (TM_RICHTEXT | TM_PLAINTEXT))
4897 mask |= TM_RICHTEXT | TM_PLAINTEXT;
4898 changes |= wParam & (TM_RICHTEXT | TM_PLAINTEXT);
4899 if (wParam & TM_PLAINTEXT) {
4900 /* Clear selection since it should be possible to select the
4901 * end of text run for rich text */
4902 ME_InvalidateSelection(editor);
4903 ME_SetCursorToStart(editor, &editor->pCursors[0]);
4904 editor->pCursors[1] = editor->pCursors[0];
4905 /* plain text can only have the default style. */
4906 ME_ClearTempStyle(editor);
4907 ME_AddRefStyle(editor->pBuffer->pDefaultStyle);
4908 ME_ReleaseStyle(editor->pCursors[0].pRun->member.run.style);
4909 editor->pCursors[0].pRun->member.run.style = editor->pBuffer->pDefaultStyle;
4912 /* FIXME: Currently no support for undo level and code page options */
4913 editor->mode = (editor->mode & ~mask) | changes;
4914 return 0;
4916 case EM_SETPASSWORDCHAR:
4918 editor->cPasswordMask = wParam;
4919 ME_RewrapRepaint(editor);
4920 return 0;
4922 case EM_SETTARGETDEVICE:
4923 if (wParam == 0)
4925 BOOL new = (lParam == 0 && (editor->styleFlags & ES_MULTILINE));
4926 if (editor->nAvailWidth || editor->bWordWrap != new)
4928 editor->bWordWrap = new;
4929 editor->nAvailWidth = 0; /* wrap to client area */
4930 ME_RewrapRepaint(editor);
4932 } else {
4933 int width = max(0, lParam);
4934 if ((editor->styleFlags & ES_MULTILINE) &&
4935 (!editor->bWordWrap || editor->nAvailWidth != width))
4937 editor->nAvailWidth = width;
4938 editor->bWordWrap = TRUE;
4939 ME_RewrapRepaint(editor);
4941 FIXME("EM_SETTARGETDEVICE doesn't use non-NULL target devices\n");
4943 return TRUE;
4944 default:
4945 do_default:
4946 *phresult = S_FALSE;
4947 break;
4949 return 0L;
4952 static BOOL create_windowed_editor(HWND hwnd, CREATESTRUCTW *create, BOOL emulate_10)
4954 ITextHost *host = ME_CreateTextHost( hwnd, create, emulate_10 );
4955 ME_TextEditor *editor;
4957 if (!host) return FALSE;
4959 editor = ME_MakeEditor( host, emulate_10 );
4960 if (!editor)
4962 ITextHost_Release( host );
4963 return FALSE;
4966 editor->exStyleFlags = GetWindowLongW( hwnd, GWL_EXSTYLE );
4967 editor->styleFlags |= GetWindowLongW( hwnd, GWL_STYLE ) & ES_WANTRETURN;
4968 editor->hWnd = hwnd; /* FIXME: Remove editor's dependence on hWnd */
4969 editor->hwndParent = create->hwndParent;
4971 SetWindowLongPtrW( hwnd, 0, (LONG_PTR)editor );
4973 return TRUE;
4976 static LRESULT RichEditWndProc_common(HWND hWnd, UINT msg, WPARAM wParam,
4977 LPARAM lParam, BOOL unicode)
4979 ME_TextEditor *editor;
4980 HRESULT hresult;
4981 LRESULT lresult = 0;
4983 TRACE("enter hwnd %p msg %04x (%s) %lx %lx, unicode %d\n",
4984 hWnd, msg, get_msg_name(msg), wParam, lParam, unicode);
4986 editor = (ME_TextEditor *)GetWindowLongPtrW(hWnd, 0);
4987 if (!editor)
4989 if (msg == WM_NCCREATE)
4991 CREATESTRUCTW *pcs = (CREATESTRUCTW *)lParam;
4993 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd, pcs->style);
4994 return create_windowed_editor( hWnd, pcs, FALSE );
4996 else
4998 return DefWindowProcW(hWnd, msg, wParam, lParam);
5002 switch (msg)
5004 case WM_PAINT:
5006 HDC hdc;
5007 RECT rc;
5008 PAINTSTRUCT ps;
5009 HBRUSH old_brush;
5011 update_caret(editor);
5012 hdc = BeginPaint(editor->hWnd, &ps);
5013 if (!editor->bEmulateVersion10 || (editor->nEventMask & ENM_UPDATE))
5014 ME_SendOldNotify(editor, EN_UPDATE);
5015 old_brush = SelectObject(hdc, editor->hbrBackground);
5017 /* Erase area outside of the formatting rectangle */
5018 if (ps.rcPaint.top < editor->rcFormat.top)
5020 rc = ps.rcPaint;
5021 rc.bottom = editor->rcFormat.top;
5022 PatBlt(hdc, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, PATCOPY);
5023 ps.rcPaint.top = editor->rcFormat.top;
5025 if (ps.rcPaint.bottom > editor->rcFormat.bottom) {
5026 rc = ps.rcPaint;
5027 rc.top = editor->rcFormat.bottom;
5028 PatBlt(hdc, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, PATCOPY);
5029 ps.rcPaint.bottom = editor->rcFormat.bottom;
5031 if (ps.rcPaint.left < editor->rcFormat.left) {
5032 rc = ps.rcPaint;
5033 rc.right = editor->rcFormat.left;
5034 PatBlt(hdc, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, PATCOPY);
5035 ps.rcPaint.left = editor->rcFormat.left;
5037 if (ps.rcPaint.right > editor->rcFormat.right) {
5038 rc = ps.rcPaint;
5039 rc.left = editor->rcFormat.right;
5040 PatBlt(hdc, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, PATCOPY);
5041 ps.rcPaint.right = editor->rcFormat.right;
5044 ME_PaintContent(editor, hdc, &ps.rcPaint);
5045 SelectObject(hdc, old_brush);
5046 EndPaint(editor->hWnd, &ps);
5047 return 0;
5049 case WM_ERASEBKGND:
5051 HDC hDC = (HDC)wParam;
5052 RECT rc;
5054 if (GetUpdateRect(editor->hWnd, &rc, TRUE))
5055 FillRect(hDC, &rc, editor->hbrBackground);
5056 return 1;
5058 case EM_SETOPTIONS:
5060 DWORD dwStyle;
5061 const DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
5062 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN |
5063 ECO_SELECTIONBAR;
5064 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
5065 dwStyle = GetWindowLongW(hWnd, GWL_STYLE);
5066 dwStyle = (dwStyle & ~mask) | (lresult & mask);
5067 SetWindowLongW(hWnd, GWL_STYLE, dwStyle);
5068 return lresult;
5070 case EM_SETREADONLY:
5072 DWORD dwStyle;
5073 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
5074 dwStyle = GetWindowLongW(hWnd, GWL_STYLE);
5075 dwStyle &= ~ES_READONLY;
5076 if (wParam)
5077 dwStyle |= ES_READONLY;
5078 SetWindowLongW(hWnd, GWL_STYLE, dwStyle);
5079 return lresult;
5081 default:
5082 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
5085 if (hresult == S_FALSE)
5086 lresult = DefWindowProcW(hWnd, msg, wParam, lParam);
5088 TRACE("exit hwnd %p msg %04x (%s) %lx %lx, unicode %d -> %lu\n",
5089 hWnd, msg, get_msg_name(msg), wParam, lParam, unicode, lresult);
5091 return lresult;
5094 static LRESULT WINAPI RichEditWndProcW(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5096 BOOL unicode = TRUE;
5098 /* Under Win9x RichEdit20W returns ANSI strings, see the tests. */
5099 if (msg == WM_GETTEXT && (GetVersion() & 0x80000000))
5100 unicode = FALSE;
5102 return RichEditWndProc_common(hWnd, msg, wParam, lParam, unicode);
5105 static LRESULT WINAPI RichEditWndProcA(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5107 return RichEditWndProc_common(hWnd, msg, wParam, lParam, FALSE);
5110 /******************************************************************
5111 * RichEditANSIWndProc (RICHED20.10)
5113 LRESULT WINAPI RichEditANSIWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5115 return RichEditWndProcA(hWnd, msg, wParam, lParam);
5118 /******************************************************************
5119 * RichEdit10ANSIWndProc (RICHED20.9)
5121 LRESULT WINAPI RichEdit10ANSIWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5123 if (msg == WM_NCCREATE && !GetWindowLongPtrW(hWnd, 0))
5125 CREATESTRUCTW *pcs = (CREATESTRUCTW *)lParam;
5127 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd, pcs->style);
5128 return create_windowed_editor( hWnd, pcs, TRUE );
5130 return RichEditANSIWndProc(hWnd, msg, wParam, lParam);
5133 void ME_SendOldNotify(ME_TextEditor *editor, int nCode)
5135 ITextHost_TxNotify(editor->texthost, nCode, NULL);
5138 /* Fill buffer with srcChars unicode characters from the start cursor.
5140 * buffer: destination buffer
5141 * buflen: length of buffer in characters excluding the NULL terminator.
5142 * start: start of editor text to copy into buffer.
5143 * srcChars: Number of characters to use from the editor text.
5144 * bCRLF: if true, replaces all end of lines with \r\n pairs.
5146 * returns the number of characters written excluding the NULL terminator.
5148 * The written text is always NULL terminated.
5150 int ME_GetTextW(ME_TextEditor *editor, WCHAR *buffer, int buflen,
5151 const ME_Cursor *start, int srcChars, BOOL bCRLF,
5152 BOOL bEOP)
5154 ME_DisplayItem *pRun, *pNextRun;
5155 const WCHAR *pStart = buffer;
5156 const WCHAR cr_lf[] = {'\r', '\n', 0};
5157 const WCHAR *str;
5158 int nLen;
5160 /* bCRLF flag is only honored in 2.0 and up. 1.0 must always return text verbatim */
5161 if (editor->bEmulateVersion10) bCRLF = FALSE;
5163 pRun = start->pRun;
5164 assert(pRun);
5165 pNextRun = ME_FindItemFwd(pRun, diRun);
5167 nLen = pRun->member.run.len - start->nOffset;
5168 str = get_text( &pRun->member.run, start->nOffset );
5170 while (srcChars && buflen && pNextRun)
5172 int nFlags = pRun->member.run.nFlags;
5174 if (bCRLF && nFlags & MERF_ENDPARA && ~nFlags & MERF_ENDCELL)
5176 if (buflen == 1) break;
5177 /* FIXME: native fails to reduce srcChars here for WM_GETTEXT or
5178 * EM_GETTEXTEX, however, this is done for copying text which
5179 * also uses this function. */
5180 srcChars -= min(nLen, srcChars);
5181 nLen = 2;
5182 str = cr_lf;
5183 } else {
5184 nLen = min(nLen, srcChars);
5185 srcChars -= nLen;
5188 nLen = min(nLen, buflen);
5189 buflen -= nLen;
5191 CopyMemory(buffer, str, sizeof(WCHAR) * nLen);
5193 buffer += nLen;
5195 pRun = pNextRun;
5196 pNextRun = ME_FindItemFwd(pRun, diRun);
5198 nLen = pRun->member.run.len;
5199 str = get_text( &pRun->member.run, 0 );
5201 /* append '\r' to the last paragraph. */
5202 if (pRun->next->type == diTextEnd && bEOP)
5204 *buffer = '\r';
5205 buffer ++;
5207 *buffer = 0;
5208 return buffer - pStart;
5211 static BOOL ME_RegisterEditorClass(HINSTANCE hInstance)
5213 WNDCLASSW wcW;
5214 WNDCLASSA wcA;
5216 wcW.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS;
5217 wcW.lpfnWndProc = RichEditWndProcW;
5218 wcW.cbClsExtra = 0;
5219 wcW.cbWndExtra = sizeof(ME_TextEditor *);
5220 wcW.hInstance = NULL; /* hInstance would register DLL-local class */
5221 wcW.hIcon = NULL;
5222 wcW.hCursor = LoadCursorW(NULL, (LPWSTR)IDC_IBEAM);
5223 wcW.hbrBackground = GetStockObject(NULL_BRUSH);
5224 wcW.lpszMenuName = NULL;
5226 if (is_version_nt())
5228 wcW.lpszClassName = RICHEDIT_CLASS20W;
5229 if (!RegisterClassW(&wcW)) return FALSE;
5230 wcW.lpszClassName = MSFTEDIT_CLASS;
5231 if (!RegisterClassW(&wcW)) return FALSE;
5233 else
5235 /* WNDCLASSA/W have the same layout */
5236 wcW.lpszClassName = (LPCWSTR)"RichEdit20W";
5237 if (!RegisterClassA((WNDCLASSA *)&wcW)) return FALSE;
5238 wcW.lpszClassName = (LPCWSTR)"RichEdit50W";
5239 if (!RegisterClassA((WNDCLASSA *)&wcW)) return FALSE;
5242 wcA.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS;
5243 wcA.lpfnWndProc = RichEditWndProcA;
5244 wcA.cbClsExtra = 0;
5245 wcA.cbWndExtra = sizeof(ME_TextEditor *);
5246 wcA.hInstance = NULL; /* hInstance would register DLL-local class */
5247 wcA.hIcon = NULL;
5248 wcA.hCursor = LoadCursorW(NULL, (LPWSTR)IDC_IBEAM);
5249 wcA.hbrBackground = GetStockObject(NULL_BRUSH);
5250 wcA.lpszMenuName = NULL;
5251 wcA.lpszClassName = RICHEDIT_CLASS20A;
5252 if (!RegisterClassA(&wcA)) return FALSE;
5253 wcA.lpszClassName = "RichEdit50A";
5254 if (!RegisterClassA(&wcA)) return FALSE;
5256 return TRUE;
5259 static LRESULT WINAPI REComboWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
5260 /* FIXME: Not implemented */
5261 TRACE("hWnd %p msg %04x (%s) %08lx %08lx\n",
5262 hWnd, msg, get_msg_name(msg), wParam, lParam);
5263 return DefWindowProcW(hWnd, msg, wParam, lParam);
5266 static LRESULT WINAPI REListWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
5267 /* FIXME: Not implemented */
5268 TRACE("hWnd %p msg %04x (%s) %08lx %08lx\n",
5269 hWnd, msg, get_msg_name(msg), wParam, lParam);
5270 return DefWindowProcW(hWnd, msg, wParam, lParam);
5273 /******************************************************************
5274 * REExtendedRegisterClass (RICHED20.8)
5276 * FIXME undocumented
5277 * Need to check for errors and implement controls and callbacks
5279 LRESULT WINAPI REExtendedRegisterClass(void)
5281 WNDCLASSW wcW;
5282 UINT result;
5284 FIXME("semi stub\n");
5286 wcW.cbClsExtra = 0;
5287 wcW.cbWndExtra = 4;
5288 wcW.hInstance = NULL;
5289 wcW.hIcon = NULL;
5290 wcW.hCursor = NULL;
5291 wcW.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
5292 wcW.lpszMenuName = NULL;
5294 if (!ME_ListBoxRegistered)
5296 wcW.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS;
5297 wcW.lpfnWndProc = REListWndProc;
5298 wcW.lpszClassName = REListBox20W;
5299 if (RegisterClassW(&wcW)) ME_ListBoxRegistered = TRUE;
5302 if (!ME_ComboBoxRegistered)
5304 wcW.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS | CS_VREDRAW | CS_HREDRAW;
5305 wcW.lpfnWndProc = REComboWndProc;
5306 wcW.lpszClassName = REComboBox20W;
5307 if (RegisterClassW(&wcW)) ME_ComboBoxRegistered = TRUE;
5310 result = 0;
5311 if (ME_ListBoxRegistered)
5312 result += 1;
5313 if (ME_ComboBoxRegistered)
5314 result += 2;
5316 return result;
5319 static int __cdecl wchar_comp( const void *key, const void *elem )
5321 return *(const WCHAR *)key - *(const WCHAR *)elem;
5324 /* neutral characters end the url if the next non-neutral character is a space character,
5325 otherwise they are included in the url. */
5326 static BOOL isurlneutral( WCHAR c )
5328 /* NB this list is sorted */
5329 static const WCHAR neutral_chars[] = {'!','\"','\'','(',')',',','-','.',':',';','<','>','?','[',']','{','}'};
5331 /* Some shortcuts */
5332 if (isalnum( c )) return FALSE;
5333 if (c > neutral_chars[ARRAY_SIZE( neutral_chars ) - 1]) return FALSE;
5335 return !!bsearch( &c, neutral_chars, ARRAY_SIZE( neutral_chars ), sizeof(c), wchar_comp );
5339 * This proc takes a selection, and scans it forward in order to select the span
5340 * of a possible URL candidate. A possible URL candidate must start with isalnum
5341 * or one of the following special characters: *|/\+%#@ and must consist entirely
5342 * of the characters allowed to start the URL, plus : (colon) which may occur
5343 * at most once, and not at either end.
5345 static BOOL ME_FindNextURLCandidate(ME_TextEditor *editor,
5346 const ME_Cursor *start,
5347 int nChars,
5348 ME_Cursor *candidate_min,
5349 ME_Cursor *candidate_max)
5351 ME_Cursor cursor = *start, neutral_end, space_end;
5352 BOOL candidateStarted = FALSE, quoted = FALSE;
5353 WCHAR c;
5355 while (nChars > 0)
5357 WCHAR *str = get_text( &cursor.pRun->member.run, 0 );
5358 int run_len = cursor.pRun->member.run.len;
5360 nChars -= run_len - cursor.nOffset;
5362 /* Find start of candidate */
5363 if (!candidateStarted)
5365 while (cursor.nOffset < run_len)
5367 c = str[cursor.nOffset];
5368 if (!iswspace( c ) && !isurlneutral( c ))
5370 *candidate_min = cursor;
5371 candidateStarted = TRUE;
5372 neutral_end.pPara = NULL;
5373 space_end.pPara = NULL;
5374 cursor.nOffset++;
5375 break;
5377 quoted = (c == '<');
5378 cursor.nOffset++;
5382 /* Find end of candidate */
5383 if (candidateStarted)
5385 while (cursor.nOffset < run_len)
5387 c = str[cursor.nOffset];
5388 if (iswspace( c ))
5390 if (quoted && c != '\r')
5392 if (!space_end.pPara)
5394 if (neutral_end.pPara)
5395 space_end = neutral_end;
5396 else
5397 space_end = cursor;
5400 else
5401 goto done;
5403 else if (isurlneutral( c ))
5405 if (quoted && c == '>')
5407 neutral_end.pPara = NULL;
5408 space_end.pPara = NULL;
5409 goto done;
5411 if (!neutral_end.pPara)
5412 neutral_end = cursor;
5414 else
5415 neutral_end.pPara = NULL;
5417 cursor.nOffset++;
5421 cursor.nOffset = 0;
5422 if (!ME_NextRun(&cursor.pPara, &cursor.pRun, TRUE))
5423 goto done;
5426 done:
5427 if (candidateStarted)
5429 if (space_end.pPara)
5430 *candidate_max = space_end;
5431 else if (neutral_end.pPara)
5432 *candidate_max = neutral_end;
5433 else
5434 *candidate_max = cursor;
5435 return TRUE;
5437 *candidate_max = *candidate_min = cursor;
5438 return FALSE;
5442 * This proc evaluates the selection and returns TRUE if it can be considered an URL
5444 static BOOL ME_IsCandidateAnURL(ME_TextEditor *editor, const ME_Cursor *start, int nChars)
5446 #define MAX_PREFIX_LEN 9
5447 struct prefix_s {
5448 const WCHAR text[MAX_PREFIX_LEN];
5449 int length;
5450 }prefixes[] = {
5451 {{'p','r','o','s','p','e','r','o',':'}, 9},
5452 {{'t','e','l','n','e','t',':'}, 7},
5453 {{'g','o','p','h','e','r',':'}, 7},
5454 {{'m','a','i','l','t','o',':'}, 7},
5455 {{'h','t','t','p','s',':'}, 6},
5456 {{'f','i','l','e',':'}, 5},
5457 {{'n','e','w','s',':'}, 5},
5458 {{'w','a','i','s',':'}, 5},
5459 {{'n','n','t','p',':'}, 5},
5460 {{'h','t','t','p',':'}, 5},
5461 {{'w','w','w','.'}, 4},
5462 {{'f','t','p',':'}, 4},
5464 WCHAR bufferW[MAX_PREFIX_LEN + 1];
5465 unsigned int i;
5467 ME_GetTextW(editor, bufferW, MAX_PREFIX_LEN, start, nChars, FALSE, FALSE);
5468 for (i = 0; i < ARRAY_SIZE(prefixes); i++)
5470 if (nChars < prefixes[i].length) continue;
5471 if (!memcmp(prefixes[i].text, bufferW, prefixes[i].length * sizeof(WCHAR)))
5472 return TRUE;
5474 return FALSE;
5475 #undef MAX_PREFIX_LEN
5479 * This proc walks through the indicated selection and evaluates whether each
5480 * section identified by ME_FindNextURLCandidate and in-between sections have
5481 * their proper CFE_LINK attributes set or unset. If the CFE_LINK attribute is
5482 * not what it is supposed to be, this proc sets or unsets it as appropriate.
5484 * Since this function can cause runs to be split, do not depend on the value
5485 * of the start cursor at the end of the function.
5487 * nChars may be set to INT_MAX to update to the end of the text.
5489 * Returns TRUE if at least one section was modified.
5491 static BOOL ME_UpdateLinkAttribute(ME_TextEditor *editor, ME_Cursor *start, int nChars)
5493 BOOL modified = FALSE;
5494 ME_Cursor startCur = *start;
5496 if (!editor->AutoURLDetect_bEnable) return FALSE;
5500 CHARFORMAT2W link;
5501 ME_Cursor candidateStart, candidateEnd;
5503 if (ME_FindNextURLCandidate(editor, &startCur, nChars,
5504 &candidateStart, &candidateEnd))
5506 /* Section before candidate is not an URL */
5507 int cMin = ME_GetCursorOfs(&candidateStart);
5508 int cMax = ME_GetCursorOfs(&candidateEnd);
5510 if (!ME_IsCandidateAnURL(editor, &candidateStart, cMax - cMin))
5511 candidateStart = candidateEnd;
5512 nChars -= cMax - ME_GetCursorOfs(&startCur);
5514 else
5516 /* No more candidates until end of selection */
5517 nChars = 0;
5520 if (startCur.pRun != candidateStart.pRun ||
5521 startCur.nOffset != candidateStart.nOffset)
5523 /* CFE_LINK effect should be consistently unset */
5524 link.cbSize = sizeof(link);
5525 ME_GetCharFormat(editor, &startCur, &candidateStart, &link);
5526 if (!(link.dwMask & CFM_LINK) || (link.dwEffects & CFE_LINK))
5528 /* CFE_LINK must be unset from this range */
5529 memset(&link, 0, sizeof(CHARFORMAT2W));
5530 link.cbSize = sizeof(link);
5531 link.dwMask = CFM_LINK;
5532 link.dwEffects = 0;
5533 ME_SetCharFormat(editor, &startCur, &candidateStart, &link);
5534 /* Update candidateEnd since setting character formats may split
5535 * runs, which can cause a cursor to be at an invalid offset within
5536 * a split run. */
5537 while (candidateEnd.nOffset >= candidateEnd.pRun->member.run.len)
5539 candidateEnd.nOffset -= candidateEnd.pRun->member.run.len;
5540 candidateEnd.pRun = ME_FindItemFwd(candidateEnd.pRun, diRun);
5542 modified = TRUE;
5545 if (candidateStart.pRun != candidateEnd.pRun ||
5546 candidateStart.nOffset != candidateEnd.nOffset)
5548 /* CFE_LINK effect should be consistently set */
5549 link.cbSize = sizeof(link);
5550 ME_GetCharFormat(editor, &candidateStart, &candidateEnd, &link);
5551 if (!(link.dwMask & CFM_LINK) || !(link.dwEffects & CFE_LINK))
5553 /* CFE_LINK must be set on this range */
5554 memset(&link, 0, sizeof(CHARFORMAT2W));
5555 link.cbSize = sizeof(link);
5556 link.dwMask = CFM_LINK;
5557 link.dwEffects = CFE_LINK;
5558 ME_SetCharFormat(editor, &candidateStart, &candidateEnd, &link);
5559 modified = TRUE;
5562 startCur = candidateEnd;
5563 } while (nChars > 0);
5564 return modified;