vbscript: Handle index read access to array properties.
[wine.git] / dlls / riched20 / editor.c
blob57601745ab280810575c38685375928e84fe3317
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_UpdateLinkAttribute(ME_TextEditor *editor, ME_Cursor *start, int nChars);
247 HINSTANCE dll_instance = NULL;
248 BOOL me_debug = FALSE;
250 static ME_TextBuffer *ME_MakeText(void) {
251 ME_TextBuffer *buf = heap_alloc(sizeof(*buf));
252 ME_DisplayItem *p1 = ME_MakeDI(diTextStart);
253 ME_DisplayItem *p2 = ME_MakeDI(diTextEnd);
255 p1->prev = NULL;
256 p1->next = p2;
257 p2->prev = p1;
258 p2->next = NULL;
259 p1->member.para.next_para = p2;
260 p2->member.para.prev_para = p1;
261 p2->member.para.nCharOfs = 0;
263 buf->pFirst = p1;
264 buf->pLast = p2;
265 buf->pCharStyle = NULL;
267 return buf;
270 ME_Paragraph *editor_first_para( ME_TextEditor *editor )
272 return para_next( &editor->pBuffer->pFirst->member.para );
275 /* Note, returns the diTextEnd sentinel paragraph */
276 ME_Paragraph *editor_end_para( ME_TextEditor *editor )
278 return &editor->pBuffer->pLast->member.para;
281 static BOOL editor_beep( ME_TextEditor *editor, UINT type )
283 return editor->props & TXTBIT_ALLOWBEEP && MessageBeep( type );
286 static LRESULT ME_StreamInText(ME_TextEditor *editor, DWORD dwFormat, ME_InStream *stream, ME_Style *style)
288 WCHAR *pText;
289 LRESULT total_bytes_read = 0;
290 BOOL is_read = FALSE;
291 DWORD cp = CP_ACP, copy = 0;
292 char conv_buf[4 + STREAMIN_BUFFER_SIZE]; /* up to 4 additional UTF-8 bytes */
294 static const char bom_utf8[] = {0xEF, 0xBB, 0xBF};
296 TRACE("%08lx %p\n", dwFormat, stream);
298 do {
299 LONG nWideChars = 0;
300 WCHAR wszText[STREAMIN_BUFFER_SIZE+1];
302 if (!stream->dwSize)
304 ME_StreamInFill(stream);
305 if (stream->editstream->dwError)
306 break;
307 if (!stream->dwSize)
308 break;
309 total_bytes_read += stream->dwSize;
312 if (!(dwFormat & SF_UNICODE))
314 char * buf = stream->buffer;
315 DWORD size = stream->dwSize, end;
317 if (!is_read)
319 is_read = TRUE;
320 if (stream->dwSize >= 3 && !memcmp(stream->buffer, bom_utf8, 3))
322 cp = CP_UTF8;
323 buf += 3;
324 size -= 3;
328 if (cp == CP_UTF8)
330 if (copy)
332 memcpy(conv_buf + copy, buf, size);
333 buf = conv_buf;
334 size += copy;
336 end = size;
337 while ((buf[end-1] & 0xC0) == 0x80)
339 --end;
340 --total_bytes_read; /* strange, but seems to match windows */
342 if (buf[end-1] & 0x80)
344 DWORD need = 0;
345 if ((buf[end-1] & 0xE0) == 0xC0)
346 need = 1;
347 if ((buf[end-1] & 0xF0) == 0xE0)
348 need = 2;
349 if ((buf[end-1] & 0xF8) == 0xF0)
350 need = 3;
352 if (size - end >= need)
354 /* we have enough bytes for this sequence */
355 end = size;
357 else
359 /* need more bytes, so don't transcode this sequence */
360 --end;
364 else
365 end = size;
367 nWideChars = MultiByteToWideChar(cp, 0, buf, end, wszText, STREAMIN_BUFFER_SIZE);
368 pText = wszText;
370 if (cp == CP_UTF8)
372 if (end != size)
374 memcpy(conv_buf, buf + end, size - end);
375 copy = size - end;
379 else
381 nWideChars = stream->dwSize >> 1;
382 pText = (WCHAR *)stream->buffer;
385 ME_InsertTextFromCursor(editor, 0, pText, nWideChars, style);
386 if (stream->dwSize == 0)
387 break;
388 stream->dwSize = 0;
389 } while(1);
390 return total_bytes_read;
393 static void ME_ApplyBorderProperties(RTF_Info *info,
394 ME_BorderRect *borderRect,
395 RTFBorder *borderDef)
397 int i, colorNum;
398 ME_Border *pBorders[] = {&borderRect->top,
399 &borderRect->left,
400 &borderRect->bottom,
401 &borderRect->right};
402 for (i = 0; i < 4; i++)
404 RTFColor *colorDef = info->colorList;
405 pBorders[i]->width = borderDef[i].width;
406 colorNum = borderDef[i].color;
407 while (colorDef && colorDef->rtfCNum != colorNum)
408 colorDef = colorDef->rtfNextColor;
409 if (colorDef)
410 pBorders[i]->colorRef = RGB(
411 colorDef->rtfCRed >= 0 ? colorDef->rtfCRed : 0,
412 colorDef->rtfCGreen >= 0 ? colorDef->rtfCGreen : 0,
413 colorDef->rtfCBlue >= 0 ? colorDef->rtfCBlue : 0);
414 else
415 pBorders[i]->colorRef = RGB(0, 0, 0);
419 void ME_RTFCharAttrHook(RTF_Info *info)
421 CHARFORMAT2W fmt;
422 fmt.cbSize = sizeof(fmt);
423 fmt.dwMask = 0;
424 fmt.dwEffects = 0;
426 switch(info->rtfMinor)
428 case rtfPlain:
429 /* FIXME add more flags once they're implemented */
430 fmt.dwMask = CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE | CFM_UNDERLINETYPE | CFM_STRIKEOUT |
431 CFM_COLOR | CFM_BACKCOLOR | CFM_SIZE | CFM_WEIGHT;
432 fmt.dwEffects = CFE_AUTOCOLOR | CFE_AUTOBACKCOLOR;
433 fmt.yHeight = 12*20; /* 12pt */
434 fmt.wWeight = FW_NORMAL;
435 fmt.bUnderlineType = CFU_UNDERLINE;
436 break;
437 case rtfBold:
438 fmt.dwMask = CFM_BOLD | CFM_WEIGHT;
439 fmt.dwEffects = info->rtfParam ? CFE_BOLD : 0;
440 fmt.wWeight = info->rtfParam ? FW_BOLD : FW_NORMAL;
441 break;
442 case rtfItalic:
443 fmt.dwMask = CFM_ITALIC;
444 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
445 break;
446 case rtfUnderline:
447 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
448 fmt.bUnderlineType = CFU_UNDERLINE;
449 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
450 break;
451 case rtfDotUnderline:
452 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
453 fmt.bUnderlineType = CFU_UNDERLINEDOTTED;
454 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
455 break;
456 case rtfDbUnderline:
457 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
458 fmt.bUnderlineType = CFU_UNDERLINEDOUBLE;
459 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
460 break;
461 case rtfWordUnderline:
462 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
463 fmt.bUnderlineType = CFU_UNDERLINEWORD;
464 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
465 break;
466 case rtfNoUnderline:
467 fmt.dwMask = CFM_UNDERLINE;
468 fmt.dwEffects = 0;
469 break;
470 case rtfStrikeThru:
471 fmt.dwMask = CFM_STRIKEOUT;
472 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
473 break;
474 case rtfSubScript:
475 case rtfSuperScript:
476 case rtfSubScrShrink:
477 case rtfSuperScrShrink:
478 case rtfNoSuperSub:
479 fmt.dwMask = CFM_SUBSCRIPT|CFM_SUPERSCRIPT;
480 if (info->rtfMinor == rtfSubScrShrink) fmt.dwEffects = CFE_SUBSCRIPT;
481 if (info->rtfMinor == rtfSuperScrShrink) fmt.dwEffects = CFE_SUPERSCRIPT;
482 if (info->rtfMinor == rtfNoSuperSub) fmt.dwEffects = 0;
483 break;
484 case rtfInvisible:
485 fmt.dwMask = CFM_HIDDEN;
486 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
487 break;
488 case rtfBackColor:
489 fmt.dwMask = CFM_BACKCOLOR;
490 fmt.dwEffects = 0;
491 if (info->rtfParam == 0)
492 fmt.dwEffects = CFE_AUTOBACKCOLOR;
493 else if (info->rtfParam != rtfNoParam)
495 RTFColor *c = RTFGetColor(info, info->rtfParam);
496 if (c && c->rtfCBlue >= 0)
497 fmt.crBackColor = (c->rtfCBlue<<16)|(c->rtfCGreen<<8)|(c->rtfCRed);
498 else
499 fmt.dwEffects = CFE_AUTOBACKCOLOR;
501 break;
502 case rtfForeColor:
503 fmt.dwMask = CFM_COLOR;
504 fmt.dwEffects = 0;
505 if (info->rtfParam == 0)
506 fmt.dwEffects = CFE_AUTOCOLOR;
507 else if (info->rtfParam != rtfNoParam)
509 RTFColor *c = RTFGetColor(info, info->rtfParam);
510 if (c && c->rtfCBlue >= 0)
511 fmt.crTextColor = (c->rtfCBlue<<16)|(c->rtfCGreen<<8)|(c->rtfCRed);
512 else {
513 fmt.dwEffects = CFE_AUTOCOLOR;
516 break;
517 case rtfFontNum:
518 if (info->rtfParam != rtfNoParam)
520 RTFFont *f = RTFGetFont(info, info->rtfParam);
521 if (f)
523 MultiByteToWideChar(CP_ACP, 0, f->rtfFName, -1, fmt.szFaceName, ARRAY_SIZE(fmt.szFaceName));
524 fmt.szFaceName[ARRAY_SIZE(fmt.szFaceName)-1] = '\0';
525 fmt.bCharSet = f->rtfFCharSet;
526 fmt.dwMask = CFM_FACE | CFM_CHARSET;
527 fmt.bPitchAndFamily = f->rtfFPitch | (f->rtfFFamily << 4);
530 break;
531 case rtfFontSize:
532 fmt.dwMask = CFM_SIZE;
533 if (info->rtfParam != rtfNoParam)
534 fmt.yHeight = info->rtfParam*10;
535 break;
537 if (fmt.dwMask) {
538 ME_Style *style2;
539 RTFFlushOutputBuffer(info);
540 /* FIXME too slow ? how come ? */
541 style2 = ME_ApplyStyle(info->editor, info->style, &fmt);
542 ME_ReleaseStyle(info->style);
543 info->style = style2;
544 info->styleChanged = TRUE;
548 /* FIXME this function doesn't get any information about context of the RTF tag, which is very bad,
549 the same tags mean different things in different contexts */
550 void ME_RTFParAttrHook(RTF_Info *info)
552 switch(info->rtfMinor)
554 case rtfParDef: /* restores default paragraph attributes */
555 if (!info->editor->bEmulateVersion10) /* v4.1 */
556 info->borderType = RTFBorderParaLeft;
557 else /* v1.0 - 3.0 */
558 info->borderType = RTFBorderParaTop;
559 info->fmt.dwMask = PFM_ALIGNMENT | PFM_BORDER | PFM_LINESPACING | PFM_TABSTOPS |
560 PFM_OFFSET | PFM_RIGHTINDENT | PFM_SPACEAFTER | PFM_SPACEBEFORE |
561 PFM_STARTINDENT | PFM_RTLPARA | PFM_NUMBERING | PFM_NUMBERINGSTART |
562 PFM_NUMBERINGSTYLE | PFM_NUMBERINGTAB;
563 /* TODO: shading */
564 info->fmt.wAlignment = PFA_LEFT;
565 info->fmt.cTabCount = 0;
566 info->fmt.dxOffset = info->fmt.dxStartIndent = info->fmt.dxRightIndent = 0;
567 info->fmt.wBorderWidth = info->fmt.wBorders = 0;
568 info->fmt.wBorderSpace = 0;
569 info->fmt.bLineSpacingRule = 0;
570 info->fmt.dySpaceBefore = info->fmt.dySpaceAfter = 0;
571 info->fmt.dyLineSpacing = 0;
572 info->fmt.wEffects &= ~PFE_RTLPARA;
573 info->fmt.wNumbering = 0;
574 info->fmt.wNumberingStart = 0;
575 info->fmt.wNumberingStyle = 0;
576 info->fmt.wNumberingTab = 0;
578 if (!info->editor->bEmulateVersion10) /* v4.1 */
580 if (info->tableDef && info->tableDef->row_start &&
581 info->tableDef->row_start->nFlags & MEPF_ROWEND)
583 ME_Cursor cursor;
584 ME_Paragraph *para;
585 /* We are just after a table row. */
586 RTFFlushOutputBuffer(info);
587 cursor = info->editor->pCursors[0];
588 para = cursor.para;
589 if (para == para_next( info->tableDef->row_start )
590 && !cursor.nOffset && !cursor.run->nCharOfs)
592 /* Since the table row end, no text has been inserted, and the \intbl
593 * control word has not be used. We can confirm that we are not in a
594 * table anymore.
596 info->tableDef->row_start = NULL;
597 info->canInheritInTbl = FALSE;
601 else /* v1.0 - v3.0 */
603 info->fmt.dwMask |= PFM_TABLE;
604 info->fmt.wEffects &= ~PFE_TABLE;
606 break;
607 case rtfNestLevel:
608 if (!info->editor->bEmulateVersion10) /* v4.1 */
610 while (info->rtfParam > info->nestingLevel)
612 RTFTable *tableDef = heap_alloc_zero(sizeof(*tableDef));
613 tableDef->parent = info->tableDef;
614 info->tableDef = tableDef;
616 RTFFlushOutputBuffer(info);
617 if (tableDef->row_start && tableDef->row_start->nFlags & MEPF_ROWEND)
619 ME_Paragraph *para = para_next( tableDef->row_start );
620 tableDef->row_start = table_insert_row_start_at_para( info->editor, para );
622 else
624 ME_Cursor cursor;
625 cursor = info->editor->pCursors[0];
626 if (cursor.nOffset || cursor.run->nCharOfs)
627 ME_InsertTextFromCursor(info->editor, 0, L"\r", 1, info->style);
628 tableDef->row_start = table_insert_row_start( info->editor, info->editor->pCursors );
631 info->nestingLevel++;
633 info->canInheritInTbl = FALSE;
635 break;
636 case rtfInTable:
638 if (!info->editor->bEmulateVersion10) /* v4.1 */
640 if (info->nestingLevel < 1)
642 RTFTable *tableDef;
643 ME_Paragraph *para;
645 if (!info->tableDef)
646 info->tableDef = heap_alloc_zero(sizeof(*info->tableDef));
647 tableDef = info->tableDef;
648 RTFFlushOutputBuffer(info);
649 if (tableDef->row_start && tableDef->row_start->nFlags & MEPF_ROWEND)
650 para = para_next( tableDef->row_start );
651 else
652 para = info->editor->pCursors[0].para;
654 tableDef->row_start = table_insert_row_start_at_para( info->editor, para );
656 info->nestingLevel = 1;
657 info->canInheritInTbl = TRUE;
659 return;
660 } else { /* v1.0 - v3.0 */
661 info->fmt.dwMask |= PFM_TABLE;
662 info->fmt.wEffects |= PFE_TABLE;
664 break;
666 case rtfFirstIndent:
667 case rtfLeftIndent:
668 if ((info->fmt.dwMask & (PFM_STARTINDENT | PFM_OFFSET)) != (PFM_STARTINDENT | PFM_OFFSET))
670 PARAFORMAT2 fmt;
671 fmt.cbSize = sizeof(fmt);
672 editor_get_selection_para_fmt( info->editor, &fmt );
673 info->fmt.dwMask |= PFM_STARTINDENT | PFM_OFFSET;
674 info->fmt.dxStartIndent = fmt.dxStartIndent;
675 info->fmt.dxOffset = fmt.dxOffset;
677 if (info->rtfMinor == rtfFirstIndent)
679 info->fmt.dxStartIndent += info->fmt.dxOffset + info->rtfParam;
680 info->fmt.dxOffset = -info->rtfParam;
682 else
683 info->fmt.dxStartIndent = info->rtfParam - info->fmt.dxOffset;
684 break;
685 case rtfRightIndent:
686 info->fmt.dwMask |= PFM_RIGHTINDENT;
687 info->fmt.dxRightIndent = info->rtfParam;
688 break;
689 case rtfQuadLeft:
690 case rtfQuadJust:
691 info->fmt.dwMask |= PFM_ALIGNMENT;
692 info->fmt.wAlignment = PFA_LEFT;
693 break;
694 case rtfQuadRight:
695 info->fmt.dwMask |= PFM_ALIGNMENT;
696 info->fmt.wAlignment = PFA_RIGHT;
697 break;
698 case rtfQuadCenter:
699 info->fmt.dwMask |= PFM_ALIGNMENT;
700 info->fmt.wAlignment = PFA_CENTER;
701 break;
702 case rtfTabPos:
703 if (!(info->fmt.dwMask & PFM_TABSTOPS))
705 PARAFORMAT2 fmt;
706 fmt.cbSize = sizeof(fmt);
707 editor_get_selection_para_fmt( info->editor, &fmt );
708 memcpy(info->fmt.rgxTabs, fmt.rgxTabs,
709 fmt.cTabCount * sizeof(fmt.rgxTabs[0]));
710 info->fmt.cTabCount = fmt.cTabCount;
711 info->fmt.dwMask |= PFM_TABSTOPS;
713 if (info->fmt.cTabCount < MAX_TAB_STOPS && info->rtfParam < 0x1000000)
714 info->fmt.rgxTabs[info->fmt.cTabCount++] = info->rtfParam;
715 break;
716 case rtfKeep:
717 info->fmt.dwMask |= PFM_KEEP;
718 info->fmt.wEffects |= PFE_KEEP;
719 break;
720 case rtfNoWidowControl:
721 info->fmt.dwMask |= PFM_NOWIDOWCONTROL;
722 info->fmt.wEffects |= PFE_NOWIDOWCONTROL;
723 break;
724 case rtfKeepNext:
725 info->fmt.dwMask |= PFM_KEEPNEXT;
726 info->fmt.wEffects |= PFE_KEEPNEXT;
727 break;
728 case rtfSpaceAfter:
729 info->fmt.dwMask |= PFM_SPACEAFTER;
730 info->fmt.dySpaceAfter = info->rtfParam;
731 break;
732 case rtfSpaceBefore:
733 info->fmt.dwMask |= PFM_SPACEBEFORE;
734 info->fmt.dySpaceBefore = info->rtfParam;
735 break;
736 case rtfSpaceBetween:
737 info->fmt.dwMask |= PFM_LINESPACING;
738 if ((int)info->rtfParam > 0)
740 info->fmt.dyLineSpacing = info->rtfParam;
741 info->fmt.bLineSpacingRule = 3;
743 else
745 info->fmt.dyLineSpacing = info->rtfParam;
746 info->fmt.bLineSpacingRule = 4;
748 break;
749 case rtfSpaceMultiply:
750 info->fmt.dwMask |= PFM_LINESPACING;
751 info->fmt.dyLineSpacing = info->rtfParam * 20;
752 info->fmt.bLineSpacingRule = 5;
753 break;
754 case rtfParBullet:
755 info->fmt.dwMask |= PFM_NUMBERING;
756 info->fmt.wNumbering = PFN_BULLET;
757 break;
758 case rtfParSimple:
759 info->fmt.dwMask |= PFM_NUMBERING;
760 info->fmt.wNumbering = 2; /* FIXME: MSDN says it's not used ?? */
761 break;
762 case rtfBorderLeft:
763 info->borderType = RTFBorderParaLeft;
764 info->fmt.wBorders |= 1;
765 info->fmt.dwMask |= PFM_BORDER;
766 break;
767 case rtfBorderRight:
768 info->borderType = RTFBorderParaRight;
769 info->fmt.wBorders |= 2;
770 info->fmt.dwMask |= PFM_BORDER;
771 break;
772 case rtfBorderTop:
773 info->borderType = RTFBorderParaTop;
774 info->fmt.wBorders |= 4;
775 info->fmt.dwMask |= PFM_BORDER;
776 break;
777 case rtfBorderBottom:
778 info->borderType = RTFBorderParaBottom;
779 info->fmt.wBorders |= 8;
780 info->fmt.dwMask |= PFM_BORDER;
781 break;
782 case rtfBorderSingle:
783 info->fmt.wBorders &= ~0x700;
784 info->fmt.wBorders |= 1 << 8;
785 info->fmt.dwMask |= PFM_BORDER;
786 break;
787 case rtfBorderThick:
788 info->fmt.wBorders &= ~0x700;
789 info->fmt.wBorders |= 2 << 8;
790 info->fmt.dwMask |= PFM_BORDER;
791 break;
792 case rtfBorderShadow:
793 info->fmt.wBorders &= ~0x700;
794 info->fmt.wBorders |= 10 << 8;
795 info->fmt.dwMask |= PFM_BORDER;
796 break;
797 case rtfBorderDouble:
798 info->fmt.wBorders &= ~0x700;
799 info->fmt.wBorders |= 7 << 8;
800 info->fmt.dwMask |= PFM_BORDER;
801 break;
802 case rtfBorderDot:
803 info->fmt.wBorders &= ~0x700;
804 info->fmt.wBorders |= 11 << 8;
805 info->fmt.dwMask |= PFM_BORDER;
806 break;
807 case rtfBorderWidth:
809 int borderSide = info->borderType & RTFBorderSideMask;
810 RTFTable *tableDef = info->tableDef;
811 if ((info->borderType & RTFBorderTypeMask) == RTFBorderTypeCell)
813 RTFBorder *border;
814 if (!tableDef || tableDef->numCellsDefined >= MAX_TABLE_CELLS)
815 break;
816 border = &tableDef->cells[tableDef->numCellsDefined].border[borderSide];
817 border->width = info->rtfParam;
818 break;
820 info->fmt.wBorderWidth = info->rtfParam;
821 info->fmt.dwMask |= PFM_BORDER;
822 break;
824 case rtfBorderSpace:
825 info->fmt.wBorderSpace = info->rtfParam;
826 info->fmt.dwMask |= PFM_BORDER;
827 break;
828 case rtfBorderColor:
830 RTFTable *tableDef = info->tableDef;
831 int borderSide = info->borderType & RTFBorderSideMask;
832 int borderType = info->borderType & RTFBorderTypeMask;
833 switch(borderType)
835 case RTFBorderTypePara:
836 if (!info->editor->bEmulateVersion10) /* v4.1 */
837 break;
838 /* v1.0 - 3.0 treat paragraph and row borders the same. */
839 case RTFBorderTypeRow:
840 if (tableDef) {
841 tableDef->border[borderSide].color = info->rtfParam;
843 break;
844 case RTFBorderTypeCell:
845 if (tableDef && tableDef->numCellsDefined < MAX_TABLE_CELLS) {
846 tableDef->cells[tableDef->numCellsDefined].border[borderSide].color = info->rtfParam;
848 break;
850 break;
852 case rtfRTLPar:
853 info->fmt.dwMask |= PFM_RTLPARA;
854 info->fmt.wEffects |= PFE_RTLPARA;
855 break;
856 case rtfLTRPar:
857 info->fmt.dwMask |= PFM_RTLPARA;
858 info->fmt.wEffects &= ~PFE_RTLPARA;
859 break;
863 void ME_RTFTblAttrHook(RTF_Info *info)
865 switch (info->rtfMinor)
867 case rtfRowDef:
869 if (!info->editor->bEmulateVersion10) /* v4.1 */
870 info->borderType = 0; /* Not sure */
871 else /* v1.0 - 3.0 */
872 info->borderType = RTFBorderRowTop;
873 if (!info->tableDef) {
874 info->tableDef = ME_MakeTableDef(info->editor);
875 } else {
876 ME_InitTableDef(info->editor, info->tableDef);
878 break;
880 case rtfCellPos:
882 int cellNum;
883 if (!info->tableDef)
885 info->tableDef = ME_MakeTableDef(info->editor);
887 cellNum = info->tableDef->numCellsDefined;
888 if (cellNum >= MAX_TABLE_CELLS)
889 break;
890 info->tableDef->cells[cellNum].rightBoundary = info->rtfParam;
891 if (cellNum < MAX_TAB_STOPS)
893 /* Tab stops were used to store cell positions before v4.1 but v4.1
894 * still seems to set the tabstops without using them. */
895 PARAFORMAT2 *fmt = &info->editor->pCursors[0].para->fmt;
896 fmt->rgxTabs[cellNum] &= ~0x00FFFFFF;
897 fmt->rgxTabs[cellNum] |= 0x00FFFFFF & info->rtfParam;
899 info->tableDef->numCellsDefined++;
900 break;
902 case rtfRowBordTop:
903 info->borderType = RTFBorderRowTop;
904 break;
905 case rtfRowBordLeft:
906 info->borderType = RTFBorderRowLeft;
907 break;
908 case rtfRowBordBottom:
909 info->borderType = RTFBorderRowBottom;
910 break;
911 case rtfRowBordRight:
912 info->borderType = RTFBorderRowRight;
913 break;
914 case rtfCellBordTop:
915 info->borderType = RTFBorderCellTop;
916 break;
917 case rtfCellBordLeft:
918 info->borderType = RTFBorderCellLeft;
919 break;
920 case rtfCellBordBottom:
921 info->borderType = RTFBorderCellBottom;
922 break;
923 case rtfCellBordRight:
924 info->borderType = RTFBorderCellRight;
925 break;
926 case rtfRowGapH:
927 if (info->tableDef)
928 info->tableDef->gapH = info->rtfParam;
929 break;
930 case rtfRowLeftEdge:
931 if (info->tableDef)
932 info->tableDef->leftEdge = info->rtfParam;
933 break;
937 void ME_RTFSpecialCharHook(RTF_Info *info)
939 RTFTable *tableDef = info->tableDef;
940 switch (info->rtfMinor)
942 case rtfNestCell:
943 if (info->editor->bEmulateVersion10) /* v1.0 - v3.0 */
944 break;
945 /* else fall through since v4.1 treats rtfNestCell and rtfCell the same */
946 case rtfCell:
947 if (!tableDef)
948 break;
949 RTFFlushOutputBuffer(info);
950 if (!info->editor->bEmulateVersion10) /* v4.1 */
952 if (tableDef->row_start)
954 if (!info->nestingLevel && tableDef->row_start->nFlags & MEPF_ROWEND)
956 ME_Paragraph *para = para_next( tableDef->row_start );
957 tableDef->row_start = table_insert_row_start_at_para( info->editor, para );
958 info->nestingLevel = 1;
960 table_insert_cell( info->editor, info->editor->pCursors );
963 else /* v1.0 - v3.0 */
965 ME_Paragraph *para = info->editor->pCursors[0].para;
967 if (para_in_table( para ) && tableDef->numCellsInserted < tableDef->numCellsDefined)
969 WCHAR tab = '\t';
970 ME_InsertTextFromCursor(info->editor, 0, &tab, 1, info->style);
971 tableDef->numCellsInserted++;
974 break;
975 case rtfNestRow:
976 if (info->editor->bEmulateVersion10) /* v1.0 - v3.0 */
977 break;
978 /* else fall through since v4.1 treats rtfNestRow and rtfRow the same */
979 case rtfRow:
981 ME_Run *run;
982 ME_Paragraph *para;
983 ME_Cell *cell;
984 int i;
986 if (!tableDef)
987 break;
988 RTFFlushOutputBuffer(info);
989 if (!info->editor->bEmulateVersion10) /* v4.1 */
991 if (!tableDef->row_start) break;
992 if (!info->nestingLevel && tableDef->row_start->nFlags & MEPF_ROWEND)
994 para = para_next( tableDef->row_start );
995 tableDef->row_start = table_insert_row_start_at_para( info->editor, para );
996 info->nestingLevel++;
998 para = tableDef->row_start;
999 cell = table_row_first_cell( para );
1000 assert( cell && !cell_prev( cell ) );
1001 if (tableDef->numCellsDefined < 1)
1003 /* 2000 twips appears to be the cell size that native richedit uses
1004 * when no cell sizes are specified. */
1005 const int default_size = 2000;
1006 int right_boundary = default_size;
1007 cell->nRightBoundary = right_boundary;
1008 while (cell_next( cell ))
1010 cell = cell_next( cell );
1011 right_boundary += default_size;
1012 cell->nRightBoundary = right_boundary;
1014 para = table_insert_cell( info->editor, info->editor->pCursors );
1015 cell = para_cell( para );
1016 cell->nRightBoundary = right_boundary;
1018 else
1020 for (i = 0; i < tableDef->numCellsDefined; i++)
1022 RTFCell *cellDef = &tableDef->cells[i];
1023 cell->nRightBoundary = cellDef->rightBoundary;
1024 ME_ApplyBorderProperties( info, &cell->border, cellDef->border );
1025 cell = cell_next( cell );
1026 if (!cell)
1028 para = table_insert_cell( info->editor, info->editor->pCursors );
1029 cell = para_cell( para );
1032 /* Cell for table row delimiter is empty */
1033 cell->nRightBoundary = tableDef->cells[i - 1].rightBoundary;
1036 run = para_first_run( cell_first_para( cell ) );
1037 if (info->editor->pCursors[0].run != run || info->editor->pCursors[0].nOffset)
1039 int nOfs, nChars;
1040 /* Delete inserted cells that aren't defined. */
1041 info->editor->pCursors[1].run = run;
1042 info->editor->pCursors[1].para = run->para;
1043 info->editor->pCursors[1].nOffset = 0;
1044 nOfs = ME_GetCursorOfs(&info->editor->pCursors[1]);
1045 nChars = ME_GetCursorOfs(&info->editor->pCursors[0]) - nOfs;
1046 ME_InternalDeleteText(info->editor, &info->editor->pCursors[1],
1047 nChars, TRUE);
1050 para = table_insert_row_end( info->editor, info->editor->pCursors );
1051 para->fmt.dxOffset = abs(info->tableDef->gapH);
1052 para->fmt.dxStartIndent = info->tableDef->leftEdge;
1053 ME_ApplyBorderProperties( info, &para->border, tableDef->border );
1054 info->nestingLevel--;
1055 if (!info->nestingLevel)
1057 if (info->canInheritInTbl) tableDef->row_start = para;
1058 else
1060 while (info->tableDef)
1062 tableDef = info->tableDef;
1063 info->tableDef = tableDef->parent;
1064 heap_free(tableDef);
1068 else
1070 info->tableDef = tableDef->parent;
1071 heap_free(tableDef);
1074 else /* v1.0 - v3.0 */
1076 para = info->editor->pCursors[0].para;
1077 para->fmt.dxOffset = info->tableDef->gapH;
1078 para->fmt.dxStartIndent = info->tableDef->leftEdge;
1080 ME_ApplyBorderProperties( info, &para->border, tableDef->border );
1081 while (tableDef->numCellsInserted < tableDef->numCellsDefined)
1083 WCHAR tab = '\t';
1084 ME_InsertTextFromCursor(info->editor, 0, &tab, 1, info->style);
1085 tableDef->numCellsInserted++;
1087 para->fmt.cTabCount = min(tableDef->numCellsDefined, MAX_TAB_STOPS);
1088 if (!tableDef->numCellsDefined) para->fmt.wEffects &= ~PFE_TABLE;
1089 ME_InsertTextFromCursor(info->editor, 0, L"\r", 1, info->style);
1090 tableDef->numCellsInserted = 0;
1092 break;
1094 case rtfTab:
1095 case rtfPar:
1096 if (info->editor->bEmulateVersion10) /* v1.0 - 3.0 */
1098 ME_Paragraph *para;
1100 RTFFlushOutputBuffer(info);
1101 para = info->editor->pCursors[0].para;
1102 if (para_in_table( para ))
1104 /* rtfPar is treated like a space within a table. */
1105 info->rtfClass = rtfText;
1106 info->rtfMajor = ' ';
1108 else if (info->rtfMinor == rtfPar && tableDef)
1109 tableDef->numCellsInserted = 0;
1111 break;
1115 static HRESULT insert_static_object(ME_TextEditor *editor, HENHMETAFILE hemf, HBITMAP hbmp,
1116 const SIZEL* sz)
1118 LPOLEOBJECT lpObject = NULL;
1119 LPSTORAGE lpStorage = NULL;
1120 LPOLECLIENTSITE lpClientSite = NULL;
1121 LPDATAOBJECT lpDataObject = NULL;
1122 LPOLECACHE lpOleCache = NULL;
1123 STGMEDIUM stgm;
1124 FORMATETC fm;
1125 CLSID clsid;
1126 HRESULT hr = E_FAIL;
1127 DWORD conn;
1129 if (hemf)
1131 stgm.tymed = TYMED_ENHMF;
1132 stgm.u.hEnhMetaFile = hemf;
1133 fm.cfFormat = CF_ENHMETAFILE;
1135 else if (hbmp)
1137 stgm.tymed = TYMED_GDI;
1138 stgm.u.hBitmap = hbmp;
1139 fm.cfFormat = CF_BITMAP;
1141 else return E_FAIL;
1143 stgm.pUnkForRelease = NULL;
1145 fm.ptd = NULL;
1146 fm.dwAspect = DVASPECT_CONTENT;
1147 fm.lindex = -1;
1148 fm.tymed = stgm.tymed;
1150 if (OleCreateDefaultHandler(&CLSID_NULL, NULL, &IID_IOleObject, (void**)&lpObject) == S_OK &&
1151 IRichEditOle_GetClientSite(editor->richole, &lpClientSite) == S_OK &&
1152 IOleObject_SetClientSite(lpObject, lpClientSite) == S_OK &&
1153 IOleObject_GetUserClassID(lpObject, &clsid) == S_OK &&
1154 IOleObject_QueryInterface(lpObject, &IID_IOleCache, (void**)&lpOleCache) == S_OK &&
1155 IOleCache_Cache(lpOleCache, &fm, 0, &conn) == S_OK &&
1156 IOleObject_QueryInterface(lpObject, &IID_IDataObject, (void**)&lpDataObject) == S_OK &&
1157 IDataObject_SetData(lpDataObject, &fm, &stgm, TRUE) == S_OK)
1159 REOBJECT reobject;
1161 reobject.cbStruct = sizeof(reobject);
1162 reobject.cp = REO_CP_SELECTION;
1163 reobject.clsid = clsid;
1164 reobject.poleobj = lpObject;
1165 reobject.pstg = lpStorage;
1166 reobject.polesite = lpClientSite;
1167 /* convert from twips to .01 mm */
1168 reobject.sizel.cx = MulDiv(sz->cx, 254, 144);
1169 reobject.sizel.cy = MulDiv(sz->cy, 254, 144);
1170 reobject.dvaspect = DVASPECT_CONTENT;
1171 reobject.dwFlags = 0; /* FIXME */
1172 reobject.dwUser = 0;
1174 hr = editor_insert_oleobj(editor, &reobject);
1177 if (lpObject) IOleObject_Release(lpObject);
1178 if (lpClientSite) IOleClientSite_Release(lpClientSite);
1179 if (lpStorage) IStorage_Release(lpStorage);
1180 if (lpDataObject) IDataObject_Release(lpDataObject);
1181 if (lpOleCache) IOleCache_Release(lpOleCache);
1183 return hr;
1186 static void ME_RTFReadShpPictGroup( RTF_Info *info )
1188 int level = 1;
1190 for (;;)
1192 RTFGetToken (info);
1194 if (info->rtfClass == rtfEOF) return;
1195 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1197 if (--level == 0) break;
1199 else if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1201 level++;
1203 else
1205 RTFRouteToken( info );
1206 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1207 level--;
1211 RTFRouteToken( info ); /* feed "}" back to router */
1212 return;
1215 static DWORD read_hex_data( RTF_Info *info, BYTE **out )
1217 DWORD read = 0, size = 1024;
1218 BYTE *buf, val;
1219 BOOL flip;
1221 *out = NULL;
1223 if (info->rtfClass != rtfText)
1225 ERR("Called with incorrect token\n");
1226 return 0;
1229 buf = HeapAlloc( GetProcessHeap(), 0, size );
1230 if (!buf) return 0;
1232 val = info->rtfMajor;
1233 for (flip = TRUE;; flip = !flip)
1235 RTFGetToken( info );
1236 if (info->rtfClass == rtfEOF)
1238 HeapFree( GetProcessHeap(), 0, buf );
1239 return 0;
1241 if (info->rtfClass != rtfText) break;
1242 if (flip)
1244 if (read >= size)
1246 size *= 2;
1247 buf = HeapReAlloc( GetProcessHeap(), 0, buf, size );
1248 if (!buf) return 0;
1250 buf[read++] = RTFCharToHex(val) * 16 + RTFCharToHex(info->rtfMajor);
1252 else
1253 val = info->rtfMajor;
1255 if (flip) FIXME("wrong hex string\n");
1257 *out = buf;
1258 return read;
1261 static void ME_RTFReadPictGroup(RTF_Info *info)
1263 SIZEL sz;
1264 BYTE *buffer = NULL;
1265 DWORD size = 0;
1266 METAFILEPICT mfp;
1267 HENHMETAFILE hemf;
1268 HBITMAP hbmp;
1269 enum gfxkind {gfx_unknown = 0, gfx_enhmetafile, gfx_metafile, gfx_dib} gfx = gfx_unknown;
1270 int level = 1;
1272 mfp.mm = MM_TEXT;
1273 sz.cx = sz.cy = 0;
1275 for (;;)
1277 RTFGetToken( info );
1279 if (info->rtfClass == rtfText)
1281 if (level == 1)
1283 if (!buffer)
1284 size = read_hex_data( info, &buffer );
1286 else
1288 RTFSkipGroup( info );
1290 } /* We potentially have a new token so fall through. */
1292 if (info->rtfClass == rtfEOF) return;
1294 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1296 if (--level == 0) break;
1297 continue;
1299 if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1301 level++;
1302 continue;
1304 if (!RTFCheckCM( info, rtfControl, rtfPictAttr ))
1306 RTFRouteToken( info );
1307 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1308 level--;
1309 continue;
1312 if (RTFCheckMM( info, rtfPictAttr, rtfWinMetafile ))
1314 mfp.mm = info->rtfParam;
1315 gfx = gfx_metafile;
1317 else if (RTFCheckMM( info, rtfPictAttr, rtfDevIndBitmap ))
1319 if (info->rtfParam != 0) FIXME("dibitmap should be 0 (%d)\n", info->rtfParam);
1320 gfx = gfx_dib;
1322 else if (RTFCheckMM( info, rtfPictAttr, rtfEmfBlip ))
1323 gfx = gfx_enhmetafile;
1324 else if (RTFCheckMM( info, rtfPictAttr, rtfPicWid ))
1325 mfp.xExt = info->rtfParam;
1326 else if (RTFCheckMM( info, rtfPictAttr, rtfPicHt ))
1327 mfp.yExt = info->rtfParam;
1328 else if (RTFCheckMM( info, rtfPictAttr, rtfPicGoalWid ))
1329 sz.cx = info->rtfParam;
1330 else if (RTFCheckMM( info, rtfPictAttr, rtfPicGoalHt ))
1331 sz.cy = info->rtfParam;
1332 else
1333 FIXME("Non supported attribute: %d %d %d\n", info->rtfClass, info->rtfMajor, info->rtfMinor);
1336 if (buffer)
1338 switch (gfx)
1340 case gfx_enhmetafile:
1341 if ((hemf = SetEnhMetaFileBits( size, buffer )))
1342 insert_static_object( info->editor, hemf, NULL, &sz );
1343 break;
1344 case gfx_metafile:
1345 if ((hemf = SetWinMetaFileBits( size, buffer, NULL, &mfp )))
1346 insert_static_object( info->editor, hemf, NULL, &sz );
1347 break;
1348 case gfx_dib:
1350 BITMAPINFO *bi = (BITMAPINFO*)buffer;
1351 HDC hdc = GetDC(0);
1352 unsigned nc = bi->bmiHeader.biClrUsed;
1354 /* not quite right, especially for bitfields type of compression */
1355 if (!nc && bi->bmiHeader.biBitCount <= 8)
1356 nc = 1 << bi->bmiHeader.biBitCount;
1357 if ((hbmp = CreateDIBitmap( hdc, &bi->bmiHeader,
1358 CBM_INIT, (char*)(bi + 1) + nc * sizeof(RGBQUAD),
1359 bi, DIB_RGB_COLORS)) )
1360 insert_static_object( info->editor, NULL, hbmp, &sz );
1361 ReleaseDC( 0, hdc );
1362 break;
1364 default:
1365 break;
1368 HeapFree( GetProcessHeap(), 0, buffer );
1369 RTFRouteToken( info ); /* feed "}" back to router */
1370 return;
1373 /* for now, lookup the \result part and use it, whatever the object */
1374 static void ME_RTFReadObjectGroup(RTF_Info *info)
1376 for (;;)
1378 RTFGetToken (info);
1379 if (info->rtfClass == rtfEOF)
1380 return;
1381 if (RTFCheckCM(info, rtfGroup, rtfEndGroup))
1382 break;
1383 if (RTFCheckCM(info, rtfGroup, rtfBeginGroup))
1385 RTFGetToken (info);
1386 if (info->rtfClass == rtfEOF)
1387 return;
1388 if (RTFCheckCMM(info, rtfControl, rtfDestination, rtfObjResult))
1390 int level = 1;
1392 while (RTFGetToken (info) != rtfEOF)
1394 if (info->rtfClass == rtfGroup)
1396 if (info->rtfMajor == rtfBeginGroup) level++;
1397 else if (info->rtfMajor == rtfEndGroup && --level < 0) break;
1399 RTFRouteToken(info);
1402 else RTFSkipGroup(info);
1403 continue;
1405 if (!RTFCheckCM (info, rtfControl, rtfObjAttr))
1407 FIXME("Non supported attribute: %d %d %d\n", info->rtfClass, info->rtfMajor, info->rtfMinor);
1408 return;
1411 RTFRouteToken(info); /* feed "}" back to router */
1414 static void ME_RTFReadParnumGroup( RTF_Info *info )
1416 int level = 1, type = -1;
1417 WORD indent = 0, start = 1;
1418 WCHAR txt_before = 0, txt_after = 0;
1420 for (;;)
1422 RTFGetToken( info );
1424 if (RTFCheckCMM( info, rtfControl, rtfDestination, rtfParNumTextBefore ) ||
1425 RTFCheckCMM( info, rtfControl, rtfDestination, rtfParNumTextAfter ))
1427 int loc = info->rtfMinor;
1429 RTFGetToken( info );
1430 if (info->rtfClass == rtfText)
1432 if (loc == rtfParNumTextBefore)
1433 txt_before = info->rtfMajor;
1434 else
1435 txt_after = info->rtfMajor;
1436 continue;
1438 /* falling through to catch EOFs and group level changes */
1441 if (info->rtfClass == rtfEOF)
1442 return;
1444 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1446 if (--level == 0) break;
1447 continue;
1450 if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1452 level++;
1453 continue;
1456 /* Ignore non para-attr */
1457 if (!RTFCheckCM( info, rtfControl, rtfParAttr ))
1458 continue;
1460 switch (info->rtfMinor)
1462 case rtfParLevel: /* Para level is ignored */
1463 case rtfParSimple:
1464 break;
1465 case rtfParBullet:
1466 type = PFN_BULLET;
1467 break;
1469 case rtfParNumDecimal:
1470 type = PFN_ARABIC;
1471 break;
1472 case rtfParNumULetter:
1473 type = PFN_UCLETTER;
1474 break;
1475 case rtfParNumURoman:
1476 type = PFN_UCROMAN;
1477 break;
1478 case rtfParNumLLetter:
1479 type = PFN_LCLETTER;
1480 break;
1481 case rtfParNumLRoman:
1482 type = PFN_LCROMAN;
1483 break;
1485 case rtfParNumIndent:
1486 indent = info->rtfParam;
1487 break;
1488 case rtfParNumStartAt:
1489 start = info->rtfParam;
1490 break;
1494 if (type != -1)
1496 info->fmt.dwMask |= (PFM_NUMBERING | PFM_NUMBERINGSTART | PFM_NUMBERINGSTYLE | PFM_NUMBERINGTAB);
1497 info->fmt.wNumbering = type;
1498 info->fmt.wNumberingStart = start;
1499 info->fmt.wNumberingStyle = PFNS_PAREN;
1500 if (type != PFN_BULLET)
1502 if (txt_before == 0 && txt_after == 0)
1503 info->fmt.wNumberingStyle = PFNS_PLAIN;
1504 else if (txt_after == '.')
1505 info->fmt.wNumberingStyle = PFNS_PERIOD;
1506 else if (txt_before == '(' && txt_after == ')')
1507 info->fmt.wNumberingStyle = PFNS_PARENS;
1509 info->fmt.wNumberingTab = indent;
1512 TRACE("type %d indent %d start %d txt before %04x txt after %04x\n",
1513 type, indent, start, txt_before, txt_after);
1515 RTFRouteToken( info ); /* feed "}" back to router */
1518 static void ME_RTFReadHook(RTF_Info *info)
1520 switch(info->rtfClass)
1522 case rtfGroup:
1523 switch(info->rtfMajor)
1525 case rtfBeginGroup:
1526 if (info->stackTop < maxStack) {
1527 info->stack[info->stackTop].style = info->style;
1528 ME_AddRefStyle(info->style);
1529 info->stack[info->stackTop].codePage = info->codePage;
1530 info->stack[info->stackTop].unicodeLength = info->unicodeLength;
1532 info->stackTop++;
1533 info->styleChanged = FALSE;
1534 break;
1535 case rtfEndGroup:
1537 RTFFlushOutputBuffer(info);
1538 info->stackTop--;
1539 if (info->stackTop <= 0)
1540 info->rtfClass = rtfEOF;
1541 if (info->stackTop < 0)
1542 return;
1544 ME_ReleaseStyle(info->style);
1545 info->style = info->stack[info->stackTop].style;
1546 info->codePage = info->stack[info->stackTop].codePage;
1547 info->unicodeLength = info->stack[info->stackTop].unicodeLength;
1548 break;
1551 break;
1555 void
1556 ME_StreamInFill(ME_InStream *stream)
1558 stream->editstream->dwError = stream->editstream->pfnCallback(stream->editstream->dwCookie,
1559 (BYTE *)stream->buffer,
1560 sizeof(stream->buffer),
1561 (LONG *)&stream->dwSize);
1562 stream->dwUsed = 0;
1565 static LRESULT ME_StreamIn(ME_TextEditor *editor, DWORD format, EDITSTREAM *stream, BOOL stripLastCR)
1567 RTF_Info parser;
1568 ME_Style *style;
1569 LONG from, to;
1570 int nUndoMode;
1571 int nEventMask = editor->nEventMask;
1572 ME_InStream inStream;
1573 BOOL invalidRTF = FALSE;
1574 ME_Cursor *selStart, *selEnd;
1575 LRESULT num_read = 0; /* bytes read for SF_TEXT, non-control chars inserted for SF_RTF */
1577 TRACE("stream==%p editor==%p format==0x%lX\n", stream, editor, format);
1578 editor->nEventMask = 0;
1580 ME_GetSelectionOfs(editor, &from, &to);
1581 if (format & SFF_SELECTION && editor->mode & TM_RICHTEXT)
1583 ME_GetSelection(editor, &selStart, &selEnd);
1584 style = ME_GetSelectionInsertStyle(editor);
1586 ME_InternalDeleteText(editor, selStart, to - from, FALSE);
1588 /* Don't insert text at the end of the table row */
1589 if (!editor->bEmulateVersion10) /* v4.1 */
1591 ME_Paragraph *para = editor->pCursors->para;
1592 if (para->nFlags & (MEPF_ROWSTART | MEPF_ROWEND))
1594 para = para_next( para );
1595 editor->pCursors[0].para = para;
1596 editor->pCursors[0].run = para_first_run( para );
1597 editor->pCursors[0].nOffset = 0;
1599 editor->pCursors[1] = editor->pCursors[0];
1601 else /* v1.0 - 3.0 */
1603 if (editor->pCursors[0].run->nFlags & MERF_ENDPARA &&
1604 para_in_table( editor->pCursors[0].para ))
1605 return 0;
1608 else
1610 style = editor->pBuffer->pDefaultStyle;
1611 ME_AddRefStyle(style);
1612 set_selection_cursors(editor, 0, 0);
1613 ME_InternalDeleteText(editor, &editor->pCursors[1],
1614 ME_GetTextLength(editor), FALSE);
1615 from = to = 0;
1616 ME_ClearTempStyle(editor);
1617 editor_set_default_para_fmt( editor, &editor->pCursors[0].para->fmt );
1621 /* Back up undo mode to a local variable */
1622 nUndoMode = editor->nUndoMode;
1624 /* Only create an undo if SFF_SELECTION is set */
1625 if (!(format & SFF_SELECTION))
1626 editor->nUndoMode = umIgnore;
1628 inStream.editstream = stream;
1629 inStream.editstream->dwError = 0;
1630 inStream.dwSize = 0;
1631 inStream.dwUsed = 0;
1633 if (format & SF_RTF)
1635 /* Check if it's really RTF, and if it is not, use plain text */
1636 ME_StreamInFill(&inStream);
1637 if (!inStream.editstream->dwError)
1639 if ((!editor->bEmulateVersion10 && strncmp(inStream.buffer, "{\\rtf", 5) && strncmp(inStream.buffer, "{\\urtf", 6))
1640 || (editor->bEmulateVersion10 && *inStream.buffer != '{'))
1642 invalidRTF = TRUE;
1643 inStream.editstream->dwError = -16;
1648 if (!invalidRTF && !inStream.editstream->dwError)
1650 ME_Cursor start;
1651 from = ME_GetCursorOfs(&editor->pCursors[0]);
1652 if (format & SF_RTF) {
1654 /* setup the RTF parser */
1655 memset(&parser, 0, sizeof parser);
1656 RTFSetEditStream(&parser, &inStream);
1657 parser.rtfFormat = format&(SF_TEXT|SF_RTF);
1658 parser.editor = editor;
1659 parser.style = style;
1660 WriterInit(&parser);
1661 RTFInit(&parser);
1662 RTFSetReadHook(&parser, ME_RTFReadHook);
1663 RTFSetDestinationCallback(&parser, rtfShpPict, ME_RTFReadShpPictGroup);
1664 RTFSetDestinationCallback(&parser, rtfPict, ME_RTFReadPictGroup);
1665 RTFSetDestinationCallback(&parser, rtfObject, ME_RTFReadObjectGroup);
1666 RTFSetDestinationCallback(&parser, rtfParNumbering, ME_RTFReadParnumGroup);
1667 if (!parser.editor->bEmulateVersion10) /* v4.1 */
1669 RTFSetDestinationCallback(&parser, rtfNoNestTables, RTFSkipGroup);
1670 RTFSetDestinationCallback(&parser, rtfNestTableProps, RTFReadGroup);
1672 BeginFile(&parser);
1674 /* do the parsing */
1675 RTFRead(&parser);
1676 RTFFlushOutputBuffer(&parser);
1677 if (!editor->bEmulateVersion10) /* v4.1 */
1679 if (parser.tableDef && parser.tableDef->row_start &&
1680 (parser.nestingLevel > 0 || parser.canInheritInTbl))
1682 /* Delete any incomplete table row at the end of the rich text. */
1683 int nOfs, nChars;
1684 ME_Paragraph *para;
1686 parser.rtfMinor = rtfRow;
1687 /* Complete the table row before deleting it.
1688 * By doing it this way we will have the current paragraph format set
1689 * properly to reflect that is not in the complete table, and undo items
1690 * will be added for this change to the current paragraph format. */
1691 if (parser.nestingLevel > 0)
1693 while (parser.nestingLevel > 1)
1694 ME_RTFSpecialCharHook(&parser); /* Decrements nestingLevel */
1695 para = parser.tableDef->row_start;
1696 ME_RTFSpecialCharHook(&parser);
1698 else
1700 para = parser.tableDef->row_start;
1701 ME_RTFSpecialCharHook(&parser);
1702 assert( para->nFlags & MEPF_ROWEND );
1703 para = para_next( para );
1706 editor->pCursors[1].para = para;
1707 editor->pCursors[1].run = para_first_run( para );
1708 editor->pCursors[1].nOffset = 0;
1709 nOfs = ME_GetCursorOfs(&editor->pCursors[1]);
1710 nChars = ME_GetCursorOfs(&editor->pCursors[0]) - nOfs;
1711 ME_InternalDeleteText(editor, &editor->pCursors[1], nChars, TRUE);
1712 if (parser.tableDef) parser.tableDef->row_start = NULL;
1715 RTFDestroy(&parser);
1717 if (parser.stackTop > 0)
1719 while (--parser.stackTop >= 0)
1721 ME_ReleaseStyle(parser.style);
1722 parser.style = parser.stack[parser.stackTop].style;
1724 if (!inStream.editstream->dwError)
1725 inStream.editstream->dwError = HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
1728 /* Remove last line break, as mandated by tests. This is not affected by
1729 CR/LF counters, since RTF streaming presents only \para tokens, which
1730 are converted according to the standard rules: \r for 2.0, \r\n for 1.0
1732 if (stripLastCR && !(format & SFF_SELECTION)) {
1733 int newto;
1734 ME_GetSelection(editor, &selStart, &selEnd);
1735 newto = ME_GetCursorOfs(selEnd);
1736 if (newto > to + (editor->bEmulateVersion10 ? 1 : 0)) {
1737 WCHAR lastchar[3] = {'\0', '\0'};
1738 int linebreakSize = editor->bEmulateVersion10 ? 2 : 1;
1739 ME_Cursor linebreakCursor = *selEnd, lastcharCursor = *selEnd;
1740 CHARFORMAT2W cf;
1742 /* Set the final eop to the char fmt of the last char */
1743 cf.cbSize = sizeof(cf);
1744 cf.dwMask = CFM_ALL2;
1745 ME_MoveCursorChars(editor, &lastcharCursor, -1, FALSE);
1746 ME_GetCharFormat(editor, &lastcharCursor, &linebreakCursor, &cf);
1747 set_selection_cursors(editor, newto, -1);
1748 ME_SetSelectionCharFormat(editor, &cf);
1749 set_selection_cursors(editor, newto, newto);
1751 ME_MoveCursorChars(editor, &linebreakCursor, -linebreakSize, FALSE);
1752 ME_GetTextW(editor, lastchar, 2, &linebreakCursor, linebreakSize, FALSE, FALSE);
1753 if (lastchar[0] == '\r' && (lastchar[1] == '\n' || lastchar[1] == '\0')) {
1754 ME_InternalDeleteText(editor, &linebreakCursor, linebreakSize, FALSE);
1758 to = ME_GetCursorOfs(&editor->pCursors[0]);
1759 num_read = to - from;
1761 style = parser.style;
1763 else if (format & SF_TEXT)
1765 num_read = ME_StreamInText(editor, format, &inStream, style);
1766 to = ME_GetCursorOfs(&editor->pCursors[0]);
1768 else
1769 ERR("EM_STREAMIN without SF_TEXT or SF_RTF\n");
1770 /* put the cursor at the top */
1771 if (!(format & SFF_SELECTION))
1772 set_selection_cursors(editor, 0, 0);
1773 cursor_from_char_ofs( editor, from, &start );
1774 ME_UpdateLinkAttribute(editor, &start, to - from);
1777 /* Restore saved undo mode */
1778 editor->nUndoMode = nUndoMode;
1780 /* even if we didn't add an undo, we need to commit anything on the stack */
1781 ME_CommitUndo(editor);
1783 /* If SFF_SELECTION isn't set, delete any undos from before we started too */
1784 if (!(format & SFF_SELECTION))
1785 ME_EmptyUndoStack(editor);
1787 ME_ReleaseStyle(style);
1788 editor->nEventMask = nEventMask;
1789 ME_UpdateRepaint(editor, FALSE);
1790 if (!(format & SFF_SELECTION)) {
1791 ME_ClearTempStyle(editor);
1793 ME_SendSelChange(editor);
1794 ME_SendRequestResize(editor, FALSE);
1796 return num_read;
1800 typedef struct tagME_RTFStringStreamStruct
1802 char *string;
1803 int pos;
1804 int length;
1805 } ME_RTFStringStreamStruct;
1807 static DWORD CALLBACK ME_ReadFromRTFString(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
1809 ME_RTFStringStreamStruct *pStruct = (ME_RTFStringStreamStruct *)dwCookie;
1810 int count;
1812 count = min(cb, pStruct->length - pStruct->pos);
1813 memmove(lpBuff, pStruct->string + pStruct->pos, count);
1814 pStruct->pos += count;
1815 *pcb = count;
1816 return 0;
1819 static void
1820 ME_StreamInRTFString(ME_TextEditor *editor, BOOL selection, char *string)
1822 EDITSTREAM es;
1823 ME_RTFStringStreamStruct data;
1825 data.string = string;
1826 data.length = strlen(string);
1827 data.pos = 0;
1828 es.dwCookie = (DWORD_PTR)&data;
1829 es.pfnCallback = ME_ReadFromRTFString;
1830 ME_StreamIn(editor, SF_RTF | (selection ? SFF_SELECTION : 0), &es, TRUE);
1834 static int
1835 ME_FindText(ME_TextEditor *editor, DWORD flags, const CHARRANGE *chrg, const WCHAR *text, CHARRANGE *chrgText)
1837 const int nLen = lstrlenW(text);
1838 const int nTextLen = ME_GetTextLength(editor);
1839 int nMin, nMax;
1840 ME_Cursor cursor;
1841 WCHAR wLastChar = ' ';
1843 TRACE("flags==0x%08lx, chrg->cpMin==%ld, chrg->cpMax==%ld text==%s\n",
1844 flags, chrg->cpMin, chrg->cpMax, debugstr_w(text));
1846 if (flags & ~(FR_DOWN | FR_MATCHCASE | FR_WHOLEWORD))
1847 FIXME("Flags 0x%08lx not implemented\n",
1848 flags & ~(FR_DOWN | FR_MATCHCASE | FR_WHOLEWORD));
1850 nMin = chrg->cpMin;
1851 if (chrg->cpMax == -1)
1852 nMax = nTextLen;
1853 else
1854 nMax = chrg->cpMax > nTextLen ? nTextLen : chrg->cpMax;
1856 /* In 1.0 emulation, if cpMax reaches end of text, add the FR_DOWN flag */
1857 if (editor->bEmulateVersion10 && nMax == nTextLen)
1859 flags |= FR_DOWN;
1862 /* In 1.0 emulation, cpMin must always be no greater than cpMax */
1863 if (editor->bEmulateVersion10 && nMax < nMin)
1865 if (chrgText)
1867 chrgText->cpMin = -1;
1868 chrgText->cpMax = -1;
1870 return -1;
1873 /* when searching up, if cpMin < cpMax, then instead of searching
1874 * on [cpMin,cpMax], we search on [0,cpMin], otherwise, search on
1875 * [cpMax, cpMin]. The exception is when cpMax is -1, in which
1876 * case, it is always bigger than cpMin.
1878 if (!editor->bEmulateVersion10 && !(flags & FR_DOWN))
1880 int nSwap = nMax;
1882 nMax = nMin > nTextLen ? nTextLen : nMin;
1883 if (nMin < nSwap || chrg->cpMax == -1)
1884 nMin = 0;
1885 else
1886 nMin = nSwap;
1889 if (!nLen || nMin < 0 || nMax < 0 || nMax < nMin)
1891 if (chrgText)
1892 chrgText->cpMin = chrgText->cpMax = -1;
1893 return -1;
1896 if (flags & FR_DOWN) /* Forward search */
1898 /* If possible, find the character before where the search starts */
1899 if ((flags & FR_WHOLEWORD) && nMin)
1901 cursor_from_char_ofs( editor, nMin - 1, &cursor );
1902 wLastChar = *get_text( cursor.run, cursor.nOffset );
1903 ME_MoveCursorChars(editor, &cursor, 1, FALSE);
1905 else cursor_from_char_ofs( editor, nMin, &cursor );
1907 while (cursor.run && ME_GetCursorOfs(&cursor) + nLen <= nMax)
1909 ME_Run *run = cursor.run;
1910 int nCurStart = cursor.nOffset;
1911 int nMatched = 0;
1913 while (run && ME_CharCompare( *get_text( run, nCurStart + nMatched ), text[nMatched], (flags & FR_MATCHCASE)))
1915 if ((flags & FR_WHOLEWORD) && iswalnum(wLastChar))
1916 break;
1918 nMatched++;
1919 if (nMatched == nLen)
1921 ME_Run *next_run = run;
1922 int nNextStart = nCurStart;
1923 WCHAR wNextChar;
1925 /* Check to see if next character is a whitespace */
1926 if (flags & FR_WHOLEWORD)
1928 if (nCurStart + nMatched == run->len)
1930 next_run = run_next_all_paras( run );
1931 nNextStart = -nMatched;
1934 if (next_run)
1935 wNextChar = *get_text( next_run, nNextStart + nMatched );
1936 else
1937 wNextChar = ' ';
1939 if (iswalnum(wNextChar))
1940 break;
1943 cursor.nOffset += cursor.para->nCharOfs + cursor.run->nCharOfs;
1944 if (chrgText)
1946 chrgText->cpMin = cursor.nOffset;
1947 chrgText->cpMax = cursor.nOffset + nLen;
1949 TRACE("found at %d-%d\n", cursor.nOffset, cursor.nOffset + nLen);
1950 return cursor.nOffset;
1952 if (nCurStart + nMatched == run->len)
1954 run = run_next_all_paras( run );
1955 nCurStart = -nMatched;
1958 if (run)
1959 wLastChar = *get_text( run, nCurStart + nMatched );
1960 else
1961 wLastChar = ' ';
1963 cursor.nOffset++;
1964 if (cursor.nOffset == cursor.run->len)
1966 if (run_next_all_paras( cursor.run ))
1968 cursor.run = run_next_all_paras( cursor.run );
1969 cursor.para = cursor.run->para;
1970 cursor.nOffset = 0;
1972 else
1973 cursor.run = NULL;
1977 else /* Backward search */
1979 /* If possible, find the character after where the search ends */
1980 if ((flags & FR_WHOLEWORD) && nMax < nTextLen - 1)
1982 cursor_from_char_ofs( editor, nMax + 1, &cursor );
1983 wLastChar = *get_text( cursor.run, cursor.nOffset );
1984 ME_MoveCursorChars(editor, &cursor, -1, FALSE);
1986 else cursor_from_char_ofs( editor, nMax, &cursor );
1988 while (cursor.run && ME_GetCursorOfs(&cursor) - nLen >= nMin)
1990 ME_Run *run = cursor.run;
1991 ME_Paragraph *para = cursor.para;
1992 int nCurEnd = cursor.nOffset;
1993 int nMatched = 0;
1995 if (nCurEnd == 0 && run_prev_all_paras( run ))
1997 run = run_prev_all_paras( run );
1998 para = run->para;
1999 nCurEnd = run->len;
2002 while (run && ME_CharCompare( *get_text( run, nCurEnd - nMatched - 1 ),
2003 text[nLen - nMatched - 1], (flags & FR_MATCHCASE) ))
2005 if ((flags & FR_WHOLEWORD) && iswalnum(wLastChar))
2006 break;
2008 nMatched++;
2009 if (nMatched == nLen)
2011 ME_Run *prev_run = run;
2012 int nPrevEnd = nCurEnd;
2013 WCHAR wPrevChar;
2014 int nStart;
2016 /* Check to see if previous character is a whitespace */
2017 if (flags & FR_WHOLEWORD)
2019 if (nPrevEnd - nMatched == 0)
2021 prev_run = run_prev_all_paras( run );
2022 if (prev_run) nPrevEnd = prev_run->len + nMatched;
2025 if (prev_run) wPrevChar = *get_text( prev_run, nPrevEnd - nMatched - 1 );
2026 else wPrevChar = ' ';
2028 if (iswalnum(wPrevChar))
2029 break;
2032 nStart = para->nCharOfs + run->nCharOfs + nCurEnd - nMatched;
2033 if (chrgText)
2035 chrgText->cpMin = nStart;
2036 chrgText->cpMax = nStart + nLen;
2038 TRACE("found at %d-%d\n", nStart, nStart + nLen);
2039 return nStart;
2041 if (nCurEnd - nMatched == 0)
2043 if (run_prev_all_paras( run ))
2045 run = run_prev_all_paras( run );
2046 para = run->para;
2048 /* Don't care about pCurItem becoming NULL here; it's already taken
2049 * care of in the exterior loop condition */
2050 nCurEnd = run->len + nMatched;
2053 if (run)
2054 wLastChar = *get_text( run, nCurEnd - nMatched - 1 );
2055 else
2056 wLastChar = ' ';
2058 cursor.nOffset--;
2059 if (cursor.nOffset < 0)
2061 if (run_prev_all_paras( cursor.run ) )
2063 cursor.run = run_prev_all_paras( cursor.run );
2064 cursor.para = cursor.run->para;
2065 cursor.nOffset = cursor.run->len;
2067 else
2068 cursor.run = NULL;
2072 TRACE("not found\n");
2073 if (chrgText)
2074 chrgText->cpMin = chrgText->cpMax = -1;
2075 return -1;
2078 static int ME_GetTextEx(ME_TextEditor *editor, GETTEXTEX *ex, LPARAM pText)
2080 int nChars;
2081 ME_Cursor start;
2083 if (!ex->cb || !pText) return 0;
2085 if (ex->flags & ~(GT_SELECTION | GT_USECRLF))
2086 FIXME("GETTEXTEX flags 0x%08lx not supported\n", ex->flags & ~(GT_SELECTION | GT_USECRLF));
2088 if (ex->flags & GT_SELECTION)
2090 LONG from, to;
2091 int nStartCur = ME_GetSelectionOfs(editor, &from, &to);
2092 start = editor->pCursors[nStartCur];
2093 nChars = to - from;
2095 else
2097 ME_SetCursorToStart(editor, &start);
2098 nChars = INT_MAX;
2100 if (ex->codepage == CP_UNICODE)
2102 return ME_GetTextW(editor, (LPWSTR)pText, ex->cb / sizeof(WCHAR) - 1,
2103 &start, nChars, ex->flags & GT_USECRLF, FALSE);
2105 else
2107 /* potentially each char may be a CR, why calculate the exact value with O(N) when
2108 we can just take a bigger buffer? :)
2109 The above assumption still holds with CR/LF counters, since CR->CRLF expansion
2110 occurs only in richedit 2.0 mode, in which line breaks have only one CR
2112 int crlfmul = (ex->flags & GT_USECRLF) ? 2 : 1;
2113 DWORD buflen;
2114 LPWSTR buffer;
2115 LRESULT rc;
2117 buflen = min(crlfmul * nChars, ex->cb - 1);
2118 buffer = heap_alloc((buflen + 1) * sizeof(WCHAR));
2120 nChars = ME_GetTextW(editor, buffer, buflen, &start, nChars, ex->flags & GT_USECRLF, FALSE);
2121 rc = WideCharToMultiByte(ex->codepage, 0, buffer, nChars + 1,
2122 (LPSTR)pText, ex->cb, ex->lpDefaultChar, ex->lpUsedDefChar);
2123 if (rc) rc--; /* do not count 0 terminator */
2125 heap_free(buffer);
2126 return rc;
2130 static int get_text_range( ME_TextEditor *editor, WCHAR *buffer,
2131 const ME_Cursor *start, int len )
2133 if (!buffer) return 0;
2134 return ME_GetTextW( editor, buffer, INT_MAX, start, len, FALSE, FALSE );
2137 int set_selection( ME_TextEditor *editor, int to, int from )
2139 int end;
2141 TRACE("%d - %d\n", to, from );
2143 if (!editor->bHideSelection) ME_InvalidateSelection( editor );
2144 end = set_selection_cursors( editor, to, from );
2145 editor_ensure_visible( editor, &editor->pCursors[0] );
2146 if (!editor->bHideSelection) ME_InvalidateSelection( editor );
2147 update_caret( editor );
2148 ME_SendSelChange( editor );
2150 return end;
2153 typedef struct tagME_GlobalDestStruct
2155 HGLOBAL hData;
2156 int nLength;
2157 } ME_GlobalDestStruct;
2159 static DWORD CALLBACK ME_ReadFromHGLOBALUnicode(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
2161 ME_GlobalDestStruct *pData = (ME_GlobalDestStruct *)dwCookie;
2162 int i;
2163 WORD *pSrc, *pDest;
2165 cb = cb >> 1;
2166 pDest = (WORD *)lpBuff;
2167 pSrc = GlobalLock(pData->hData);
2168 for (i = 0; i<cb && pSrc[pData->nLength+i]; i++) {
2169 pDest[i] = pSrc[pData->nLength+i];
2171 pData->nLength += i;
2172 *pcb = 2*i;
2173 GlobalUnlock(pData->hData);
2174 return 0;
2177 static DWORD CALLBACK ME_ReadFromHGLOBALRTF(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
2179 ME_GlobalDestStruct *pData = (ME_GlobalDestStruct *)dwCookie;
2180 int i;
2181 BYTE *pSrc, *pDest;
2183 pDest = lpBuff;
2184 pSrc = GlobalLock(pData->hData);
2185 for (i = 0; i<cb && pSrc[pData->nLength+i]; i++) {
2186 pDest[i] = pSrc[pData->nLength+i];
2188 pData->nLength += i;
2189 *pcb = i;
2190 GlobalUnlock(pData->hData);
2191 return 0;
2194 static HRESULT paste_rtf(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2196 EDITSTREAM es;
2197 ME_GlobalDestStruct gds;
2198 HRESULT hr;
2200 gds.hData = med->u.hGlobal;
2201 gds.nLength = 0;
2202 es.dwCookie = (DWORD_PTR)&gds;
2203 es.pfnCallback = ME_ReadFromHGLOBALRTF;
2204 hr = ME_StreamIn( editor, SF_RTF | SFF_SELECTION, &es, FALSE ) == 0 ? E_FAIL : S_OK;
2205 ReleaseStgMedium( med );
2206 return hr;
2209 static HRESULT paste_text(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2211 EDITSTREAM es;
2212 ME_GlobalDestStruct gds;
2213 HRESULT hr;
2215 gds.hData = med->u.hGlobal;
2216 gds.nLength = 0;
2217 es.dwCookie = (DWORD_PTR)&gds;
2218 es.pfnCallback = ME_ReadFromHGLOBALUnicode;
2219 hr = ME_StreamIn( editor, SF_TEXT | SF_UNICODE | SFF_SELECTION, &es, FALSE ) == 0 ? E_FAIL : S_OK;
2220 ReleaseStgMedium( med );
2221 return hr;
2224 static HRESULT paste_emf(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2226 HRESULT hr;
2227 SIZEL sz = {0, 0};
2229 hr = insert_static_object( editor, med->u.hEnhMetaFile, NULL, &sz );
2230 if (SUCCEEDED(hr))
2232 ME_CommitUndo( editor );
2233 ME_UpdateRepaint( editor, FALSE );
2235 else
2236 ReleaseStgMedium( med );
2238 return hr;
2241 static struct paste_format
2243 FORMATETC fmt;
2244 HRESULT (*paste)(ME_TextEditor *, FORMATETC *, STGMEDIUM *);
2245 const WCHAR *name;
2246 } paste_formats[] =
2248 {{ -1, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, paste_rtf, L"Rich Text Format" },
2249 {{ CF_UNICODETEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, paste_text },
2250 {{ CF_ENHMETAFILE, NULL, DVASPECT_CONTENT, -1, TYMED_ENHMF }, paste_emf },
2251 {{ 0 }}
2254 static void init_paste_formats(void)
2256 struct paste_format *format;
2257 static int done;
2259 if (!done)
2261 for (format = paste_formats; format->fmt.cfFormat; format++)
2263 if (format->name)
2264 format->fmt.cfFormat = RegisterClipboardFormatW( format->name );
2266 done = 1;
2270 static BOOL paste_special(ME_TextEditor *editor, UINT cf, REPASTESPECIAL *ps, BOOL check_only)
2272 HRESULT hr;
2273 STGMEDIUM med;
2274 struct paste_format *format;
2275 IDataObject *data;
2277 /* Protect read-only edit control from modification */
2278 if (editor->props & TXTBIT_READONLY)
2280 if (!check_only) editor_beep( editor, MB_ICONERROR );
2281 return FALSE;
2284 init_paste_formats();
2286 if (ps && ps->dwAspect != DVASPECT_CONTENT)
2287 FIXME("Ignoring aspect %lx\n", ps->dwAspect);
2289 hr = OleGetClipboard( &data );
2290 if (hr != S_OK) return FALSE;
2292 if (cf == CF_TEXT) cf = CF_UNICODETEXT;
2294 hr = S_FALSE;
2295 for (format = paste_formats; format->fmt.cfFormat; format++)
2297 if (cf && cf != format->fmt.cfFormat) continue;
2298 hr = IDataObject_QueryGetData( data, &format->fmt );
2299 if (hr == S_OK)
2301 if (!check_only)
2303 hr = IDataObject_GetData( data, &format->fmt, &med );
2304 if (hr != S_OK) goto done;
2305 hr = format->paste( editor, &format->fmt, &med );
2307 break;
2311 done:
2312 IDataObject_Release( data );
2314 return hr == S_OK;
2317 static HRESULT editor_copy( ME_TextEditor *editor, ME_Cursor *start, int chars, IDataObject **data_out )
2319 IDataObject *data = NULL;
2320 HRESULT hr = S_OK;
2322 if (editor->lpOleCallback)
2324 CHARRANGE range;
2325 range.cpMin = ME_GetCursorOfs( start );
2326 range.cpMax = range.cpMin + chars;
2327 hr = IRichEditOleCallback_GetClipboardData( editor->lpOleCallback, &range, RECO_COPY, &data );
2330 if (FAILED( hr ) || !data)
2331 hr = ME_GetDataObject( editor, start, chars, &data );
2333 if (SUCCEEDED( hr ))
2335 if (data_out)
2336 *data_out = data;
2337 else
2339 hr = OleSetClipboard( data );
2340 IDataObject_Release( data );
2344 return hr;
2347 HRESULT editor_copy_or_cut( ME_TextEditor *editor, BOOL cut, ME_Cursor *start, int count,
2348 IDataObject **data_out )
2350 HRESULT hr;
2352 if (cut && (editor->props & TXTBIT_READONLY))
2354 return E_ACCESSDENIED;
2357 hr = editor_copy( editor, start, count, data_out );
2358 if (SUCCEEDED(hr) && cut)
2360 ME_InternalDeleteText( editor, start, count, FALSE );
2361 ME_CommitUndo( editor );
2362 ME_UpdateRepaint( editor, TRUE );
2364 return hr;
2367 static BOOL copy_or_cut( ME_TextEditor *editor, BOOL cut )
2369 HRESULT hr;
2370 LONG offs, count;
2371 int start_cursor = ME_GetSelectionOfs( editor, &offs, &count );
2372 ME_Cursor *sel_start = &editor->pCursors[start_cursor];
2374 if (editor->password_char) return FALSE;
2376 count -= offs;
2377 hr = editor_copy_or_cut( editor, cut, sel_start, count, NULL );
2378 if (FAILED( hr )) editor_beep( editor, MB_ICONERROR );
2380 return SUCCEEDED( hr );
2383 static void ME_UpdateSelectionLinkAttribute(ME_TextEditor *editor)
2385 ME_Paragraph *start_para, *end_para;
2386 ME_Cursor *from, *to, start;
2387 int num_chars;
2389 if (!editor->AutoURLDetect_bEnable) return;
2391 ME_GetSelection(editor, &from, &to);
2393 /* Find paragraph previous to the one that contains start cursor */
2394 start_para = from->para;
2395 if (para_prev( start_para )) start_para = para_prev( start_para );
2397 /* Find paragraph that contains end cursor */
2398 end_para = para_next( to->para );
2400 start.para = start_para;
2401 start.run = para_first_run( start_para );
2402 start.nOffset = 0;
2403 num_chars = end_para->nCharOfs - start_para->nCharOfs;
2405 ME_UpdateLinkAttribute( editor, &start, num_chars );
2408 static BOOL handle_enter(ME_TextEditor *editor)
2410 BOOL shift_is_down = GetKeyState(VK_SHIFT) & 0x8000;
2412 if (editor->props & TXTBIT_MULTILINE)
2414 ME_Cursor cursor = editor->pCursors[0];
2415 ME_Paragraph *para = cursor.para;
2416 LONG from, to;
2417 ME_Style *style, *eop_style;
2419 if (editor->props & TXTBIT_READONLY)
2421 editor_beep( editor, MB_ICONERROR );
2422 return TRUE;
2425 ME_GetSelectionOfs(editor, &from, &to);
2426 if (editor->nTextLimit > ME_GetTextLength(editor) - (to-from))
2428 if (!editor->bEmulateVersion10) /* v4.1 */
2430 if (para->nFlags & MEPF_ROWEND)
2432 /* Add a new table row after this row. */
2433 para = table_append_row( editor, para );
2434 para = para_next( para );
2435 editor->pCursors[0].para = para;
2436 editor->pCursors[0].run = para_first_run( para );
2437 editor->pCursors[0].nOffset = 0;
2438 editor->pCursors[1] = editor->pCursors[0];
2439 ME_CommitUndo(editor);
2440 ME_UpdateRepaint(editor, FALSE);
2441 return TRUE;
2443 else if (para == editor->pCursors[1].para &&
2444 cursor.nOffset + cursor.run->nCharOfs == 0 &&
2445 para_prev( para ) && para_prev( para )->nFlags & MEPF_ROWSTART &&
2446 !para_prev( para )->nCharOfs)
2448 /* Insert a newline before the table. */
2449 para = para_prev( para );
2450 para->nFlags &= ~MEPF_ROWSTART;
2451 editor->pCursors[0].para = para;
2452 editor->pCursors[0].run = para_first_run( para );
2453 editor->pCursors[1] = editor->pCursors[0];
2454 ME_InsertTextFromCursor( editor, 0, L"\r", 1, editor->pCursors[0].run->style );
2455 para = editor_first_para( editor );
2456 editor_set_default_para_fmt( editor, &para->fmt );
2457 para->nFlags = 0;
2458 para_mark_rewrap( editor, para );
2459 editor->pCursors[0].para = para;
2460 editor->pCursors[0].run = para_first_run( para );
2461 editor->pCursors[1] = editor->pCursors[0];
2462 para_next( para )->nFlags |= MEPF_ROWSTART;
2463 ME_CommitCoalescingUndo(editor);
2464 ME_UpdateRepaint(editor, FALSE);
2465 return TRUE;
2468 else /* v1.0 - 3.0 */
2470 ME_Paragraph *para = cursor.para;
2471 if (para_in_table( para ))
2473 if (cursor.run->nFlags & MERF_ENDPARA)
2475 if (from == to)
2477 ME_ContinueCoalescingTransaction(editor);
2478 para = table_append_row( editor, para );
2479 editor->pCursors[0].para = para;
2480 editor->pCursors[0].run = para_first_run( para );
2481 editor->pCursors[0].nOffset = 0;
2482 editor->pCursors[1] = editor->pCursors[0];
2483 ME_CommitCoalescingUndo(editor);
2484 ME_UpdateRepaint(editor, FALSE);
2485 return TRUE;
2488 else
2490 ME_ContinueCoalescingTransaction(editor);
2491 if (cursor.run->nCharOfs + cursor.nOffset == 0 &&
2492 para_prev( para ) && !para_in_table( para_prev( para ) ))
2494 /* Insert newline before table */
2495 cursor.run = para_end_run( para_prev( para ) );
2496 if (cursor.run)
2498 editor->pCursors[0].run = cursor.run;
2499 editor->pCursors[0].para = para_prev( para );
2501 editor->pCursors[0].nOffset = 0;
2502 editor->pCursors[1] = editor->pCursors[0];
2503 ME_InsertTextFromCursor( editor, 0, L"\r", 1, editor->pCursors[0].run->style );
2505 else
2507 editor->pCursors[1] = editor->pCursors[0];
2508 para = table_append_row( editor, para );
2509 editor->pCursors[0].para = para;
2510 editor->pCursors[0].run = para_first_run( para );
2511 editor->pCursors[0].nOffset = 0;
2512 editor->pCursors[1] = editor->pCursors[0];
2514 ME_CommitCoalescingUndo(editor);
2515 ME_UpdateRepaint(editor, FALSE);
2516 return TRUE;
2521 style = style_get_insert_style( editor, editor->pCursors );
2523 /* Normally the new eop style is the insert style, however in a list it is copied from the existing
2524 eop style (this prevents the list label style changing when the new eop is inserted).
2525 No extra ref is taken here on eop_style. */
2526 if (para->fmt.wNumbering)
2527 eop_style = para->eop_run->style;
2528 else
2529 eop_style = style;
2530 ME_ContinueCoalescingTransaction(editor);
2531 if (shift_is_down)
2532 ME_InsertEndRowFromCursor(editor, 0);
2533 else
2534 if (!editor->bEmulateVersion10)
2535 ME_InsertTextFromCursor(editor, 0, L"\r", 1, eop_style);
2536 else
2537 ME_InsertTextFromCursor(editor, 0, L"\r\n", 2, eop_style);
2538 ME_CommitCoalescingUndo(editor);
2539 SetCursor(NULL);
2541 ME_UpdateSelectionLinkAttribute(editor);
2542 ME_UpdateRepaint(editor, FALSE);
2543 ME_SaveTempStyle(editor, style); /* set the temp insert style for the new para */
2544 ME_ReleaseStyle(style);
2546 return TRUE;
2548 return FALSE;
2551 static BOOL
2552 ME_KeyDown(ME_TextEditor *editor, WORD nKey)
2554 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2555 BOOL shift_is_down = GetKeyState(VK_SHIFT) & 0x8000;
2557 if (editor->bMouseCaptured)
2558 return FALSE;
2559 if (nKey != VK_SHIFT && nKey != VK_CONTROL && nKey != VK_MENU)
2560 editor->nSelectionType = stPosition;
2562 switch (nKey)
2564 case VK_LEFT:
2565 case VK_RIGHT:
2566 case VK_HOME:
2567 case VK_END:
2568 editor->nUDArrowX = -1;
2569 /* fall through */
2570 case VK_UP:
2571 case VK_DOWN:
2572 case VK_PRIOR:
2573 case VK_NEXT:
2574 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
2575 ME_ArrowKey(editor, nKey, shift_is_down, ctrl_is_down);
2576 return TRUE;
2577 case VK_BACK:
2578 case VK_DELETE:
2579 editor->nUDArrowX = -1;
2580 /* FIXME backspace and delete aren't the same, they act different wrt paragraph style of the merged paragraph */
2581 if (editor->props & TXTBIT_READONLY)
2582 return FALSE;
2583 if (ME_IsSelection(editor))
2585 ME_DeleteSelection(editor);
2586 ME_CommitUndo(editor);
2588 else if (nKey == VK_DELETE)
2590 /* Delete stops group typing.
2591 * (See MSDN remarks on EM_STOPGROUPTYPING message) */
2592 ME_DeleteTextAtCursor(editor, 1, 1);
2593 ME_CommitUndo(editor);
2595 else if (ME_ArrowKey(editor, VK_LEFT, FALSE, FALSE))
2597 BOOL bDeletionSucceeded;
2598 /* Backspace can be grouped for a single undo */
2599 ME_ContinueCoalescingTransaction(editor);
2600 bDeletionSucceeded = ME_DeleteTextAtCursor(editor, 1, 1);
2601 if (!bDeletionSucceeded && !editor->bEmulateVersion10) { /* v4.1 */
2602 /* Deletion was prevented so the cursor is moved back to where it was.
2603 * (e.g. this happens when trying to delete cell boundaries)
2605 ME_ArrowKey(editor, VK_RIGHT, FALSE, FALSE);
2607 ME_CommitCoalescingUndo(editor);
2609 else
2610 return TRUE;
2611 table_move_from_row_start( editor );
2612 ME_UpdateSelectionLinkAttribute(editor);
2613 ME_UpdateRepaint(editor, FALSE);
2614 ME_SendRequestResize(editor, FALSE);
2615 return TRUE;
2616 case VK_RETURN:
2617 if (!editor->bEmulateVersion10)
2618 return handle_enter(editor);
2619 break;
2620 case 'A':
2621 if (ctrl_is_down)
2623 set_selection( editor, 0, -1 );
2624 return TRUE;
2626 break;
2627 case 'V':
2628 if (ctrl_is_down)
2629 return paste_special( editor, 0, NULL, FALSE );
2630 break;
2631 case 'C':
2632 case 'X':
2633 if (ctrl_is_down)
2634 return copy_or_cut(editor, nKey == 'X');
2635 break;
2636 case 'Z':
2637 if (ctrl_is_down)
2639 ME_Undo(editor);
2640 return TRUE;
2642 break;
2643 case 'Y':
2644 if (ctrl_is_down)
2646 ME_Redo(editor);
2647 return TRUE;
2649 break;
2651 default:
2652 if (nKey != VK_SHIFT && nKey != VK_CONTROL && nKey && nKey != VK_MENU)
2653 editor->nUDArrowX = -1;
2654 if (ctrl_is_down)
2656 if (nKey == 'W')
2658 CHARFORMAT2W chf;
2659 char buf[2048];
2660 chf.cbSize = sizeof(chf);
2662 ME_GetSelectionCharFormat(editor, &chf);
2663 ME_DumpStyleToBuf(&chf, buf);
2664 MessageBoxA(NULL, buf, "Style dump", MB_OK);
2666 if (nKey == 'Q')
2668 ME_CheckCharOffsets(editor);
2672 return FALSE;
2675 static LRESULT handle_wm_char( ME_TextEditor *editor, WCHAR wstr, LPARAM flags )
2677 if (editor->bMouseCaptured)
2678 return 0;
2680 if (editor->props & TXTBIT_READONLY)
2682 editor_beep( editor, MB_ICONERROR );
2683 return 0; /* FIXME really 0 ? */
2686 if (editor->bEmulateVersion10 && wstr == '\r')
2687 handle_enter(editor);
2689 if ((unsigned)wstr >= ' ' || wstr == '\t')
2691 ME_Cursor cursor = editor->pCursors[0];
2692 ME_Paragraph *para = cursor.para;
2693 LONG from, to;
2694 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2695 ME_GetSelectionOfs(editor, &from, &to);
2696 if (wstr == '\t' &&
2697 /* v4.1 allows tabs to be inserted with ctrl key down */
2698 !(ctrl_is_down && !editor->bEmulateVersion10))
2700 BOOL selected_row = FALSE;
2702 if (ME_IsSelection(editor) &&
2703 cursor.run->nCharOfs + cursor.nOffset == 0 &&
2704 to == ME_GetCursorOfs(&editor->pCursors[0]) && para_prev( para ))
2706 para = para_prev( para );
2707 selected_row = TRUE;
2709 if (para_in_table( para ))
2711 table_handle_tab( editor, selected_row );
2712 ME_CommitUndo(editor);
2713 return 0;
2716 else if (!editor->bEmulateVersion10) /* v4.1 */
2718 if (para->nFlags & MEPF_ROWEND)
2720 if (from == to)
2722 para = para_next( para );
2723 if (para->nFlags & MEPF_ROWSTART) para = para_next( para );
2724 editor->pCursors[0].para = para;
2725 editor->pCursors[0].run = para_first_run( para );
2726 editor->pCursors[0].nOffset = 0;
2727 editor->pCursors[1] = editor->pCursors[0];
2731 else /* v1.0 - 3.0 */
2733 if (para_in_table( para ) && cursor.run->nFlags & MERF_ENDPARA && from == to)
2735 /* Text should not be inserted at the end of the table. */
2736 editor_beep( editor, -1 );
2737 return 0;
2740 /* FIXME maybe it would make sense to call EM_REPLACESEL instead ? */
2741 /* WM_CHAR is restricted to nTextLimit */
2742 if(editor->nTextLimit > ME_GetTextLength(editor) - (to-from))
2744 ME_Style *style = style_get_insert_style( editor, editor->pCursors );
2745 ME_ContinueCoalescingTransaction(editor);
2746 ME_InsertTextFromCursor(editor, 0, &wstr, 1, style);
2747 ME_ReleaseStyle(style);
2748 ME_CommitCoalescingUndo(editor);
2749 ITextHost_TxSetCursor(editor->texthost, NULL, FALSE);
2752 ME_UpdateSelectionLinkAttribute(editor);
2753 ME_UpdateRepaint(editor, FALSE);
2755 return 0;
2758 /* Process the message and calculate the new click count.
2760 * returns: The click count if it is mouse down event, else returns 0. */
2761 static int ME_CalculateClickCount(ME_TextEditor *editor, UINT msg, WPARAM wParam,
2762 LPARAM lParam)
2764 static int clickNum = 0;
2765 if (msg < WM_MOUSEFIRST || msg > WM_MOUSELAST)
2766 return 0;
2768 if ((msg == WM_LBUTTONDBLCLK) ||
2769 (msg == WM_RBUTTONDBLCLK) ||
2770 (msg == WM_MBUTTONDBLCLK) ||
2771 (msg == WM_XBUTTONDBLCLK))
2773 msg -= (WM_LBUTTONDBLCLK - WM_LBUTTONDOWN);
2776 if ((msg == WM_LBUTTONDOWN) ||
2777 (msg == WM_RBUTTONDOWN) ||
2778 (msg == WM_MBUTTONDOWN) ||
2779 (msg == WM_XBUTTONDOWN))
2781 static MSG prevClickMsg;
2782 MSG clickMsg;
2783 /* Compare the editor instead of the hwnd so that the this
2784 * can still be done for windowless richedit controls. */
2785 clickMsg.hwnd = (HWND)editor;
2786 clickMsg.message = msg;
2787 clickMsg.wParam = wParam;
2788 clickMsg.lParam = lParam;
2789 clickMsg.time = GetMessageTime();
2790 clickMsg.pt.x = (short)LOWORD(lParam);
2791 clickMsg.pt.y = (short)HIWORD(lParam);
2792 if ((clickNum != 0) &&
2793 (clickMsg.message == prevClickMsg.message) &&
2794 (clickMsg.hwnd == prevClickMsg.hwnd) &&
2795 (clickMsg.wParam == prevClickMsg.wParam) &&
2796 (clickMsg.time - prevClickMsg.time < GetDoubleClickTime()) &&
2797 (abs(clickMsg.pt.x - prevClickMsg.pt.x) < GetSystemMetrics(SM_CXDOUBLECLK)/2) &&
2798 (abs(clickMsg.pt.y - prevClickMsg.pt.y) < GetSystemMetrics(SM_CYDOUBLECLK)/2))
2800 clickNum++;
2801 } else {
2802 clickNum = 1;
2804 prevClickMsg = clickMsg;
2805 } else {
2806 return 0;
2808 return clickNum;
2811 static BOOL is_link( ME_Run *run )
2813 return (run->style->fmt.dwMask & CFM_LINK) && (run->style->fmt.dwEffects & CFE_LINK);
2816 void editor_set_cursor( ME_TextEditor *editor, int x, int y )
2818 ME_Cursor pos;
2819 static HCURSOR cursor_arrow, cursor_hand, cursor_ibeam, cursor_reverse;
2820 HCURSOR cursor;
2822 if (!cursor_arrow)
2824 cursor_arrow = LoadCursorW( NULL, MAKEINTRESOURCEW( IDC_ARROW ) );
2825 cursor_hand = LoadCursorW( NULL, MAKEINTRESOURCEW( IDC_HAND ) );
2826 cursor_ibeam = LoadCursorW( NULL, MAKEINTRESOURCEW( IDC_IBEAM ) );
2827 cursor_reverse = LoadCursorW( dll_instance, MAKEINTRESOURCEW( OCR_REVERSE ) );
2830 cursor = cursor_ibeam;
2832 if ((editor->nSelectionType == stLine && editor->bMouseCaptured) ||
2833 (!editor->bEmulateVersion10 && y < editor->rcFormat.top && x < editor->rcFormat.left))
2834 cursor = cursor_reverse;
2835 else if (y < editor->rcFormat.top || y > editor->rcFormat.bottom)
2837 if (editor->bEmulateVersion10) cursor = cursor_arrow;
2838 else cursor = cursor_ibeam;
2840 else if (x < editor->rcFormat.left) cursor = cursor_reverse;
2841 else if (cursor_from_coords( editor, x, y, &pos ))
2843 ME_Run *run = pos.run;
2845 if (is_link( run )) cursor = cursor_hand;
2847 else if (ME_IsSelection( editor ))
2849 LONG start, end;
2850 int offset = ME_GetCursorOfs( &pos );
2852 ME_GetSelectionOfs( editor, &start, &end );
2853 if (start <= offset && end >= offset) cursor = cursor_arrow;
2857 ITextHost_TxSetCursor( editor->texthost, cursor, cursor == cursor_ibeam );
2860 static LONG ME_GetSelectionType(ME_TextEditor *editor)
2862 LONG sel_type = SEL_EMPTY;
2863 LONG start, end;
2865 ME_GetSelectionOfs(editor, &start, &end);
2866 if (start == end)
2867 sel_type = SEL_EMPTY;
2868 else
2870 LONG object_count = 0, character_count = 0;
2871 int i;
2873 for (i = 0; i < end - start; i++)
2875 ME_Cursor cursor;
2877 cursor_from_char_ofs( editor, start + i, &cursor );
2878 if (cursor.run->reobj) object_count++;
2879 else character_count++;
2880 if (character_count >= 2 && object_count >= 2)
2881 return (SEL_TEXT | SEL_MULTICHAR | SEL_OBJECT | SEL_MULTIOBJECT);
2883 if (character_count)
2885 sel_type |= SEL_TEXT;
2886 if (character_count >= 2)
2887 sel_type |= SEL_MULTICHAR;
2889 if (object_count)
2891 sel_type |= SEL_OBJECT;
2892 if (object_count >= 2)
2893 sel_type |= SEL_MULTIOBJECT;
2896 return sel_type;
2899 static BOOL ME_ShowContextMenu(ME_TextEditor *editor, int x, int y)
2901 CHARRANGE selrange;
2902 HMENU menu;
2903 int seltype;
2904 HWND hwnd, parent;
2906 if (!editor->lpOleCallback || !editor->have_texthost2) return FALSE;
2907 if (FAILED( ITextHost2_TxGetWindow( editor->texthost, &hwnd ))) return FALSE;
2908 parent = GetParent( hwnd );
2909 if (!parent) parent = hwnd;
2911 ME_GetSelectionOfs( editor, &selrange.cpMin, &selrange.cpMax );
2912 seltype = ME_GetSelectionType( editor );
2913 if (SUCCEEDED( IRichEditOleCallback_GetContextMenu( editor->lpOleCallback, seltype, NULL, &selrange, &menu ) ))
2915 TrackPopupMenu( menu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, x, y, 0, parent, NULL );
2916 DestroyMenu( menu );
2918 return TRUE;
2921 ME_TextEditor *ME_MakeEditor(ITextHost *texthost, BOOL bEmulateVersion10)
2923 ME_TextEditor *ed = heap_alloc(sizeof(*ed));
2924 int i;
2925 LONG selbarwidth;
2926 HRESULT hr;
2928 ed->sizeWindow.cx = ed->sizeWindow.cy = 0;
2929 if (ITextHost_QueryInterface( texthost, &IID_ITextHost2, (void **)&ed->texthost ) == S_OK)
2931 ITextHost_Release( texthost );
2932 ed->have_texthost2 = TRUE;
2934 else
2936 ed->texthost = (ITextHost2 *)texthost;
2937 ed->have_texthost2 = FALSE;
2940 ed->bEmulateVersion10 = bEmulateVersion10;
2941 ed->in_place_active = FALSE;
2942 ed->total_rows = 0;
2943 ITextHost_TxGetPropertyBits( ed->texthost, TXTBIT_RICHTEXT | TXTBIT_MULTILINE | TXTBIT_READONLY |
2944 TXTBIT_USEPASSWORD | TXTBIT_HIDESELECTION | TXTBIT_SAVESELECTION |
2945 TXTBIT_AUTOWORDSEL | TXTBIT_VERTICAL | TXTBIT_WORDWRAP | TXTBIT_ALLOWBEEP |
2946 TXTBIT_DISABLEDRAG,
2947 &ed->props );
2948 ITextHost_TxGetScrollBars( ed->texthost, &ed->scrollbars );
2949 ed->pBuffer = ME_MakeText();
2950 ed->nZoomNumerator = ed->nZoomDenominator = 0;
2951 ed->nAvailWidth = 0; /* wrap to client area */
2952 list_init( &ed->style_list );
2953 ME_MakeFirstParagraph(ed);
2954 /* The four cursors are for:
2955 * 0 - The position where the caret is shown
2956 * 1 - The anchored end of the selection (for normal selection)
2957 * 2 & 3 - The anchored start and end respectively for word, line,
2958 * or paragraph selection.
2960 ed->nCursors = 4;
2961 ed->pCursors = heap_alloc(ed->nCursors * sizeof(*ed->pCursors));
2962 ME_SetCursorToStart(ed, &ed->pCursors[0]);
2963 ed->pCursors[1] = ed->pCursors[0];
2964 ed->pCursors[2] = ed->pCursors[0];
2965 ed->pCursors[3] = ed->pCursors[1];
2966 ed->nLastTotalLength = ed->nTotalLength = 0;
2967 ed->nLastTotalWidth = ed->nTotalWidth = 0;
2968 ed->nUDArrowX = -1;
2969 ed->nEventMask = 0;
2970 ed->nModifyStep = 0;
2971 ed->nTextLimit = TEXT_LIMIT_DEFAULT;
2972 list_init( &ed->undo_stack );
2973 list_init( &ed->redo_stack );
2974 ed->nUndoStackSize = 0;
2975 ed->nUndoLimit = STACK_SIZE_DEFAULT;
2976 ed->nUndoMode = umAddToUndo;
2977 ed->undo_ctl_state = undoActive;
2978 ed->nParagraphs = 1;
2979 ed->nLastSelStart = ed->nLastSelEnd = 0;
2980 ed->last_sel_start_para = ed->last_sel_end_para = ed->pCursors[0].para;
2981 ed->bHideSelection = FALSE;
2982 ed->pfnWordBreak = NULL;
2983 ed->richole = NULL;
2984 ed->lpOleCallback = NULL;
2985 ed->mode = TM_MULTILEVELUNDO | TM_MULTICODEPAGE;
2986 ed->mode |= (ed->props & TXTBIT_RICHTEXT) ? TM_RICHTEXT : TM_PLAINTEXT;
2987 ed->AutoURLDetect_bEnable = FALSE;
2988 ed->bHaveFocus = FALSE;
2989 ed->bMouseCaptured = FALSE;
2990 ed->caret_hidden = FALSE;
2991 ed->caret_height = 0;
2992 for (i=0; i<HFONT_CACHE_SIZE; i++)
2994 ed->pFontCache[i].nRefs = 0;
2995 ed->pFontCache[i].nAge = 0;
2996 ed->pFontCache[i].hFont = NULL;
2999 ME_CheckCharOffsets(ed);
3000 SetRectEmpty(&ed->rcFormat);
3001 hr = ITextHost_TxGetSelectionBarWidth( ed->texthost, &selbarwidth );
3002 /* FIXME: Convert selbarwidth from HIMETRIC to pixels */
3003 if (hr == S_OK && selbarwidth) ed->selofs = SELECTIONBAR_WIDTH;
3004 else ed->selofs = 0;
3005 ed->nSelectionType = stPosition;
3007 ed->password_char = 0;
3008 if (ed->props & TXTBIT_USEPASSWORD)
3009 ITextHost_TxGetPasswordChar( ed->texthost, &ed->password_char );
3011 ed->bWordWrap = (ed->props & TXTBIT_WORDWRAP) && (ed->props & TXTBIT_MULTILINE);
3013 ed->notified_cr.cpMin = ed->notified_cr.cpMax = 0;
3015 /* Default scrollbar information */
3016 ed->vert_si.cbSize = sizeof(SCROLLINFO);
3017 ed->vert_si.nMin = 0;
3018 ed->vert_si.nMax = 0;
3019 ed->vert_si.nPage = 0;
3020 ed->vert_si.nPos = 0;
3021 ed->vert_sb_enabled = 0;
3023 ed->horz_si.cbSize = sizeof(SCROLLINFO);
3024 ed->horz_si.nMin = 0;
3025 ed->horz_si.nMax = 0;
3026 ed->horz_si.nPage = 0;
3027 ed->horz_si.nPos = 0;
3028 ed->horz_sb_enabled = 0;
3030 if (ed->scrollbars & ES_DISABLENOSCROLL)
3032 if (ed->scrollbars & WS_VSCROLL)
3034 ITextHost_TxSetScrollRange( ed->texthost, SB_VERT, 0, 1, TRUE );
3035 ITextHost_TxEnableScrollBar( ed->texthost, SB_VERT, ESB_DISABLE_BOTH );
3037 if (ed->scrollbars & WS_HSCROLL)
3039 ITextHost_TxSetScrollRange( ed->texthost, SB_HORZ, 0, 1, TRUE );
3040 ITextHost_TxEnableScrollBar( ed->texthost, SB_HORZ, ESB_DISABLE_BOTH );
3044 ed->wheel_remain = 0;
3046 ed->back_style = TXTBACK_OPAQUE;
3047 ITextHost_TxGetBackStyle( ed->texthost, &ed->back_style );
3049 list_init( &ed->reobj_list );
3050 OleInitialize(NULL);
3052 return ed;
3055 void ME_DestroyEditor(ME_TextEditor *editor)
3057 ME_DisplayItem *p = editor->pBuffer->pFirst, *pNext = NULL;
3058 ME_Style *s, *cursor2;
3059 int i;
3061 ME_ClearTempStyle(editor);
3062 ME_EmptyUndoStack(editor);
3063 editor->pBuffer->pFirst = NULL;
3064 while(p)
3066 pNext = p->next;
3067 if (p->type == diParagraph)
3068 para_destroy( editor, &p->member.para );
3069 else
3070 ME_DestroyDisplayItem(p);
3071 p = pNext;
3074 LIST_FOR_EACH_ENTRY_SAFE( s, cursor2, &editor->style_list, ME_Style, entry )
3075 ME_DestroyStyle( s );
3077 ME_ReleaseStyle(editor->pBuffer->pDefaultStyle);
3078 for (i=0; i<HFONT_CACHE_SIZE; i++)
3080 if (editor->pFontCache[i].hFont)
3081 DeleteObject(editor->pFontCache[i].hFont);
3083 if(editor->lpOleCallback)
3084 IRichEditOleCallback_Release(editor->lpOleCallback);
3086 OleUninitialize();
3088 heap_free(editor->pBuffer);
3089 heap_free(editor->pCursors);
3090 heap_free(editor);
3093 static inline int get_default_line_height( ME_TextEditor *editor )
3095 int height = 0;
3097 if (editor->pBuffer && editor->pBuffer->pDefaultStyle)
3098 height = editor->pBuffer->pDefaultStyle->tm.tmHeight;
3099 if (height <= 0) height = 24;
3101 return height;
3104 static inline int calc_wheel_change( int *remain, int amount_per_click )
3106 int change = amount_per_click * (float)*remain / WHEEL_DELTA;
3107 *remain -= WHEEL_DELTA * change / amount_per_click;
3108 return change;
3111 void link_notify(ME_TextEditor *editor, UINT msg, WPARAM wParam, LPARAM lParam)
3113 int x,y;
3114 ME_Cursor cursor; /* The start of the clicked text. */
3115 ME_Run *run;
3116 ENLINK info;
3118 x = (short)LOWORD(lParam);
3119 y = (short)HIWORD(lParam);
3120 if (!cursor_from_coords( editor, x, y, &cursor )) return;
3122 if (is_link( cursor.run ))
3123 { /* The clicked run has CFE_LINK set */
3124 info.nmhdr.hwndFrom = NULL;
3125 info.nmhdr.idFrom = 0;
3126 info.nmhdr.code = EN_LINK;
3127 info.msg = msg;
3128 info.wParam = wParam;
3129 info.lParam = lParam;
3130 cursor.nOffset = 0;
3132 /* find the first contiguous run with CFE_LINK set */
3133 info.chrg.cpMin = ME_GetCursorOfs(&cursor);
3134 run = cursor.run;
3135 while ((run = run_prev( run )) && is_link( run ))
3136 info.chrg.cpMin -= run->len;
3138 /* find the last contiguous run with CFE_LINK set */
3139 info.chrg.cpMax = ME_GetCursorOfs(&cursor) + cursor.run->len;
3140 run = cursor.run;
3141 while ((run = run_next( run )) && is_link( run ))
3142 info.chrg.cpMax += run->len;
3144 ITextHost_TxNotify(editor->texthost, info.nmhdr.code, &info);
3148 void ME_ReplaceSel(ME_TextEditor *editor, BOOL can_undo, const WCHAR *str, int len)
3150 LONG from, to;
3151 int nStartCursor;
3152 ME_Style *style;
3154 nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3155 style = ME_GetSelectionInsertStyle(editor);
3156 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to-from, FALSE);
3157 ME_InsertTextFromCursor(editor, 0, str, len, style);
3158 ME_ReleaseStyle(style);
3159 /* drop temporary style if line end */
3161 * FIXME question: does abc\n mean: put abc,
3162 * clear temp style, put \n? (would require a change)
3164 if (len>0 && str[len-1] == '\n')
3165 ME_ClearTempStyle(editor);
3166 ME_CommitUndo(editor);
3167 ME_UpdateSelectionLinkAttribute(editor);
3168 if (!can_undo)
3169 ME_EmptyUndoStack(editor);
3170 ME_UpdateRepaint(editor, FALSE);
3173 static void ME_SetText(ME_TextEditor *editor, void *text, BOOL unicode)
3175 LONG codepage = unicode ? CP_UNICODE : CP_ACP;
3176 int textLen;
3178 LPWSTR wszText = ME_ToUnicode(codepage, text, &textLen);
3179 ME_InsertTextFromCursor(editor, 0, wszText, textLen, editor->pBuffer->pDefaultStyle);
3180 ME_EndToUnicode(codepage, wszText);
3183 static LRESULT handle_EM_SETCHARFORMAT( ME_TextEditor *editor, WPARAM flags, const CHARFORMAT2W *fmt_in )
3185 CHARFORMAT2W fmt;
3186 BOOL changed = TRUE;
3187 ME_Cursor start, end;
3189 if (!cfany_to_cf2w( &fmt, fmt_in )) return 0;
3191 if (flags & SCF_ALL)
3193 if (editor->mode & TM_PLAINTEXT)
3195 ME_SetDefaultCharFormat( editor, &fmt );
3197 else
3199 ME_SetCursorToStart( editor, &start );
3200 ME_SetCharFormat( editor, &start, NULL, &fmt );
3201 editor->nModifyStep = 1;
3204 else if (flags & SCF_SELECTION)
3206 if (editor->mode & TM_PLAINTEXT) return 0;
3207 if (flags & SCF_WORD)
3209 end = editor->pCursors[0];
3210 ME_MoveCursorWords( editor, &end, +1 );
3211 start = end;
3212 ME_MoveCursorWords( editor, &start, -1 );
3213 ME_SetCharFormat( editor, &start, &end, &fmt );
3215 changed = ME_IsSelection( editor );
3216 ME_SetSelectionCharFormat( editor, &fmt );
3217 if (changed) editor->nModifyStep = 1;
3219 else /* SCF_DEFAULT */
3221 ME_SetDefaultCharFormat( editor, &fmt );
3224 ME_CommitUndo( editor );
3225 if (changed)
3227 ME_WrapMarkedParagraphs( editor );
3228 ME_UpdateScrollBar( editor );
3230 return 1;
3233 #define UNSUPPORTED_MSG(e) \
3234 case e: \
3235 FIXME(#e ": stub\n"); \
3236 *phresult = S_FALSE; \
3237 return 0;
3239 /* Handle messages for windowless and windowed richedit controls.
3241 * The LRESULT that is returned is a return value for window procs,
3242 * and the phresult parameter is the COM return code needed by the
3243 * text services interface. */
3244 LRESULT editor_handle_message( ME_TextEditor *editor, UINT msg, WPARAM wParam,
3245 LPARAM lParam, HRESULT* phresult )
3247 *phresult = S_OK;
3249 switch(msg) {
3251 UNSUPPORTED_MSG(EM_DISPLAYBAND)
3252 UNSUPPORTED_MSG(EM_FINDWORDBREAK)
3253 UNSUPPORTED_MSG(EM_FMTLINES)
3254 UNSUPPORTED_MSG(EM_FORMATRANGE)
3255 UNSUPPORTED_MSG(EM_GETBIDIOPTIONS)
3256 UNSUPPORTED_MSG(EM_GETEDITSTYLE)
3257 UNSUPPORTED_MSG(EM_GETIMECOMPMODE)
3258 UNSUPPORTED_MSG(EM_GETIMESTATUS)
3259 UNSUPPORTED_MSG(EM_SETIMESTATUS)
3260 UNSUPPORTED_MSG(EM_GETLANGOPTIONS)
3261 UNSUPPORTED_MSG(EM_GETREDONAME)
3262 UNSUPPORTED_MSG(EM_GETTYPOGRAPHYOPTIONS)
3263 UNSUPPORTED_MSG(EM_GETUNDONAME)
3264 UNSUPPORTED_MSG(EM_GETWORDBREAKPROCEX)
3265 UNSUPPORTED_MSG(EM_SETBIDIOPTIONS)
3266 UNSUPPORTED_MSG(EM_SETEDITSTYLE)
3267 UNSUPPORTED_MSG(EM_SETLANGOPTIONS)
3268 UNSUPPORTED_MSG(EM_SETMARGINS)
3269 UNSUPPORTED_MSG(EM_SETPALETTE)
3270 UNSUPPORTED_MSG(EM_SETTABSTOPS)
3271 UNSUPPORTED_MSG(EM_SETTYPOGRAPHYOPTIONS)
3272 UNSUPPORTED_MSG(EM_SETWORDBREAKPROCEX)
3274 /* Messages specific to Richedit controls */
3276 case EM_STREAMIN:
3277 return ME_StreamIn(editor, wParam, (EDITSTREAM*)lParam, TRUE);
3278 case EM_STREAMOUT:
3279 return ME_StreamOut(editor, wParam, (EDITSTREAM *)lParam);
3280 case EM_EMPTYUNDOBUFFER:
3281 ME_EmptyUndoStack(editor);
3282 return 0;
3283 case EM_GETSEL:
3285 /* Note: wParam/lParam can be NULL */
3286 LONG from, to;
3287 LONG *pfrom = wParam ? (LONG *)wParam : &from;
3288 LONG *pto = lParam ? (LONG *)lParam : &to;
3289 ME_GetSelectionOfs(editor, pfrom, pto);
3290 if ((*pfrom|*pto) & 0xFFFF0000)
3291 return -1;
3292 return MAKELONG(*pfrom,*pto);
3294 case EM_EXGETSEL:
3296 CHARRANGE *pRange = (CHARRANGE *)lParam;
3297 ME_GetSelectionOfs(editor, &pRange->cpMin, &pRange->cpMax);
3298 TRACE("EM_EXGETSEL = (%ld,%ld)\n", pRange->cpMin, pRange->cpMax);
3299 return 0;
3301 case EM_SETUNDOLIMIT:
3303 editor_enable_undo(editor);
3304 if ((int)wParam < 0)
3305 editor->nUndoLimit = STACK_SIZE_DEFAULT;
3306 else
3307 editor->nUndoLimit = min(wParam, STACK_SIZE_MAX);
3308 /* Setting a max stack size keeps wine from getting killed
3309 for hogging memory. Windows allocates all this memory at once, so
3310 no program would realistically set a value above our maximum. */
3311 return editor->nUndoLimit;
3313 case EM_CANUNDO:
3314 return !list_empty( &editor->undo_stack );
3315 case EM_CANREDO:
3316 return !list_empty( &editor->redo_stack );
3317 case WM_UNDO: /* FIXME: actually not the same */
3318 case EM_UNDO:
3319 return ME_Undo(editor);
3320 case EM_REDO:
3321 return ME_Redo(editor);
3322 case EM_SETFONTSIZE:
3324 CHARFORMAT2W cf;
3325 LONG tmp_size, size;
3326 BOOL is_increase = ((LONG)wParam > 0);
3328 if (editor->mode & TM_PLAINTEXT)
3329 return FALSE;
3331 cf.cbSize = sizeof(cf);
3332 cf.dwMask = CFM_SIZE;
3333 ME_GetSelectionCharFormat(editor, &cf);
3334 tmp_size = (cf.yHeight / 20) + wParam;
3336 if (tmp_size <= 1)
3337 size = 1;
3338 else if (tmp_size > 12 && tmp_size < 28 && tmp_size % 2)
3339 size = tmp_size + (is_increase ? 1 : -1);
3340 else if (tmp_size > 28 && tmp_size < 36)
3341 size = is_increase ? 36 : 28;
3342 else if (tmp_size > 36 && tmp_size < 48)
3343 size = is_increase ? 48 : 36;
3344 else if (tmp_size > 48 && tmp_size < 72)
3345 size = is_increase ? 72 : 48;
3346 else if (tmp_size > 72 && tmp_size < 80)
3347 size = is_increase ? 80 : 72;
3348 else if (tmp_size > 80 && tmp_size < 1638)
3349 size = 10 * (is_increase ? (tmp_size / 10 + 1) : (tmp_size / 10));
3350 else if (tmp_size >= 1638)
3351 size = 1638;
3352 else
3353 size = tmp_size;
3355 cf.yHeight = size * 20; /* convert twips to points */
3356 ME_SetSelectionCharFormat(editor, &cf);
3357 ME_CommitUndo(editor);
3358 ME_WrapMarkedParagraphs(editor);
3359 ME_UpdateScrollBar(editor);
3361 return TRUE;
3363 case EM_SETSEL:
3365 return set_selection( editor, wParam, lParam );
3367 case EM_SETSCROLLPOS:
3369 POINT *point = (POINT *)lParam;
3370 scroll_abs( editor, point->x, point->y, TRUE );
3371 return 0;
3373 case EM_AUTOURLDETECT:
3375 if (wParam==1 || wParam ==0)
3377 editor->AutoURLDetect_bEnable = (BOOL)wParam;
3378 return 0;
3380 return E_INVALIDARG;
3382 case EM_GETAUTOURLDETECT:
3384 return editor->AutoURLDetect_bEnable;
3386 case EM_EXSETSEL:
3388 CHARRANGE range = *(CHARRANGE *)lParam;
3390 return set_selection( editor, range.cpMin, range.cpMax );
3392 case EM_SETTEXTEX:
3394 LPWSTR wszText;
3395 SETTEXTEX *pStruct = (SETTEXTEX *)wParam;
3396 LONG from, to;
3397 int len;
3398 ME_Style *style;
3399 BOOL bRtf, bUnicode, bSelection, bUTF8;
3400 int oldModify = editor->nModifyStep;
3401 static const char utf8_bom[] = {0xef, 0xbb, 0xbf};
3403 if (!pStruct) return 0;
3405 /* If we detect ascii rtf at the start of the string,
3406 * we know it isn't unicode. */
3407 bRtf = (lParam && (!strncmp((char *)lParam, "{\\rtf", 5) ||
3408 !strncmp((char *)lParam, "{\\urtf", 6)));
3409 bUnicode = !bRtf && pStruct->codepage == CP_UNICODE;
3410 bUTF8 = (lParam && (!strncmp((char *)lParam, utf8_bom, 3)));
3412 TRACE("EM_SETTEXTEX - %s, flags %ld, cp %d\n",
3413 bUnicode ? debugstr_w((LPCWSTR)lParam) : debugstr_a((LPCSTR)lParam),
3414 pStruct->flags, pStruct->codepage);
3416 bSelection = (pStruct->flags & ST_SELECTION) != 0;
3417 if (bSelection) {
3418 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3419 style = ME_GetSelectionInsertStyle(editor);
3420 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to - from, FALSE);
3421 } else {
3422 ME_Cursor start;
3423 ME_SetCursorToStart(editor, &start);
3424 ME_InternalDeleteText(editor, &start, ME_GetTextLength(editor), FALSE);
3425 style = editor->pBuffer->pDefaultStyle;
3428 if (bRtf) {
3429 ME_StreamInRTFString(editor, bSelection, (char *)lParam);
3430 if (bSelection) {
3431 /* FIXME: The length returned doesn't include the rtf control
3432 * characters, only the actual text. */
3433 len = lParam ? strlen((char *)lParam) : 0;
3435 } else {
3436 if (bUTF8 && !bUnicode) {
3437 wszText = ME_ToUnicode(CP_UTF8, (void *)(lParam+3), &len);
3438 ME_InsertTextFromCursor(editor, 0, wszText, len, style);
3439 ME_EndToUnicode(CP_UTF8, wszText);
3440 } else {
3441 wszText = ME_ToUnicode(pStruct->codepage, (void *)lParam, &len);
3442 ME_InsertTextFromCursor(editor, 0, wszText, len, style);
3443 ME_EndToUnicode(pStruct->codepage, wszText);
3447 if (bSelection) {
3448 ME_ReleaseStyle(style);
3449 ME_UpdateSelectionLinkAttribute(editor);
3450 } else {
3451 ME_Cursor cursor;
3452 len = 1;
3453 ME_SetCursorToStart(editor, &cursor);
3454 ME_UpdateLinkAttribute(editor, &cursor, INT_MAX);
3456 ME_CommitUndo(editor);
3457 if (!(pStruct->flags & ST_KEEPUNDO))
3459 editor->nModifyStep = oldModify;
3460 ME_EmptyUndoStack(editor);
3462 ME_UpdateRepaint(editor, FALSE);
3463 return len;
3465 case EM_SELECTIONTYPE:
3466 return ME_GetSelectionType(editor);
3467 case EM_GETMODIFY:
3468 return editor->nModifyStep == 0 ? 0 : -1;
3469 case EM_SETMODIFY:
3471 if (wParam)
3472 editor->nModifyStep = 1;
3473 else
3474 editor->nModifyStep = 0;
3476 return 0;
3478 case EM_SETEVENTMASK:
3480 DWORD nOldMask = editor->nEventMask;
3482 editor->nEventMask = lParam;
3483 return nOldMask;
3485 case EM_GETEVENTMASK:
3486 return editor->nEventMask;
3487 case EM_SETCHARFORMAT:
3488 return handle_EM_SETCHARFORMAT( editor, wParam, (CHARFORMAT2W *)lParam );
3489 case EM_GETCHARFORMAT:
3491 CHARFORMAT2W tmp, *dst = (CHARFORMAT2W *)lParam;
3492 if (dst->cbSize != sizeof(CHARFORMATA) &&
3493 dst->cbSize != sizeof(CHARFORMATW) &&
3494 dst->cbSize != sizeof(CHARFORMAT2A) &&
3495 dst->cbSize != sizeof(CHARFORMAT2W))
3496 return 0;
3497 tmp.cbSize = sizeof(tmp);
3498 if (!wParam)
3499 ME_GetDefaultCharFormat(editor, &tmp);
3500 else
3501 ME_GetSelectionCharFormat(editor, &tmp);
3502 cf2w_to_cfany(dst, &tmp);
3503 return tmp.dwMask;
3505 case EM_SETPARAFORMAT:
3507 BOOL result = editor_set_selection_para_fmt( editor, (PARAFORMAT2 *)lParam );
3508 ME_WrapMarkedParagraphs(editor);
3509 ME_UpdateScrollBar(editor);
3510 ME_CommitUndo(editor);
3511 return result;
3513 case EM_GETPARAFORMAT:
3514 editor_get_selection_para_fmt( editor, (PARAFORMAT2 *)lParam );
3515 return ((PARAFORMAT2 *)lParam)->dwMask;
3516 case EM_GETFIRSTVISIBLELINE:
3518 ME_Paragraph *para = editor_first_para( editor );
3519 ME_Row *row;
3520 int y = editor->vert_si.nPos;
3521 int count = 0;
3523 while (para_next( para ))
3525 if (y < para->pt.y + para->nHeight) break;
3526 count += para->nRows;
3527 para = para_next( para );
3530 row = para_first_row( para );
3531 while (row)
3533 if (y < para->pt.y + row->pt.y + row->nHeight) break;
3534 count++;
3535 row = row_next( row );
3537 return count;
3539 case EM_HIDESELECTION:
3541 editor->bHideSelection = (wParam != 0);
3542 ME_InvalidateSelection(editor);
3543 return 0;
3545 case EM_LINESCROLL:
3547 if (!(editor->props & TXTBIT_MULTILINE))
3548 return FALSE;
3549 ME_ScrollDown( editor, lParam * get_default_line_height( editor ) );
3550 return TRUE;
3552 case WM_CLEAR:
3554 LONG from, to;
3555 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3556 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to-from, FALSE);
3557 ME_CommitUndo(editor);
3558 ME_UpdateRepaint(editor, TRUE);
3559 return 0;
3561 case EM_REPLACESEL:
3563 WCHAR *text = (WCHAR *)lParam;
3564 int len = text ? lstrlenW( text ) : 0;
3566 TRACE( "EM_REPLACESEL - %s\n", debugstr_w( text ) );
3567 ME_ReplaceSel( editor, !!wParam, text, len );
3568 return len;
3570 case EM_SCROLLCARET:
3571 editor_ensure_visible( editor, &editor->pCursors[0] );
3572 return 0;
3573 case WM_SETFONT:
3575 LOGFONTW lf;
3576 CHARFORMAT2W fmt;
3577 HDC hDC;
3578 BOOL bRepaint = LOWORD(lParam);
3580 if (!wParam)
3581 wParam = (WPARAM)GetStockObject(SYSTEM_FONT);
3583 if (!GetObjectW((HGDIOBJ)wParam, sizeof(LOGFONTW), &lf))
3584 return 0;
3586 hDC = ITextHost_TxGetDC(editor->texthost);
3587 ME_CharFormatFromLogFont(hDC, &lf, &fmt);
3588 ITextHost_TxReleaseDC(editor->texthost, hDC);
3589 if (editor->mode & TM_RICHTEXT) {
3590 ME_Cursor start;
3591 ME_SetCursorToStart(editor, &start);
3592 ME_SetCharFormat(editor, &start, NULL, &fmt);
3594 ME_SetDefaultCharFormat(editor, &fmt);
3596 ME_CommitUndo(editor);
3597 editor_mark_rewrap_all( editor );
3598 ME_WrapMarkedParagraphs(editor);
3599 ME_UpdateScrollBar(editor);
3600 if (bRepaint)
3601 ME_Repaint(editor);
3602 return 0;
3604 case WM_SETTEXT:
3606 ME_Cursor cursor;
3607 ME_SetCursorToStart(editor, &cursor);
3608 ME_InternalDeleteText(editor, &cursor, ME_GetTextLength(editor), FALSE);
3609 if (lParam)
3611 TRACE("WM_SETTEXT lParam==%Ix\n",lParam);
3612 if (!strncmp((char *)lParam, "{\\rtf", 5) ||
3613 !strncmp((char *)lParam, "{\\urtf", 6))
3615 /* Undocumented: WM_SETTEXT supports RTF text */
3616 ME_StreamInRTFString(editor, 0, (char *)lParam);
3618 else
3619 ME_SetText( editor, (void*)lParam, TRUE );
3621 else
3622 TRACE("WM_SETTEXT - NULL\n");
3623 ME_SetCursorToStart(editor, &cursor);
3624 ME_UpdateLinkAttribute(editor, &cursor, INT_MAX);
3625 set_selection_cursors(editor, 0, 0);
3626 editor->nModifyStep = 0;
3627 ME_CommitUndo(editor);
3628 ME_EmptyUndoStack(editor);
3629 ME_UpdateRepaint(editor, FALSE);
3630 return 1;
3632 case EM_CANPASTE:
3633 return paste_special( editor, 0, NULL, TRUE );
3634 case WM_PASTE:
3635 case WM_MBUTTONDOWN:
3636 wParam = 0;
3637 lParam = 0;
3638 /* fall through */
3639 case EM_PASTESPECIAL:
3640 paste_special( editor, wParam, (REPASTESPECIAL *)lParam, FALSE );
3641 return 0;
3642 case WM_CUT:
3643 case WM_COPY:
3644 copy_or_cut(editor, msg == WM_CUT);
3645 return 0;
3646 case WM_GETTEXTLENGTH:
3648 GETTEXTLENGTHEX how;
3649 how.flags = GTL_CLOSE | (editor->bEmulateVersion10 ? 0 : GTL_USECRLF) | GTL_NUMCHARS;
3650 how.codepage = CP_UNICODE;
3651 return ME_GetTextLengthEx(editor, &how);
3653 case EM_GETTEXTLENGTHEX:
3654 return ME_GetTextLengthEx(editor, (GETTEXTLENGTHEX *)wParam);
3655 case WM_GETTEXT:
3657 GETTEXTEX ex;
3658 ex.cb = wParam * sizeof(WCHAR);
3659 ex.flags = GT_USECRLF;
3660 ex.codepage = CP_UNICODE;
3661 ex.lpDefaultChar = NULL;
3662 ex.lpUsedDefChar = NULL;
3663 return ME_GetTextEx(editor, &ex, lParam);
3665 case EM_GETTEXTEX:
3666 return ME_GetTextEx(editor, (GETTEXTEX*)wParam, lParam);
3667 case EM_GETSELTEXT:
3669 LONG nFrom, nTo;
3670 int nStartCur = ME_GetSelectionOfs(editor, &nFrom, &nTo);
3671 ME_Cursor *from = &editor->pCursors[nStartCur];
3672 return get_text_range( editor, (WCHAR *)lParam, from, nTo - nFrom );
3674 case EM_GETSCROLLPOS:
3676 POINT *point = (POINT *)lParam;
3677 point->x = editor->horz_si.nPos;
3678 point->y = editor->vert_si.nPos;
3679 /* 16-bit scaled value is returned as stored in scrollinfo */
3680 if (editor->horz_si.nMax > 0xffff)
3681 point->x = MulDiv(point->x, 0xffff, editor->horz_si.nMax);
3682 if (editor->vert_si.nMax > 0xffff)
3683 point->y = MulDiv(point->y, 0xffff, editor->vert_si.nMax);
3684 return 1;
3686 case EM_GETTEXTRANGE:
3688 TEXTRANGEW *rng = (TEXTRANGEW *)lParam;
3689 ME_Cursor start;
3690 int nStart = rng->chrg.cpMin;
3691 int nEnd = rng->chrg.cpMax;
3692 int textlength = ME_GetTextLength(editor);
3694 TRACE( "EM_GETTEXTRANGE min = %ld max = %ld textlength = %d\n", rng->chrg.cpMin, rng->chrg.cpMax, textlength );
3695 if (nStart < 0) return 0;
3696 if ((nStart == 0 && nEnd == -1) || nEnd > textlength)
3697 nEnd = textlength;
3698 if (nStart >= nEnd) return 0;
3700 cursor_from_char_ofs( editor, nStart, &start );
3701 return get_text_range( editor, rng->lpstrText, &start, nEnd - nStart );
3703 case EM_GETLINE:
3705 ME_Row *row;
3706 ME_Run *run;
3707 const unsigned int nMaxChars = *(WORD *) lParam;
3708 unsigned int nCharsLeft = nMaxChars;
3709 char *dest = (char *) lParam;
3710 ME_Cursor start, end;
3712 TRACE( "EM_GETLINE: row=%d, nMaxChars=%d\n", (int)wParam, nMaxChars );
3714 row = row_from_row_number( editor, wParam );
3715 if (row == NULL) return 0;
3717 row_first_cursor( row, &start );
3718 row_end_cursor( row, &end, TRUE );
3719 run = start.run;
3720 while (nCharsLeft)
3722 WCHAR *str;
3723 unsigned int nCopy;
3724 int ofs = (run == start.run) ? start.nOffset : 0;
3725 int len = (run == end.run) ? end.nOffset : run->len;
3727 str = get_text( run, ofs );
3728 nCopy = min( nCharsLeft, len );
3730 memcpy(dest, str, nCopy * sizeof(WCHAR));
3731 dest += nCopy * sizeof(WCHAR);
3732 nCharsLeft -= nCopy;
3733 if (run == end.run) break;
3734 run = row_next_run( row, run );
3737 /* append line termination, space allowing */
3738 if (nCharsLeft > 0) *((WCHAR *)dest) = '\0';
3740 TRACE("EM_GETLINE: got %u characters\n", nMaxChars - nCharsLeft);
3741 return nMaxChars - nCharsLeft;
3743 case EM_GETLINECOUNT:
3745 int count = editor->total_rows;
3746 ME_Run *prev_run, *last_run;
3748 last_run = para_end_run( para_prev( editor_end_para( editor ) ) );
3749 prev_run = run_prev_all_paras( last_run );
3751 if (editor->bEmulateVersion10 && prev_run && last_run->nCharOfs == 0 &&
3752 prev_run->len == 1 && *get_text( prev_run, 0 ) == '\r')
3754 /* In 1.0 emulation, the last solitary \r at the very end of the text
3755 (if one exists) is NOT a line break.
3756 FIXME: this is an ugly hack. This should have a more regular model. */
3757 count--;
3760 count = max(1, count);
3761 TRACE("EM_GETLINECOUNT: count==%d\n", count);
3762 return count;
3764 case EM_LINEFROMCHAR:
3766 if (wParam == -1) wParam = ME_GetCursorOfs( editor->pCursors + 1 );
3767 return row_number_from_char_ofs( editor, wParam );
3769 case EM_EXLINEFROMCHAR:
3771 if (lParam == -1) lParam = ME_GetCursorOfs( editor->pCursors + 1 );
3772 return row_number_from_char_ofs( editor, lParam );
3774 case EM_LINEINDEX:
3776 ME_Row *row;
3777 ME_Cursor cursor;
3778 int ofs;
3780 if (wParam == -1) row = row_from_cursor( editor->pCursors );
3781 else row = row_from_row_number( editor, wParam );
3782 if (!row) return -1;
3784 row_first_cursor( row, &cursor );
3785 ofs = ME_GetCursorOfs( &cursor );
3786 TRACE( "EM_LINEINDEX: nCharOfs==%d\n", ofs );
3787 return ofs;
3789 case EM_LINELENGTH:
3791 ME_Row *row;
3792 int start_ofs, end_ofs;
3793 ME_Cursor cursor;
3795 if (wParam > ME_GetTextLength(editor))
3796 return 0;
3797 if (wParam == -1)
3799 FIXME("EM_LINELENGTH: returning number of unselected characters on lines with selection unsupported.\n");
3800 return 0;
3802 cursor_from_char_ofs( editor, wParam, &cursor );
3803 row = row_from_cursor( &cursor );
3804 row_first_cursor( row, &cursor );
3805 start_ofs = ME_GetCursorOfs( &cursor );
3806 row_end_cursor( row, &cursor, FALSE );
3807 end_ofs = ME_GetCursorOfs( &cursor );
3808 TRACE( "EM_LINELENGTH(%Id)==%d\n", wParam, end_ofs - start_ofs );
3809 return end_ofs - start_ofs;
3811 case EM_EXLIMITTEXT:
3813 if ((int)lParam < 0)
3814 return 0;
3815 if (lParam == 0)
3816 editor->nTextLimit = 65536;
3817 else
3818 editor->nTextLimit = (int) lParam;
3819 return 0;
3821 case EM_LIMITTEXT:
3823 if (wParam == 0)
3824 editor->nTextLimit = 65536;
3825 else
3826 editor->nTextLimit = (int) wParam;
3827 return 0;
3829 case EM_GETLIMITTEXT:
3831 return editor->nTextLimit;
3833 case EM_FINDTEXT:
3834 case EM_FINDTEXTW:
3836 FINDTEXTW *ft = (FINDTEXTW *)lParam;
3837 return ME_FindText(editor, wParam, &ft->chrg, ft->lpstrText, NULL);
3839 case EM_FINDTEXTEX:
3840 case EM_FINDTEXTEXW:
3842 FINDTEXTEXW *ex = (FINDTEXTEXW *)lParam;
3843 return ME_FindText(editor, wParam, &ex->chrg, ex->lpstrText, &ex->chrgText);
3845 case EM_GETZOOM:
3846 if (!wParam || !lParam)
3847 return FALSE;
3848 *(int *)wParam = editor->nZoomNumerator;
3849 *(int *)lParam = editor->nZoomDenominator;
3850 return TRUE;
3851 case EM_SETZOOM:
3852 return ME_SetZoom(editor, wParam, lParam);
3853 case EM_CHARFROMPOS:
3855 ME_Cursor cursor;
3856 POINTL *pt = (POINTL *)lParam;
3858 cursor_from_coords(editor, pt->x, pt->y, &cursor);
3859 return ME_GetCursorOfs(&cursor);
3861 case EM_POSFROMCHAR:
3863 ME_Cursor cursor;
3864 int nCharOfs, nLength;
3865 POINTL pt = {0,0};
3867 nCharOfs = wParam;
3868 /* detect which API version we're dealing with */
3869 if (wParam >= 0x40000)
3870 nCharOfs = lParam;
3871 nLength = ME_GetTextLength(editor);
3872 nCharOfs = min(nCharOfs, nLength);
3873 nCharOfs = max(nCharOfs, 0);
3875 cursor_from_char_ofs( editor, nCharOfs, &cursor );
3876 pt.y = cursor.run->pt.y;
3877 pt.x = cursor.run->pt.x +
3878 ME_PointFromChar( editor, cursor.run, cursor.nOffset, TRUE );
3879 pt.y += cursor.para->pt.y + editor->rcFormat.top;
3880 pt.x += editor->rcFormat.left;
3882 pt.x -= editor->horz_si.nPos;
3883 pt.y -= editor->vert_si.nPos;
3885 if (wParam >= 0x40000) *(POINTL *)wParam = pt;
3887 return (wParam >= 0x40000) ? 0 : MAKELONG( pt.x, pt.y );
3889 case WM_LBUTTONDBLCLK:
3890 case WM_LBUTTONDOWN:
3892 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
3893 ITextHost_TxSetFocus(editor->texthost);
3894 ME_LButtonDown(editor, (short)LOWORD(lParam), (short)HIWORD(lParam),
3895 ME_CalculateClickCount(editor, msg, wParam, lParam));
3896 ITextHost_TxSetCapture(editor->texthost, TRUE);
3897 editor->bMouseCaptured = TRUE;
3898 link_notify( editor, msg, wParam, lParam );
3899 break;
3901 case WM_MOUSEMOVE:
3902 if (editor->bMouseCaptured)
3903 ME_MouseMove(editor, (short)LOWORD(lParam), (short)HIWORD(lParam));
3904 else
3905 link_notify( editor, msg, wParam, lParam );
3906 break;
3907 case WM_LBUTTONUP:
3908 if (editor->bMouseCaptured) {
3909 ITextHost_TxSetCapture(editor->texthost, FALSE);
3910 editor->bMouseCaptured = FALSE;
3912 if (editor->nSelectionType == stDocument)
3913 editor->nSelectionType = stPosition;
3914 else
3916 link_notify( editor, msg, wParam, lParam );
3918 break;
3919 case WM_RBUTTONUP:
3920 case WM_RBUTTONDOWN:
3921 case WM_RBUTTONDBLCLK:
3922 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
3923 link_notify( editor, msg, wParam, lParam );
3924 goto do_default;
3925 case WM_CONTEXTMENU:
3926 if (!ME_ShowContextMenu(editor, (short)LOWORD(lParam), (short)HIWORD(lParam)))
3927 goto do_default;
3928 break;
3929 case WM_SETFOCUS:
3930 editor->bHaveFocus = TRUE;
3931 create_caret(editor);
3932 update_caret(editor);
3933 ITextHost_TxNotify( editor->texthost, EN_SETFOCUS, NULL );
3934 if (!editor->bHideSelection && (editor->props & TXTBIT_HIDESELECTION))
3935 ME_InvalidateSelection( editor );
3936 return 0;
3937 case WM_KILLFOCUS:
3938 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
3939 editor->bHaveFocus = FALSE;
3940 editor->wheel_remain = 0;
3941 hide_caret(editor);
3942 DestroyCaret();
3943 ITextHost_TxNotify( editor->texthost, EN_KILLFOCUS, NULL );
3944 if (!editor->bHideSelection && (editor->props & TXTBIT_HIDESELECTION))
3945 ME_InvalidateSelection( editor );
3946 return 0;
3947 case WM_COMMAND:
3948 TRACE("editor wnd command = %d\n", LOWORD(wParam));
3949 return 0;
3950 case WM_KEYDOWN:
3951 if (ME_KeyDown(editor, LOWORD(wParam)))
3952 return 0;
3953 goto do_default;
3954 case WM_CHAR:
3955 return handle_wm_char( editor, wParam, lParam );
3956 case WM_UNICHAR:
3957 if (wParam == UNICODE_NOCHAR) return TRUE;
3958 if (wParam <= 0x000fffff)
3960 if (wParam > 0xffff) /* convert to surrogates */
3962 wParam -= 0x10000;
3963 handle_wm_char( editor, (wParam >> 10) + 0xd800, 0 );
3964 handle_wm_char( editor, (wParam & 0x03ff) + 0xdc00, 0 );
3966 else
3967 handle_wm_char( editor, wParam, 0 );
3969 return 0;
3970 case EM_STOPGROUPTYPING:
3971 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
3972 return 0;
3973 case WM_HSCROLL:
3975 const int scrollUnit = 7;
3977 switch(LOWORD(wParam))
3979 case SB_LEFT:
3980 scroll_abs( editor, 0, 0, TRUE );
3981 break;
3982 case SB_RIGHT:
3983 scroll_abs( editor, editor->horz_si.nMax - (int)editor->horz_si.nPage,
3984 editor->vert_si.nMax - (int)editor->vert_si.nPage, TRUE );
3985 break;
3986 case SB_LINELEFT:
3987 ME_ScrollLeft(editor, scrollUnit);
3988 break;
3989 case SB_LINERIGHT:
3990 ME_ScrollRight(editor, scrollUnit);
3991 break;
3992 case SB_PAGELEFT:
3993 ME_ScrollLeft(editor, editor->sizeWindow.cx);
3994 break;
3995 case SB_PAGERIGHT:
3996 ME_ScrollRight(editor, editor->sizeWindow.cx);
3997 break;
3998 case SB_THUMBTRACK:
3999 case SB_THUMBPOSITION:
4001 int pos = HIWORD(wParam);
4002 if (editor->horz_si.nMax > 0xffff)
4003 pos = MulDiv(pos, editor->horz_si.nMax, 0xffff);
4004 scroll_h_abs( editor, pos, FALSE );
4005 break;
4008 break;
4010 case EM_SCROLL: /* fall through */
4011 case WM_VSCROLL:
4013 int origNPos;
4014 int lineHeight = get_default_line_height( editor );
4016 origNPos = editor->vert_si.nPos;
4018 switch(LOWORD(wParam))
4020 case SB_TOP:
4021 scroll_abs( editor, 0, 0, TRUE );
4022 break;
4023 case SB_BOTTOM:
4024 scroll_abs( editor, editor->horz_si.nMax - (int)editor->horz_si.nPage,
4025 editor->vert_si.nMax - (int)editor->vert_si.nPage, TRUE );
4026 break;
4027 case SB_LINEUP:
4028 ME_ScrollUp(editor,lineHeight);
4029 break;
4030 case SB_LINEDOWN:
4031 ME_ScrollDown(editor,lineHeight);
4032 break;
4033 case SB_PAGEUP:
4034 ME_ScrollUp(editor,editor->sizeWindow.cy);
4035 break;
4036 case SB_PAGEDOWN:
4037 ME_ScrollDown(editor,editor->sizeWindow.cy);
4038 break;
4039 case SB_THUMBTRACK:
4040 case SB_THUMBPOSITION:
4042 int pos = HIWORD(wParam);
4043 if (editor->vert_si.nMax > 0xffff)
4044 pos = MulDiv(pos, editor->vert_si.nMax, 0xffff);
4045 scroll_v_abs( editor, pos, FALSE );
4046 break;
4049 if (msg == EM_SCROLL)
4050 return 0x00010000 | (((editor->vert_si.nPos - origNPos)/lineHeight) & 0xffff);
4051 break;
4053 case WM_MOUSEWHEEL:
4055 int delta = GET_WHEEL_DELTA_WPARAM( wParam );
4056 BOOL ctrl_is_down = GetKeyState( VK_CONTROL ) & 0x8000;
4058 /* if scrolling changes direction, ignore left overs */
4059 if ((delta < 0 && editor->wheel_remain < 0) ||
4060 (delta > 0 && editor->wheel_remain > 0))
4061 editor->wheel_remain += delta;
4062 else
4063 editor->wheel_remain = delta;
4065 if (editor->wheel_remain)
4067 if (ctrl_is_down) {
4068 int numerator;
4069 if (!editor->nZoomNumerator || !editor->nZoomDenominator)
4071 numerator = 100;
4072 } else {
4073 numerator = editor->nZoomNumerator * 100 / editor->nZoomDenominator;
4075 numerator += calc_wheel_change( &editor->wheel_remain, 10 );
4076 if (numerator >= 10 && numerator <= 500)
4077 ME_SetZoom(editor, numerator, 100);
4078 } else {
4079 UINT max_lines = 3;
4080 int lines = 0;
4082 SystemParametersInfoW( SPI_GETWHEELSCROLLLINES, 0, &max_lines, 0 );
4083 if (max_lines)
4084 lines = calc_wheel_change( &editor->wheel_remain, (int)max_lines );
4085 if (lines)
4086 ME_ScrollDown( editor, -lines * get_default_line_height( editor ) );
4089 break;
4091 case EM_REQUESTRESIZE:
4092 ME_SendRequestResize(editor, TRUE);
4093 return 0;
4094 /* IME messages to make richedit controls IME aware */
4095 case WM_IME_SETCONTEXT:
4096 case WM_IME_CONTROL:
4097 case WM_IME_SELECT:
4098 case WM_IME_COMPOSITIONFULL:
4099 return 0;
4100 case WM_IME_STARTCOMPOSITION:
4102 editor->imeStartIndex=ME_GetCursorOfs(&editor->pCursors[0]);
4103 ME_DeleteSelection(editor);
4104 ME_CommitUndo(editor);
4105 ME_UpdateRepaint(editor, FALSE);
4106 return 0;
4108 case WM_IME_COMPOSITION:
4110 HIMC hIMC;
4112 ME_Style *style = style_get_insert_style( editor, editor->pCursors );
4113 hIMC = ITextHost_TxImmGetContext(editor->texthost);
4114 ME_DeleteSelection(editor);
4115 ME_SaveTempStyle(editor, style);
4116 if (lParam & (GCS_RESULTSTR|GCS_COMPSTR))
4118 LPWSTR lpCompStr = NULL;
4119 DWORD dwBufLen;
4120 DWORD dwIndex = lParam & GCS_RESULTSTR;
4121 if (!dwIndex)
4122 dwIndex = GCS_COMPSTR;
4124 dwBufLen = ImmGetCompositionStringW(hIMC, dwIndex, NULL, 0);
4125 lpCompStr = HeapAlloc(GetProcessHeap(),0,dwBufLen + sizeof(WCHAR));
4126 ImmGetCompositionStringW(hIMC, dwIndex, lpCompStr, dwBufLen);
4127 lpCompStr[dwBufLen/sizeof(WCHAR)] = 0;
4128 ME_InsertTextFromCursor(editor,0,lpCompStr,dwBufLen/sizeof(WCHAR),style);
4129 HeapFree(GetProcessHeap(), 0, lpCompStr);
4131 if (dwIndex == GCS_COMPSTR)
4132 set_selection_cursors(editor,editor->imeStartIndex,
4133 editor->imeStartIndex + dwBufLen/sizeof(WCHAR));
4135 ME_ReleaseStyle(style);
4136 ME_CommitUndo(editor);
4137 ME_UpdateRepaint(editor, FALSE);
4138 return 0;
4140 case WM_IME_ENDCOMPOSITION:
4142 ME_DeleteSelection(editor);
4143 editor->imeStartIndex=-1;
4144 return 0;
4146 case EM_GETOLEINTERFACE:
4147 IRichEditOle_AddRef( editor->richole );
4148 *(IRichEditOle **)lParam = editor->richole;
4149 return 1;
4151 case EM_SETOLECALLBACK:
4152 if(editor->lpOleCallback)
4153 IRichEditOleCallback_Release(editor->lpOleCallback);
4154 editor->lpOleCallback = (IRichEditOleCallback*)lParam;
4155 if(editor->lpOleCallback)
4156 IRichEditOleCallback_AddRef(editor->lpOleCallback);
4157 return TRUE;
4158 case EM_GETWORDBREAKPROC:
4159 return (LRESULT)editor->pfnWordBreak;
4160 case EM_SETWORDBREAKPROC:
4162 EDITWORDBREAKPROCW pfnOld = editor->pfnWordBreak;
4164 editor->pfnWordBreak = (EDITWORDBREAKPROCW)lParam;
4165 return (LRESULT)pfnOld;
4167 case EM_GETTEXTMODE:
4168 return editor->mode;
4169 case EM_SETTEXTMODE:
4171 int mask = 0;
4172 int changes = 0;
4174 if (ME_GetTextLength(editor) ||
4175 !list_empty( &editor->undo_stack ) || !list_empty( &editor->redo_stack ))
4176 return E_UNEXPECTED;
4178 /* Check for mutually exclusive flags in adjacent bits of wParam */
4179 if ((wParam & (TM_RICHTEXT | TM_MULTILEVELUNDO | TM_MULTICODEPAGE)) &
4180 (wParam & (TM_PLAINTEXT | TM_SINGLELEVELUNDO | TM_SINGLECODEPAGE)) << 1)
4181 return E_INVALIDARG;
4183 if (wParam & (TM_RICHTEXT | TM_PLAINTEXT))
4185 mask |= TM_RICHTEXT | TM_PLAINTEXT;
4186 changes |= wParam & (TM_RICHTEXT | TM_PLAINTEXT);
4187 if (wParam & TM_PLAINTEXT) {
4188 /* Clear selection since it should be possible to select the
4189 * end of text run for rich text */
4190 ME_InvalidateSelection(editor);
4191 ME_SetCursorToStart(editor, &editor->pCursors[0]);
4192 editor->pCursors[1] = editor->pCursors[0];
4193 /* plain text can only have the default style. */
4194 ME_ClearTempStyle(editor);
4195 ME_AddRefStyle(editor->pBuffer->pDefaultStyle);
4196 ME_ReleaseStyle( editor->pCursors[0].run->style );
4197 editor->pCursors[0].run->style = editor->pBuffer->pDefaultStyle;
4200 /* FIXME: Currently no support for undo level and code page options */
4201 editor->mode = (editor->mode & ~mask) | changes;
4202 return 0;
4204 case EM_SETTARGETDEVICE:
4205 if (wParam == 0)
4207 BOOL new = (lParam == 0 && (editor->props & TXTBIT_MULTILINE));
4208 if (editor->nAvailWidth || editor->bWordWrap != new)
4210 editor->bWordWrap = new;
4211 editor->nAvailWidth = 0; /* wrap to client area */
4212 ME_RewrapRepaint(editor);
4214 } else {
4215 int width = max(0, lParam);
4216 if ((editor->props & TXTBIT_MULTILINE) &&
4217 (!editor->bWordWrap || editor->nAvailWidth != width))
4219 editor->nAvailWidth = width;
4220 editor->bWordWrap = TRUE;
4221 ME_RewrapRepaint(editor);
4223 FIXME("EM_SETTARGETDEVICE doesn't use non-NULL target devices\n");
4225 return TRUE;
4226 default:
4227 do_default:
4228 *phresult = S_FALSE;
4229 break;
4231 return 0L;
4234 /* Fill buffer with srcChars unicode characters from the start cursor.
4236 * buffer: destination buffer
4237 * buflen: length of buffer in characters excluding the NULL terminator.
4238 * start: start of editor text to copy into buffer.
4239 * srcChars: Number of characters to use from the editor text.
4240 * bCRLF: if true, replaces all end of lines with \r\n pairs.
4242 * returns the number of characters written excluding the NULL terminator.
4244 * The written text is always NULL terminated.
4246 int ME_GetTextW(ME_TextEditor *editor, WCHAR *buffer, int buflen,
4247 const ME_Cursor *start, int srcChars, BOOL bCRLF,
4248 BOOL bEOP)
4250 ME_Run *run, *next_run;
4251 const WCHAR *pStart = buffer;
4252 const WCHAR *str;
4253 int nLen;
4255 /* bCRLF flag is only honored in 2.0 and up. 1.0 must always return text verbatim */
4256 if (editor->bEmulateVersion10) bCRLF = FALSE;
4258 run = start->run;
4259 next_run = run_next_all_paras( run );
4261 nLen = run->len - start->nOffset;
4262 str = get_text( run, start->nOffset );
4264 while (srcChars && buflen && next_run)
4266 if (bCRLF && run->nFlags & MERF_ENDPARA && ~run->nFlags & MERF_ENDCELL)
4268 if (buflen == 1) break;
4269 /* FIXME: native fails to reduce srcChars here for WM_GETTEXT or
4270 * EM_GETTEXTEX, however, this is done for copying text which
4271 * also uses this function. */
4272 srcChars -= min(nLen, srcChars);
4273 nLen = 2;
4274 str = L"\r\n";
4276 else
4278 nLen = min(nLen, srcChars);
4279 srcChars -= nLen;
4282 nLen = min(nLen, buflen);
4283 buflen -= nLen;
4285 CopyMemory(buffer, str, sizeof(WCHAR) * nLen);
4287 buffer += nLen;
4289 run = next_run;
4290 next_run = run_next_all_paras( run );
4292 nLen = run->len;
4293 str = get_text( run, 0 );
4295 /* append '\r' to the last paragraph. */
4296 if (run == para_end_run( para_prev( editor_end_para( editor ) ) ) && bEOP)
4298 *buffer = '\r';
4299 buffer ++;
4301 *buffer = 0;
4302 return buffer - pStart;
4305 static int __cdecl wchar_comp( const void *key, const void *elem )
4307 return *(const WCHAR *)key - *(const WCHAR *)elem;
4310 /* neutral characters end the url if the next non-neutral character is a space character,
4311 otherwise they are included in the url. */
4312 static BOOL isurlneutral( WCHAR c )
4314 /* NB this list is sorted */
4315 static const WCHAR neutral_chars[] = L"!\"'(),-.:;<>?[]{}";
4317 /* Some shortcuts */
4318 if (isalnum( c )) return FALSE;
4319 if (c > L'}') return FALSE;
4321 return !!bsearch( &c, neutral_chars, ARRAY_SIZE( neutral_chars ) - 1, sizeof(c), wchar_comp );
4325 * This proc takes a selection, and scans it forward in order to select the span
4326 * of a possible URL candidate. A possible URL candidate must start with isalnum
4327 * or one of the following special characters: *|/\+%#@ and must consist entirely
4328 * of the characters allowed to start the URL, plus : (colon) which may occur
4329 * at most once, and not at either end.
4331 static BOOL ME_FindNextURLCandidate(ME_TextEditor *editor,
4332 const ME_Cursor *start,
4333 int nChars,
4334 ME_Cursor *candidate_min,
4335 ME_Cursor *candidate_max)
4337 ME_Cursor cursor = *start, neutral_end, space_end;
4338 BOOL candidateStarted = FALSE, quoted = FALSE;
4339 WCHAR c;
4341 while (nChars > 0)
4343 WCHAR *str = get_text( cursor.run, 0 );
4344 int run_len = cursor.run->len;
4346 nChars -= run_len - cursor.nOffset;
4348 /* Find start of candidate */
4349 if (!candidateStarted)
4351 while (cursor.nOffset < run_len)
4353 c = str[cursor.nOffset];
4354 if (!iswspace( c ) && !isurlneutral( c ))
4356 *candidate_min = cursor;
4357 candidateStarted = TRUE;
4358 neutral_end.para = NULL;
4359 space_end.para = NULL;
4360 cursor.nOffset++;
4361 break;
4363 quoted = (c == '<');
4364 cursor.nOffset++;
4368 /* Find end of candidate */
4369 if (candidateStarted)
4371 while (cursor.nOffset < run_len)
4373 c = str[cursor.nOffset];
4374 if (iswspace( c ))
4376 if (quoted && c != '\r')
4378 if (!space_end.para)
4380 if (neutral_end.para)
4381 space_end = neutral_end;
4382 else
4383 space_end = cursor;
4386 else
4387 goto done;
4389 else if (isurlneutral( c ))
4391 if (quoted && c == '>')
4393 neutral_end.para = NULL;
4394 space_end.para = NULL;
4395 goto done;
4397 if (!neutral_end.para)
4398 neutral_end = cursor;
4400 else
4401 neutral_end.para = NULL;
4403 cursor.nOffset++;
4407 cursor.nOffset = 0;
4408 if (!cursor_next_run( &cursor, TRUE ))
4409 goto done;
4412 done:
4413 if (candidateStarted)
4415 if (space_end.para)
4416 *candidate_max = space_end;
4417 else if (neutral_end.para)
4418 *candidate_max = neutral_end;
4419 else
4420 *candidate_max = cursor;
4421 return TRUE;
4423 *candidate_max = *candidate_min = cursor;
4424 return FALSE;
4428 * This proc evaluates the selection and returns TRUE if it can be considered an URL
4430 static BOOL ME_IsCandidateAnURL(ME_TextEditor *editor, const ME_Cursor *start, int nChars)
4432 #define MAX_PREFIX_LEN 9
4433 #define X(str) str, ARRAY_SIZE(str) - 1
4434 struct prefix_s {
4435 const WCHAR text[MAX_PREFIX_LEN];
4436 int length;
4437 }prefixes[] = {
4438 {X(L"prospero:")},
4439 {X(L"telnet:")},
4440 {X(L"gopher:")},
4441 {X(L"mailto:")},
4442 {X(L"https:")},
4443 {X(L"file:")},
4444 {X(L"news:")},
4445 {X(L"wais:")},
4446 {X(L"nntp:")},
4447 {X(L"http:")},
4448 {X(L"www.")},
4449 {X(L"ftp:")},
4451 #undef X
4452 WCHAR bufferW[MAX_PREFIX_LEN + 1];
4453 unsigned int i;
4455 ME_GetTextW(editor, bufferW, MAX_PREFIX_LEN, start, nChars, FALSE, FALSE);
4456 for (i = 0; i < ARRAY_SIZE(prefixes); i++)
4458 if (nChars < prefixes[i].length) continue;
4459 if (!memcmp(prefixes[i].text, bufferW, prefixes[i].length * sizeof(WCHAR)))
4460 return TRUE;
4462 return FALSE;
4463 #undef MAX_PREFIX_LEN
4467 * This proc walks through the indicated selection and evaluates whether each
4468 * section identified by ME_FindNextURLCandidate and in-between sections have
4469 * their proper CFE_LINK attributes set or unset. If the CFE_LINK attribute is
4470 * not what it is supposed to be, this proc sets or unsets it as appropriate.
4472 * Since this function can cause runs to be split, do not depend on the value
4473 * of the start cursor at the end of the function.
4475 * nChars may be set to INT_MAX to update to the end of the text.
4477 * Returns TRUE if at least one section was modified.
4479 static BOOL ME_UpdateLinkAttribute(ME_TextEditor *editor, ME_Cursor *start, int nChars)
4481 BOOL modified = FALSE;
4482 ME_Cursor startCur = *start;
4484 if (!editor->AutoURLDetect_bEnable) return FALSE;
4488 CHARFORMAT2W link;
4489 ME_Cursor candidateStart, candidateEnd;
4491 if (ME_FindNextURLCandidate(editor, &startCur, nChars,
4492 &candidateStart, &candidateEnd))
4494 /* Section before candidate is not an URL */
4495 int cMin = ME_GetCursorOfs(&candidateStart);
4496 int cMax = ME_GetCursorOfs(&candidateEnd);
4498 if (!ME_IsCandidateAnURL(editor, &candidateStart, cMax - cMin))
4499 candidateStart = candidateEnd;
4500 nChars -= cMax - ME_GetCursorOfs(&startCur);
4502 else
4504 /* No more candidates until end of selection */
4505 nChars = 0;
4508 if (startCur.run != candidateStart.run ||
4509 startCur.nOffset != candidateStart.nOffset)
4511 /* CFE_LINK effect should be consistently unset */
4512 link.cbSize = sizeof(link);
4513 ME_GetCharFormat(editor, &startCur, &candidateStart, &link);
4514 if (!(link.dwMask & CFM_LINK) || (link.dwEffects & CFE_LINK))
4516 /* CFE_LINK must be unset from this range */
4517 memset(&link, 0, sizeof(CHARFORMAT2W));
4518 link.cbSize = sizeof(link);
4519 link.dwMask = CFM_LINK;
4520 link.dwEffects = 0;
4521 ME_SetCharFormat(editor, &startCur, &candidateStart, &link);
4522 /* Update candidateEnd since setting character formats may split
4523 * runs, which can cause a cursor to be at an invalid offset within
4524 * a split run. */
4525 while (candidateEnd.nOffset >= candidateEnd.run->len)
4527 candidateEnd.nOffset -= candidateEnd.run->len;
4528 candidateEnd.run = run_next_all_paras( candidateEnd.run );
4530 modified = TRUE;
4533 if (candidateStart.run != candidateEnd.run ||
4534 candidateStart.nOffset != candidateEnd.nOffset)
4536 /* CFE_LINK effect should be consistently set */
4537 link.cbSize = sizeof(link);
4538 ME_GetCharFormat(editor, &candidateStart, &candidateEnd, &link);
4539 if (!(link.dwMask & CFM_LINK) || !(link.dwEffects & CFE_LINK))
4541 /* CFE_LINK must be set on this range */
4542 memset(&link, 0, sizeof(CHARFORMAT2W));
4543 link.cbSize = sizeof(link);
4544 link.dwMask = CFM_LINK;
4545 link.dwEffects = CFE_LINK;
4546 ME_SetCharFormat(editor, &candidateStart, &candidateEnd, &link);
4547 modified = TRUE;
4550 startCur = candidateEnd;
4551 } while (nChars > 0);
4552 return modified;