sapi: Add SpMMAudioOut stub.
[wine.git] / dlls / riched20 / editor.c
blob412648d9116095bdebfaffaa69312d086e659c42
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 if (format & SFF_SELECTION)
1614 ME_GetSelection(editor, &selStart, &selEnd);
1615 ME_InternalDeleteText(editor, selStart, to - from, FALSE);
1617 else
1619 set_selection_cursors(editor, 0, 0);
1620 ME_InternalDeleteText(editor, &editor->pCursors[1],
1621 ME_GetTextLength(editor), FALSE);
1623 from = to = 0;
1624 ME_ClearTempStyle(editor);
1625 editor_set_default_para_fmt( editor, &editor->pCursors[0].para->fmt );
1629 /* Back up undo mode to a local variable */
1630 nUndoMode = editor->nUndoMode;
1632 /* Only create an undo if SFF_SELECTION is set */
1633 if (!(format & SFF_SELECTION))
1634 editor->nUndoMode = umIgnore;
1636 inStream.editstream = stream;
1637 inStream.editstream->dwError = 0;
1638 inStream.dwSize = 0;
1639 inStream.dwUsed = 0;
1641 if (format & SF_RTF)
1643 /* Check if it's really RTF, and if it is not, use plain text */
1644 ME_StreamInFill(&inStream);
1645 if (!inStream.editstream->dwError)
1647 if ((!editor->bEmulateVersion10 && strncmp(inStream.buffer, "{\\rtf", 5) && strncmp(inStream.buffer, "{\\urtf", 6))
1648 || (editor->bEmulateVersion10 && *inStream.buffer != '{'))
1650 invalidRTF = TRUE;
1651 inStream.editstream->dwError = -16;
1656 if (!invalidRTF && !inStream.editstream->dwError)
1658 ME_Cursor start;
1659 from = ME_GetCursorOfs(&editor->pCursors[0]);
1660 if (format & SF_RTF) {
1662 /* setup the RTF parser */
1663 memset(&parser, 0, sizeof parser);
1664 RTFSetEditStream(&parser, &inStream);
1665 parser.rtfFormat = format&(SF_TEXT|SF_RTF);
1666 parser.editor = editor;
1667 parser.style = style;
1668 WriterInit(&parser);
1669 RTFInit(&parser);
1670 RTFSetReadHook(&parser, ME_RTFReadHook);
1671 RTFSetDestinationCallback(&parser, rtfShpPict, ME_RTFReadShpPictGroup);
1672 RTFSetDestinationCallback(&parser, rtfPict, ME_RTFReadPictGroup);
1673 RTFSetDestinationCallback(&parser, rtfObject, ME_RTFReadObjectGroup);
1674 RTFSetDestinationCallback(&parser, rtfParNumbering, ME_RTFReadParnumGroup);
1675 if (!parser.editor->bEmulateVersion10) /* v4.1 */
1677 RTFSetDestinationCallback(&parser, rtfNoNestTables, RTFSkipGroup);
1678 RTFSetDestinationCallback(&parser, rtfNestTableProps, RTFReadGroup);
1680 BeginFile(&parser);
1682 /* do the parsing */
1683 RTFRead(&parser);
1684 RTFFlushOutputBuffer(&parser);
1685 if (!editor->bEmulateVersion10) /* v4.1 */
1687 if (parser.tableDef && parser.tableDef->row_start &&
1688 (parser.nestingLevel > 0 || parser.canInheritInTbl))
1690 /* Delete any incomplete table row at the end of the rich text. */
1691 int nOfs, nChars;
1692 ME_Paragraph *para;
1694 parser.rtfMinor = rtfRow;
1695 /* Complete the table row before deleting it.
1696 * By doing it this way we will have the current paragraph format set
1697 * properly to reflect that is not in the complete table, and undo items
1698 * will be added for this change to the current paragraph format. */
1699 if (parser.nestingLevel > 0)
1701 while (parser.nestingLevel > 1)
1702 ME_RTFSpecialCharHook(&parser); /* Decrements nestingLevel */
1703 para = parser.tableDef->row_start;
1704 ME_RTFSpecialCharHook(&parser);
1706 else
1708 para = parser.tableDef->row_start;
1709 ME_RTFSpecialCharHook(&parser);
1710 assert( para->nFlags & MEPF_ROWEND );
1711 para = para_next( para );
1714 editor->pCursors[1].para = para;
1715 editor->pCursors[1].run = para_first_run( para );
1716 editor->pCursors[1].nOffset = 0;
1717 nOfs = ME_GetCursorOfs(&editor->pCursors[1]);
1718 nChars = ME_GetCursorOfs(&editor->pCursors[0]) - nOfs;
1719 ME_InternalDeleteText(editor, &editor->pCursors[1], nChars, TRUE);
1720 if (parser.tableDef) parser.tableDef->row_start = NULL;
1723 RTFDestroy(&parser);
1725 if (parser.stackTop > 0)
1727 while (--parser.stackTop >= 0)
1729 ME_ReleaseStyle(parser.style);
1730 parser.style = parser.stack[parser.stackTop].style;
1732 if (!inStream.editstream->dwError)
1733 inStream.editstream->dwError = HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
1736 /* Remove last line break, as mandated by tests. This is not affected by
1737 CR/LF counters, since RTF streaming presents only \para tokens, which
1738 are converted according to the standard rules: \r for 2.0, \r\n for 1.0
1740 if (stripLastCR && !(format & SFF_SELECTION)) {
1741 int newto;
1742 ME_GetSelection(editor, &selStart, &selEnd);
1743 newto = ME_GetCursorOfs(selEnd);
1744 if (newto > to + (editor->bEmulateVersion10 ? 1 : 0)) {
1745 WCHAR lastchar[3] = {'\0', '\0'};
1746 int linebreakSize = editor->bEmulateVersion10 ? 2 : 1;
1747 ME_Cursor linebreakCursor = *selEnd, lastcharCursor = *selEnd;
1748 CHARFORMAT2W cf;
1750 /* Set the final eop to the char fmt of the last char */
1751 cf.cbSize = sizeof(cf);
1752 cf.dwMask = CFM_ALL2;
1753 ME_MoveCursorChars(editor, &lastcharCursor, -1, FALSE);
1754 ME_GetCharFormat(editor, &lastcharCursor, &linebreakCursor, &cf);
1755 set_selection_cursors(editor, newto, -1);
1756 ME_SetSelectionCharFormat(editor, &cf);
1757 set_selection_cursors(editor, newto, newto);
1759 ME_MoveCursorChars(editor, &linebreakCursor, -linebreakSize, FALSE);
1760 ME_GetTextW(editor, lastchar, 2, &linebreakCursor, linebreakSize, FALSE, FALSE);
1761 if (lastchar[0] == '\r' && (lastchar[1] == '\n' || lastchar[1] == '\0')) {
1762 ME_InternalDeleteText(editor, &linebreakCursor, linebreakSize, FALSE);
1766 to = ME_GetCursorOfs(&editor->pCursors[0]);
1767 num_read = to - from;
1769 style = parser.style;
1771 else if (format & SF_TEXT)
1773 num_read = ME_StreamInText(editor, format, &inStream, style);
1774 to = ME_GetCursorOfs(&editor->pCursors[0]);
1776 else
1777 ERR("EM_STREAMIN without SF_TEXT or SF_RTF\n");
1778 /* put the cursor at the top */
1779 if (!(format & SFF_SELECTION))
1780 set_selection_cursors(editor, 0, 0);
1781 cursor_from_char_ofs( editor, from, &start );
1782 ME_UpdateLinkAttribute(editor, &start, to - from);
1785 /* Restore saved undo mode */
1786 editor->nUndoMode = nUndoMode;
1788 /* even if we didn't add an undo, we need to commit anything on the stack */
1789 ME_CommitUndo(editor);
1791 /* If SFF_SELECTION isn't set, delete any undos from before we started too */
1792 if (!(format & SFF_SELECTION))
1793 ME_EmptyUndoStack(editor);
1795 ME_ReleaseStyle(style);
1796 editor->nEventMask = nEventMask;
1797 ME_UpdateRepaint(editor, FALSE);
1798 if (!(format & SFF_SELECTION)) {
1799 ME_ClearTempStyle(editor);
1801 ME_SendSelChange(editor);
1802 ME_SendRequestResize(editor, FALSE);
1804 return num_read;
1808 typedef struct tagME_RTFStringStreamStruct
1810 char *string;
1811 int pos;
1812 int length;
1813 } ME_RTFStringStreamStruct;
1815 static DWORD CALLBACK ME_ReadFromRTFString(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
1817 ME_RTFStringStreamStruct *pStruct = (ME_RTFStringStreamStruct *)dwCookie;
1818 int count;
1820 count = min(cb, pStruct->length - pStruct->pos);
1821 memmove(lpBuff, pStruct->string + pStruct->pos, count);
1822 pStruct->pos += count;
1823 *pcb = count;
1824 return 0;
1827 static void
1828 ME_StreamInRTFString(ME_TextEditor *editor, BOOL selection, char *string)
1830 EDITSTREAM es;
1831 ME_RTFStringStreamStruct data;
1833 data.string = string;
1834 data.length = strlen(string);
1835 data.pos = 0;
1836 es.dwCookie = (DWORD_PTR)&data;
1837 es.pfnCallback = ME_ReadFromRTFString;
1838 ME_StreamIn(editor, SF_RTF | (selection ? SFF_SELECTION : 0), &es, TRUE);
1842 static int
1843 ME_FindText(ME_TextEditor *editor, DWORD flags, const CHARRANGE *chrg, const WCHAR *text, CHARRANGE *chrgText)
1845 const int nLen = lstrlenW(text);
1846 const int nTextLen = ME_GetTextLength(editor);
1847 int nMin, nMax;
1848 ME_Cursor cursor;
1849 WCHAR wLastChar = ' ';
1851 TRACE("flags==0x%08lx, chrg->cpMin==%ld, chrg->cpMax==%ld text==%s\n",
1852 flags, chrg->cpMin, chrg->cpMax, debugstr_w(text));
1854 if (flags & ~(FR_DOWN | FR_MATCHCASE | FR_WHOLEWORD))
1855 FIXME("Flags 0x%08lx not implemented\n",
1856 flags & ~(FR_DOWN | FR_MATCHCASE | FR_WHOLEWORD));
1858 nMin = chrg->cpMin;
1859 if (chrg->cpMax == -1)
1860 nMax = nTextLen;
1861 else
1862 nMax = chrg->cpMax > nTextLen ? nTextLen : chrg->cpMax;
1864 /* In 1.0 emulation, if cpMax reaches end of text, add the FR_DOWN flag */
1865 if (editor->bEmulateVersion10 && nMax == nTextLen)
1867 flags |= FR_DOWN;
1870 /* In 1.0 emulation, cpMin must always be no greater than cpMax */
1871 if (editor->bEmulateVersion10 && nMax < nMin)
1873 if (chrgText)
1875 chrgText->cpMin = -1;
1876 chrgText->cpMax = -1;
1878 return -1;
1881 /* when searching up, if cpMin < cpMax, then instead of searching
1882 * on [cpMin,cpMax], we search on [0,cpMin], otherwise, search on
1883 * [cpMax, cpMin]. The exception is when cpMax is -1, in which
1884 * case, it is always bigger than cpMin.
1886 if (!editor->bEmulateVersion10 && !(flags & FR_DOWN))
1888 int nSwap = nMax;
1890 nMax = nMin > nTextLen ? nTextLen : nMin;
1891 if (nMin < nSwap || chrg->cpMax == -1)
1892 nMin = 0;
1893 else
1894 nMin = nSwap;
1897 if (!nLen || nMin < 0 || nMax < 0 || nMax < nMin)
1899 if (chrgText)
1900 chrgText->cpMin = chrgText->cpMax = -1;
1901 return -1;
1904 if (flags & FR_DOWN) /* Forward search */
1906 /* If possible, find the character before where the search starts */
1907 if ((flags & FR_WHOLEWORD) && nMin)
1909 cursor_from_char_ofs( editor, nMin - 1, &cursor );
1910 wLastChar = *get_text( cursor.run, cursor.nOffset );
1911 ME_MoveCursorChars(editor, &cursor, 1, FALSE);
1913 else cursor_from_char_ofs( editor, nMin, &cursor );
1915 while (cursor.run && ME_GetCursorOfs(&cursor) + nLen <= nMax)
1917 ME_Run *run = cursor.run;
1918 int nCurStart = cursor.nOffset;
1919 int nMatched = 0;
1921 while (run && ME_CharCompare( *get_text( run, nCurStart + nMatched ), text[nMatched], (flags & FR_MATCHCASE)))
1923 if ((flags & FR_WHOLEWORD) && iswalnum(wLastChar))
1924 break;
1926 nMatched++;
1927 if (nMatched == nLen)
1929 ME_Run *next_run = run;
1930 int nNextStart = nCurStart;
1931 WCHAR wNextChar;
1933 /* Check to see if next character is a whitespace */
1934 if (flags & FR_WHOLEWORD)
1936 if (nCurStart + nMatched == run->len)
1938 next_run = run_next_all_paras( run );
1939 nNextStart = -nMatched;
1942 if (next_run)
1943 wNextChar = *get_text( next_run, nNextStart + nMatched );
1944 else
1945 wNextChar = ' ';
1947 if (iswalnum(wNextChar))
1948 break;
1951 cursor.nOffset += cursor.para->nCharOfs + cursor.run->nCharOfs;
1952 if (chrgText)
1954 chrgText->cpMin = cursor.nOffset;
1955 chrgText->cpMax = cursor.nOffset + nLen;
1957 TRACE("found at %d-%d\n", cursor.nOffset, cursor.nOffset + nLen);
1958 return cursor.nOffset;
1960 if (nCurStart + nMatched == run->len)
1962 run = run_next_all_paras( run );
1963 nCurStart = -nMatched;
1966 if (run)
1967 wLastChar = *get_text( run, nCurStart + nMatched );
1968 else
1969 wLastChar = ' ';
1971 cursor.nOffset++;
1972 if (cursor.nOffset == cursor.run->len)
1974 if (run_next_all_paras( cursor.run ))
1976 cursor.run = run_next_all_paras( cursor.run );
1977 cursor.para = cursor.run->para;
1978 cursor.nOffset = 0;
1980 else
1981 cursor.run = NULL;
1985 else /* Backward search */
1987 /* If possible, find the character after where the search ends */
1988 if ((flags & FR_WHOLEWORD) && nMax < nTextLen - 1)
1990 cursor_from_char_ofs( editor, nMax + 1, &cursor );
1991 wLastChar = *get_text( cursor.run, cursor.nOffset );
1992 ME_MoveCursorChars(editor, &cursor, -1, FALSE);
1994 else cursor_from_char_ofs( editor, nMax, &cursor );
1996 while (cursor.run && ME_GetCursorOfs(&cursor) - nLen >= nMin)
1998 ME_Run *run = cursor.run;
1999 ME_Paragraph *para = cursor.para;
2000 int nCurEnd = cursor.nOffset;
2001 int nMatched = 0;
2003 if (nCurEnd == 0 && run_prev_all_paras( run ))
2005 run = run_prev_all_paras( run );
2006 para = run->para;
2007 nCurEnd = run->len;
2010 while (run && ME_CharCompare( *get_text( run, nCurEnd - nMatched - 1 ),
2011 text[nLen - nMatched - 1], (flags & FR_MATCHCASE) ))
2013 if ((flags & FR_WHOLEWORD) && iswalnum(wLastChar))
2014 break;
2016 nMatched++;
2017 if (nMatched == nLen)
2019 ME_Run *prev_run = run;
2020 int nPrevEnd = nCurEnd;
2021 WCHAR wPrevChar;
2022 int nStart;
2024 /* Check to see if previous character is a whitespace */
2025 if (flags & FR_WHOLEWORD)
2027 if (nPrevEnd - nMatched == 0)
2029 prev_run = run_prev_all_paras( run );
2030 if (prev_run) nPrevEnd = prev_run->len + nMatched;
2033 if (prev_run) wPrevChar = *get_text( prev_run, nPrevEnd - nMatched - 1 );
2034 else wPrevChar = ' ';
2036 if (iswalnum(wPrevChar))
2037 break;
2040 nStart = para->nCharOfs + run->nCharOfs + nCurEnd - nMatched;
2041 if (chrgText)
2043 chrgText->cpMin = nStart;
2044 chrgText->cpMax = nStart + nLen;
2046 TRACE("found at %d-%d\n", nStart, nStart + nLen);
2047 return nStart;
2049 if (nCurEnd - nMatched == 0)
2051 if (run_prev_all_paras( run ))
2053 run = run_prev_all_paras( run );
2054 para = run->para;
2056 /* Don't care about pCurItem becoming NULL here; it's already taken
2057 * care of in the exterior loop condition */
2058 nCurEnd = run->len + nMatched;
2061 if (run)
2062 wLastChar = *get_text( run, nCurEnd - nMatched - 1 );
2063 else
2064 wLastChar = ' ';
2066 cursor.nOffset--;
2067 if (cursor.nOffset < 0)
2069 if (run_prev_all_paras( cursor.run ) )
2071 cursor.run = run_prev_all_paras( cursor.run );
2072 cursor.para = cursor.run->para;
2073 cursor.nOffset = cursor.run->len;
2075 else
2076 cursor.run = NULL;
2080 TRACE("not found\n");
2081 if (chrgText)
2082 chrgText->cpMin = chrgText->cpMax = -1;
2083 return -1;
2086 static int ME_GetTextEx(ME_TextEditor *editor, GETTEXTEX *ex, LPARAM pText)
2088 int nChars;
2089 ME_Cursor start;
2091 if (!ex->cb || !pText) return 0;
2093 if (ex->flags & ~(GT_SELECTION | GT_USECRLF))
2094 FIXME("GETTEXTEX flags 0x%08lx not supported\n", ex->flags & ~(GT_SELECTION | GT_USECRLF));
2096 if (ex->flags & GT_SELECTION)
2098 LONG from, to;
2099 int nStartCur = ME_GetSelectionOfs(editor, &from, &to);
2100 start = editor->pCursors[nStartCur];
2101 nChars = to - from;
2103 else
2105 ME_SetCursorToStart(editor, &start);
2106 nChars = INT_MAX;
2108 if (ex->codepage == CP_UNICODE)
2110 return ME_GetTextW(editor, (LPWSTR)pText, ex->cb / sizeof(WCHAR) - 1,
2111 &start, nChars, ex->flags & GT_USECRLF, FALSE);
2113 else
2115 /* potentially each char may be a CR, why calculate the exact value with O(N) when
2116 we can just take a bigger buffer? :)
2117 The above assumption still holds with CR/LF counters, since CR->CRLF expansion
2118 occurs only in richedit 2.0 mode, in which line breaks have only one CR
2120 int crlfmul = (ex->flags & GT_USECRLF) ? 2 : 1;
2121 DWORD buflen;
2122 LPWSTR buffer;
2123 LRESULT rc;
2125 buflen = min(crlfmul * nChars, ex->cb - 1);
2126 buffer = heap_alloc((buflen + 1) * sizeof(WCHAR));
2128 nChars = ME_GetTextW(editor, buffer, buflen, &start, nChars, ex->flags & GT_USECRLF, FALSE);
2129 rc = WideCharToMultiByte(ex->codepage, 0, buffer, nChars + 1,
2130 (LPSTR)pText, ex->cb, ex->lpDefaultChar, ex->lpUsedDefChar);
2131 if (rc) rc--; /* do not count 0 terminator */
2133 heap_free(buffer);
2134 return rc;
2138 static int get_text_range( ME_TextEditor *editor, WCHAR *buffer,
2139 const ME_Cursor *start, int len )
2141 if (!buffer) return 0;
2142 return ME_GetTextW( editor, buffer, INT_MAX, start, len, FALSE, FALSE );
2145 int set_selection( ME_TextEditor *editor, int to, int from )
2147 int end;
2149 TRACE("%d - %d\n", to, from );
2151 if (!editor->bHideSelection) ME_InvalidateSelection( editor );
2152 end = set_selection_cursors( editor, to, from );
2153 editor_ensure_visible( editor, &editor->pCursors[0] );
2154 if (!editor->bHideSelection) ME_InvalidateSelection( editor );
2155 update_caret( editor );
2156 ME_SendSelChange( editor );
2158 return end;
2161 typedef struct tagME_GlobalDestStruct
2163 HGLOBAL hData;
2164 int nLength;
2165 } ME_GlobalDestStruct;
2167 static DWORD CALLBACK ME_ReadFromHGLOBALUnicode(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
2169 ME_GlobalDestStruct *pData = (ME_GlobalDestStruct *)dwCookie;
2170 int i;
2171 WORD *pSrc, *pDest;
2173 cb = cb >> 1;
2174 pDest = (WORD *)lpBuff;
2175 pSrc = GlobalLock(pData->hData);
2176 for (i = 0; i<cb && pSrc[pData->nLength+i]; i++) {
2177 pDest[i] = pSrc[pData->nLength+i];
2179 pData->nLength += i;
2180 *pcb = 2*i;
2181 GlobalUnlock(pData->hData);
2182 return 0;
2185 static DWORD CALLBACK ME_ReadFromHGLOBALRTF(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
2187 ME_GlobalDestStruct *pData = (ME_GlobalDestStruct *)dwCookie;
2188 int i;
2189 BYTE *pSrc, *pDest;
2191 pDest = lpBuff;
2192 pSrc = GlobalLock(pData->hData);
2193 for (i = 0; i<cb && pSrc[pData->nLength+i]; i++) {
2194 pDest[i] = pSrc[pData->nLength+i];
2196 pData->nLength += i;
2197 *pcb = i;
2198 GlobalUnlock(pData->hData);
2199 return 0;
2202 static HRESULT paste_rtf(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2204 EDITSTREAM es;
2205 ME_GlobalDestStruct gds;
2206 HRESULT hr;
2208 gds.hData = med->u.hGlobal;
2209 gds.nLength = 0;
2210 es.dwCookie = (DWORD_PTR)&gds;
2211 es.pfnCallback = ME_ReadFromHGLOBALRTF;
2212 hr = ME_StreamIn( editor, SF_RTF | SFF_SELECTION, &es, FALSE ) == 0 ? E_FAIL : S_OK;
2213 ReleaseStgMedium( med );
2214 return hr;
2217 static HRESULT paste_text(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2219 EDITSTREAM es;
2220 ME_GlobalDestStruct gds;
2221 HRESULT hr;
2223 gds.hData = med->u.hGlobal;
2224 gds.nLength = 0;
2225 es.dwCookie = (DWORD_PTR)&gds;
2226 es.pfnCallback = ME_ReadFromHGLOBALUnicode;
2227 hr = ME_StreamIn( editor, SF_TEXT | SF_UNICODE | SFF_SELECTION, &es, FALSE ) == 0 ? E_FAIL : S_OK;
2228 ReleaseStgMedium( med );
2229 return hr;
2232 static HRESULT paste_emf(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2234 HRESULT hr;
2235 SIZEL sz = {0, 0};
2237 hr = insert_static_object( editor, med->u.hEnhMetaFile, NULL, &sz );
2238 if (SUCCEEDED(hr))
2240 ME_CommitUndo( editor );
2241 ME_UpdateRepaint( editor, FALSE );
2243 else
2244 ReleaseStgMedium( med );
2246 return hr;
2249 static struct paste_format
2251 FORMATETC fmt;
2252 HRESULT (*paste)(ME_TextEditor *, FORMATETC *, STGMEDIUM *);
2253 const WCHAR *name;
2254 } paste_formats[] =
2256 {{ -1, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, paste_rtf, L"Rich Text Format" },
2257 {{ CF_UNICODETEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, paste_text },
2258 {{ CF_ENHMETAFILE, NULL, DVASPECT_CONTENT, -1, TYMED_ENHMF }, paste_emf },
2259 {{ 0 }}
2262 static void init_paste_formats(void)
2264 struct paste_format *format;
2265 static int done;
2267 if (!done)
2269 for (format = paste_formats; format->fmt.cfFormat; format++)
2271 if (format->name)
2272 format->fmt.cfFormat = RegisterClipboardFormatW( format->name );
2274 done = 1;
2278 static BOOL paste_special(ME_TextEditor *editor, UINT cf, REPASTESPECIAL *ps, BOOL check_only)
2280 HRESULT hr;
2281 STGMEDIUM med;
2282 struct paste_format *format;
2283 IDataObject *data;
2285 /* Protect read-only edit control from modification */
2286 if (editor->props & TXTBIT_READONLY)
2288 if (!check_only) editor_beep( editor, MB_ICONERROR );
2289 return FALSE;
2292 init_paste_formats();
2294 if (ps && ps->dwAspect != DVASPECT_CONTENT)
2295 FIXME("Ignoring aspect %lx\n", ps->dwAspect);
2297 hr = OleGetClipboard( &data );
2298 if (hr != S_OK) return FALSE;
2300 if (cf == CF_TEXT) cf = CF_UNICODETEXT;
2302 hr = S_FALSE;
2303 for (format = paste_formats; format->fmt.cfFormat; format++)
2305 if (cf && cf != format->fmt.cfFormat) continue;
2306 hr = IDataObject_QueryGetData( data, &format->fmt );
2307 if (hr == S_OK)
2309 if (!check_only)
2311 hr = IDataObject_GetData( data, &format->fmt, &med );
2312 if (hr != S_OK) goto done;
2313 hr = format->paste( editor, &format->fmt, &med );
2315 break;
2319 done:
2320 IDataObject_Release( data );
2322 return hr == S_OK;
2325 static HRESULT editor_copy( ME_TextEditor *editor, ME_Cursor *start, int chars, IDataObject **data_out )
2327 IDataObject *data = NULL;
2328 HRESULT hr = S_OK;
2330 if (editor->lpOleCallback)
2332 CHARRANGE range;
2333 range.cpMin = ME_GetCursorOfs( start );
2334 range.cpMax = range.cpMin + chars;
2335 hr = IRichEditOleCallback_GetClipboardData( editor->lpOleCallback, &range, RECO_COPY, &data );
2338 if (FAILED( hr ) || !data)
2339 hr = ME_GetDataObject( editor, start, chars, &data );
2341 if (SUCCEEDED( hr ))
2343 if (data_out)
2344 *data_out = data;
2345 else
2347 hr = OleSetClipboard( data );
2348 IDataObject_Release( data );
2352 return hr;
2355 HRESULT editor_copy_or_cut( ME_TextEditor *editor, BOOL cut, ME_Cursor *start, int count,
2356 IDataObject **data_out )
2358 HRESULT hr;
2360 if (cut && (editor->props & TXTBIT_READONLY))
2362 return E_ACCESSDENIED;
2365 hr = editor_copy( editor, start, count, data_out );
2366 if (SUCCEEDED(hr) && cut)
2368 ME_InternalDeleteText( editor, start, count, FALSE );
2369 ME_CommitUndo( editor );
2370 ME_UpdateRepaint( editor, TRUE );
2372 return hr;
2375 static BOOL copy_or_cut( ME_TextEditor *editor, BOOL cut )
2377 HRESULT hr;
2378 LONG offs, count;
2379 int start_cursor = ME_GetSelectionOfs( editor, &offs, &count );
2380 ME_Cursor *sel_start = &editor->pCursors[start_cursor];
2382 if (editor->password_char) return FALSE;
2384 count -= offs;
2385 hr = editor_copy_or_cut( editor, cut, sel_start, count, NULL );
2386 if (FAILED( hr )) editor_beep( editor, MB_ICONERROR );
2388 return SUCCEEDED( hr );
2391 static void ME_UpdateSelectionLinkAttribute(ME_TextEditor *editor)
2393 ME_Paragraph *start_para, *end_para;
2394 ME_Cursor *from, *to, start;
2395 int num_chars;
2397 if (!editor->AutoURLDetect_bEnable) return;
2399 ME_GetSelection(editor, &from, &to);
2401 /* Find paragraph previous to the one that contains start cursor */
2402 start_para = from->para;
2403 if (para_prev( start_para )) start_para = para_prev( start_para );
2405 /* Find paragraph that contains end cursor */
2406 end_para = para_next( to->para );
2408 start.para = start_para;
2409 start.run = para_first_run( start_para );
2410 start.nOffset = 0;
2411 num_chars = end_para->nCharOfs - start_para->nCharOfs;
2413 ME_UpdateLinkAttribute( editor, &start, num_chars );
2416 static BOOL handle_enter(ME_TextEditor *editor)
2418 BOOL shift_is_down = GetKeyState(VK_SHIFT) & 0x8000;
2420 if (editor->props & TXTBIT_MULTILINE)
2422 ME_Cursor cursor = editor->pCursors[0];
2423 ME_Paragraph *para = cursor.para;
2424 LONG from, to;
2425 ME_Style *style, *eop_style;
2427 if (editor->props & TXTBIT_READONLY)
2429 editor_beep( editor, MB_ICONERROR );
2430 return TRUE;
2433 ME_GetSelectionOfs(editor, &from, &to);
2434 if (editor->nTextLimit > ME_GetTextLength(editor) - (to-from))
2436 if (!editor->bEmulateVersion10) /* v4.1 */
2438 if (para->nFlags & MEPF_ROWEND)
2440 /* Add a new table row after this row. */
2441 para = table_append_row( editor, para );
2442 para = para_next( para );
2443 editor->pCursors[0].para = para;
2444 editor->pCursors[0].run = para_first_run( para );
2445 editor->pCursors[0].nOffset = 0;
2446 editor->pCursors[1] = editor->pCursors[0];
2447 ME_CommitUndo(editor);
2448 ME_UpdateRepaint(editor, FALSE);
2449 return TRUE;
2451 else if (para == editor->pCursors[1].para &&
2452 cursor.nOffset + cursor.run->nCharOfs == 0 &&
2453 para_prev( para ) && para_prev( para )->nFlags & MEPF_ROWSTART &&
2454 !para_prev( para )->nCharOfs)
2456 /* Insert a newline before the table. */
2457 para = para_prev( para );
2458 para->nFlags &= ~MEPF_ROWSTART;
2459 editor->pCursors[0].para = para;
2460 editor->pCursors[0].run = para_first_run( para );
2461 editor->pCursors[1] = editor->pCursors[0];
2462 ME_InsertTextFromCursor( editor, 0, L"\r", 1, editor->pCursors[0].run->style );
2463 para = editor_first_para( editor );
2464 editor_set_default_para_fmt( editor, &para->fmt );
2465 para->nFlags = 0;
2466 para_mark_rewrap( editor, para );
2467 editor->pCursors[0].para = para;
2468 editor->pCursors[0].run = para_first_run( para );
2469 editor->pCursors[1] = editor->pCursors[0];
2470 para_next( para )->nFlags |= MEPF_ROWSTART;
2471 ME_CommitCoalescingUndo(editor);
2472 ME_UpdateRepaint(editor, FALSE);
2473 return TRUE;
2476 else /* v1.0 - 3.0 */
2478 ME_Paragraph *para = cursor.para;
2479 if (para_in_table( para ))
2481 if (cursor.run->nFlags & MERF_ENDPARA)
2483 if (from == to)
2485 ME_ContinueCoalescingTransaction(editor);
2486 para = table_append_row( editor, para );
2487 editor->pCursors[0].para = para;
2488 editor->pCursors[0].run = para_first_run( para );
2489 editor->pCursors[0].nOffset = 0;
2490 editor->pCursors[1] = editor->pCursors[0];
2491 ME_CommitCoalescingUndo(editor);
2492 ME_UpdateRepaint(editor, FALSE);
2493 return TRUE;
2496 else
2498 ME_ContinueCoalescingTransaction(editor);
2499 if (cursor.run->nCharOfs + cursor.nOffset == 0 &&
2500 para_prev( para ) && !para_in_table( para_prev( para ) ))
2502 /* Insert newline before table */
2503 cursor.run = para_end_run( para_prev( para ) );
2504 if (cursor.run)
2506 editor->pCursors[0].run = cursor.run;
2507 editor->pCursors[0].para = para_prev( para );
2509 editor->pCursors[0].nOffset = 0;
2510 editor->pCursors[1] = editor->pCursors[0];
2511 ME_InsertTextFromCursor( editor, 0, L"\r", 1, editor->pCursors[0].run->style );
2513 else
2515 editor->pCursors[1] = editor->pCursors[0];
2516 para = table_append_row( editor, para );
2517 editor->pCursors[0].para = para;
2518 editor->pCursors[0].run = para_first_run( para );
2519 editor->pCursors[0].nOffset = 0;
2520 editor->pCursors[1] = editor->pCursors[0];
2522 ME_CommitCoalescingUndo(editor);
2523 ME_UpdateRepaint(editor, FALSE);
2524 return TRUE;
2529 style = style_get_insert_style( editor, editor->pCursors );
2531 /* Normally the new eop style is the insert style, however in a list it is copied from the existing
2532 eop style (this prevents the list label style changing when the new eop is inserted).
2533 No extra ref is taken here on eop_style. */
2534 if (para->fmt.wNumbering)
2535 eop_style = para->eop_run->style;
2536 else
2537 eop_style = style;
2538 ME_ContinueCoalescingTransaction(editor);
2539 if (shift_is_down)
2540 ME_InsertEndRowFromCursor(editor, 0);
2541 else
2542 if (!editor->bEmulateVersion10)
2543 ME_InsertTextFromCursor(editor, 0, L"\r", 1, eop_style);
2544 else
2545 ME_InsertTextFromCursor(editor, 0, L"\r\n", 2, eop_style);
2546 ME_CommitCoalescingUndo(editor);
2547 SetCursor(NULL);
2549 ME_UpdateSelectionLinkAttribute(editor);
2550 ME_UpdateRepaint(editor, FALSE);
2551 ME_SaveTempStyle(editor, style); /* set the temp insert style for the new para */
2552 ME_ReleaseStyle(style);
2554 return TRUE;
2556 return FALSE;
2559 static BOOL
2560 ME_KeyDown(ME_TextEditor *editor, WORD nKey)
2562 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2563 BOOL shift_is_down = GetKeyState(VK_SHIFT) & 0x8000;
2565 if (editor->bMouseCaptured)
2566 return FALSE;
2567 if (nKey != VK_SHIFT && nKey != VK_CONTROL && nKey != VK_MENU)
2568 editor->nSelectionType = stPosition;
2570 switch (nKey)
2572 case VK_LEFT:
2573 case VK_RIGHT:
2574 case VK_HOME:
2575 case VK_END:
2576 editor->nUDArrowX = -1;
2577 /* fall through */
2578 case VK_UP:
2579 case VK_DOWN:
2580 case VK_PRIOR:
2581 case VK_NEXT:
2582 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
2583 ME_ArrowKey(editor, nKey, shift_is_down, ctrl_is_down);
2584 return TRUE;
2585 case VK_BACK:
2586 case VK_DELETE:
2587 editor->nUDArrowX = -1;
2588 /* FIXME backspace and delete aren't the same, they act different wrt paragraph style of the merged paragraph */
2589 if (editor->props & TXTBIT_READONLY)
2590 return FALSE;
2591 if (ME_IsSelection(editor))
2593 ME_DeleteSelection(editor);
2594 ME_CommitUndo(editor);
2596 else if (nKey == VK_DELETE)
2598 /* Delete stops group typing.
2599 * (See MSDN remarks on EM_STOPGROUPTYPING message) */
2600 ME_DeleteTextAtCursor(editor, 1, 1);
2601 ME_CommitUndo(editor);
2603 else if (ME_ArrowKey(editor, VK_LEFT, FALSE, FALSE))
2605 BOOL bDeletionSucceeded;
2606 /* Backspace can be grouped for a single undo */
2607 ME_ContinueCoalescingTransaction(editor);
2608 bDeletionSucceeded = ME_DeleteTextAtCursor(editor, 1, 1);
2609 if (!bDeletionSucceeded && !editor->bEmulateVersion10) { /* v4.1 */
2610 /* Deletion was prevented so the cursor is moved back to where it was.
2611 * (e.g. this happens when trying to delete cell boundaries)
2613 ME_ArrowKey(editor, VK_RIGHT, FALSE, FALSE);
2615 ME_CommitCoalescingUndo(editor);
2617 else
2618 return TRUE;
2619 table_move_from_row_start( editor );
2620 ME_UpdateSelectionLinkAttribute(editor);
2621 ME_UpdateRepaint(editor, FALSE);
2622 ME_SendRequestResize(editor, FALSE);
2623 return TRUE;
2624 case VK_RETURN:
2625 if (!editor->bEmulateVersion10)
2626 return handle_enter(editor);
2627 break;
2628 case 'A':
2629 if (ctrl_is_down)
2631 set_selection( editor, 0, -1 );
2632 return TRUE;
2634 break;
2635 case 'V':
2636 if (ctrl_is_down)
2637 return paste_special( editor, 0, NULL, FALSE );
2638 break;
2639 case 'C':
2640 case 'X':
2641 if (ctrl_is_down)
2642 return copy_or_cut(editor, nKey == 'X');
2643 break;
2644 case 'Z':
2645 if (ctrl_is_down)
2647 ME_Undo(editor);
2648 return TRUE;
2650 break;
2651 case 'Y':
2652 if (ctrl_is_down)
2654 ME_Redo(editor);
2655 return TRUE;
2657 break;
2659 default:
2660 if (nKey != VK_SHIFT && nKey != VK_CONTROL && nKey && nKey != VK_MENU)
2661 editor->nUDArrowX = -1;
2662 if (ctrl_is_down)
2664 if (nKey == 'W')
2666 CHARFORMAT2W chf;
2667 char buf[2048];
2668 chf.cbSize = sizeof(chf);
2670 ME_GetSelectionCharFormat(editor, &chf);
2671 ME_DumpStyleToBuf(&chf, buf);
2672 MessageBoxA(NULL, buf, "Style dump", MB_OK);
2674 if (nKey == 'Q')
2676 ME_CheckCharOffsets(editor);
2680 return FALSE;
2683 static LRESULT handle_wm_char( ME_TextEditor *editor, WCHAR wstr, LPARAM flags )
2685 if (editor->bMouseCaptured)
2686 return 0;
2688 if (editor->props & TXTBIT_READONLY)
2690 editor_beep( editor, MB_ICONERROR );
2691 return 0; /* FIXME really 0 ? */
2694 if (editor->bEmulateVersion10 && wstr == '\r')
2695 handle_enter(editor);
2697 if ((unsigned)wstr >= ' ' || wstr == '\t')
2699 ME_Cursor cursor = editor->pCursors[0];
2700 ME_Paragraph *para = cursor.para;
2701 LONG from, to;
2702 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2703 ME_GetSelectionOfs(editor, &from, &to);
2704 if (wstr == '\t' &&
2705 /* v4.1 allows tabs to be inserted with ctrl key down */
2706 !(ctrl_is_down && !editor->bEmulateVersion10))
2708 BOOL selected_row = FALSE;
2710 if (ME_IsSelection(editor) &&
2711 cursor.run->nCharOfs + cursor.nOffset == 0 &&
2712 to == ME_GetCursorOfs(&editor->pCursors[0]) && para_prev( para ))
2714 para = para_prev( para );
2715 selected_row = TRUE;
2717 if (para_in_table( para ))
2719 table_handle_tab( editor, selected_row );
2720 ME_CommitUndo(editor);
2721 return 0;
2724 else if (!editor->bEmulateVersion10) /* v4.1 */
2726 if (para->nFlags & MEPF_ROWEND)
2728 if (from == to)
2730 para = para_next( para );
2731 if (para->nFlags & MEPF_ROWSTART) para = para_next( para );
2732 editor->pCursors[0].para = para;
2733 editor->pCursors[0].run = para_first_run( para );
2734 editor->pCursors[0].nOffset = 0;
2735 editor->pCursors[1] = editor->pCursors[0];
2739 else /* v1.0 - 3.0 */
2741 if (para_in_table( para ) && cursor.run->nFlags & MERF_ENDPARA && from == to)
2743 /* Text should not be inserted at the end of the table. */
2744 editor_beep( editor, -1 );
2745 return 0;
2748 /* FIXME maybe it would make sense to call EM_REPLACESEL instead ? */
2749 /* WM_CHAR is restricted to nTextLimit */
2750 if(editor->nTextLimit > ME_GetTextLength(editor) - (to-from))
2752 ME_Style *style = style_get_insert_style( editor, editor->pCursors );
2753 ME_ContinueCoalescingTransaction(editor);
2754 ME_InsertTextFromCursor(editor, 0, &wstr, 1, style);
2755 ME_ReleaseStyle(style);
2756 ME_CommitCoalescingUndo(editor);
2757 ITextHost_TxSetCursor(editor->texthost, NULL, FALSE);
2760 ME_UpdateSelectionLinkAttribute(editor);
2761 ME_UpdateRepaint(editor, FALSE);
2763 return 0;
2766 /* Process the message and calculate the new click count.
2768 * returns: The click count if it is mouse down event, else returns 0. */
2769 static int ME_CalculateClickCount(ME_TextEditor *editor, UINT msg, WPARAM wParam,
2770 LPARAM lParam)
2772 static int clickNum = 0;
2773 if (msg < WM_MOUSEFIRST || msg > WM_MOUSELAST)
2774 return 0;
2776 if ((msg == WM_LBUTTONDBLCLK) ||
2777 (msg == WM_RBUTTONDBLCLK) ||
2778 (msg == WM_MBUTTONDBLCLK) ||
2779 (msg == WM_XBUTTONDBLCLK))
2781 msg -= (WM_LBUTTONDBLCLK - WM_LBUTTONDOWN);
2784 if ((msg == WM_LBUTTONDOWN) ||
2785 (msg == WM_RBUTTONDOWN) ||
2786 (msg == WM_MBUTTONDOWN) ||
2787 (msg == WM_XBUTTONDOWN))
2789 static MSG prevClickMsg;
2790 MSG clickMsg;
2791 /* Compare the editor instead of the hwnd so that the this
2792 * can still be done for windowless richedit controls. */
2793 clickMsg.hwnd = (HWND)editor;
2794 clickMsg.message = msg;
2795 clickMsg.wParam = wParam;
2796 clickMsg.lParam = lParam;
2797 clickMsg.time = GetMessageTime();
2798 clickMsg.pt.x = (short)LOWORD(lParam);
2799 clickMsg.pt.y = (short)HIWORD(lParam);
2800 if ((clickNum != 0) &&
2801 (clickMsg.message == prevClickMsg.message) &&
2802 (clickMsg.hwnd == prevClickMsg.hwnd) &&
2803 (clickMsg.wParam == prevClickMsg.wParam) &&
2804 (clickMsg.time - prevClickMsg.time < GetDoubleClickTime()) &&
2805 (abs(clickMsg.pt.x - prevClickMsg.pt.x) < GetSystemMetrics(SM_CXDOUBLECLK)/2) &&
2806 (abs(clickMsg.pt.y - prevClickMsg.pt.y) < GetSystemMetrics(SM_CYDOUBLECLK)/2))
2808 clickNum++;
2809 } else {
2810 clickNum = 1;
2812 prevClickMsg = clickMsg;
2813 } else {
2814 return 0;
2816 return clickNum;
2819 static BOOL is_link( ME_Run *run )
2821 return (run->style->fmt.dwMask & CFM_LINK) && (run->style->fmt.dwEffects & CFE_LINK);
2824 void editor_set_cursor( ME_TextEditor *editor, int x, int y )
2826 ME_Cursor pos;
2827 static HCURSOR cursor_arrow, cursor_hand, cursor_ibeam, cursor_reverse;
2828 HCURSOR cursor;
2830 if (!cursor_arrow)
2832 cursor_arrow = LoadCursorW( NULL, MAKEINTRESOURCEW( IDC_ARROW ) );
2833 cursor_hand = LoadCursorW( NULL, MAKEINTRESOURCEW( IDC_HAND ) );
2834 cursor_ibeam = LoadCursorW( NULL, MAKEINTRESOURCEW( IDC_IBEAM ) );
2835 cursor_reverse = LoadCursorW( dll_instance, MAKEINTRESOURCEW( OCR_REVERSE ) );
2838 cursor = cursor_ibeam;
2840 if ((editor->nSelectionType == stLine && editor->bMouseCaptured) ||
2841 (!editor->bEmulateVersion10 && y < editor->rcFormat.top && x < editor->rcFormat.left))
2842 cursor = cursor_reverse;
2843 else if (y < editor->rcFormat.top || y > editor->rcFormat.bottom)
2845 if (editor->bEmulateVersion10) cursor = cursor_arrow;
2846 else cursor = cursor_ibeam;
2848 else if (x < editor->rcFormat.left) cursor = cursor_reverse;
2849 else if (cursor_from_coords( editor, x, y, &pos ))
2851 ME_Run *run = pos.run;
2853 if (is_link( run )) cursor = cursor_hand;
2855 else if (ME_IsSelection( editor ))
2857 LONG start, end;
2858 int offset = ME_GetCursorOfs( &pos );
2860 ME_GetSelectionOfs( editor, &start, &end );
2861 if (start <= offset && end >= offset) cursor = cursor_arrow;
2865 ITextHost_TxSetCursor( editor->texthost, cursor, cursor == cursor_ibeam );
2868 static LONG ME_GetSelectionType(ME_TextEditor *editor)
2870 LONG sel_type = SEL_EMPTY;
2871 LONG start, end;
2873 ME_GetSelectionOfs(editor, &start, &end);
2874 if (start == end)
2875 sel_type = SEL_EMPTY;
2876 else
2878 LONG object_count = 0, character_count = 0;
2879 int i;
2881 for (i = 0; i < end - start; i++)
2883 ME_Cursor cursor;
2885 cursor_from_char_ofs( editor, start + i, &cursor );
2886 if (cursor.run->reobj) object_count++;
2887 else character_count++;
2888 if (character_count >= 2 && object_count >= 2)
2889 return (SEL_TEXT | SEL_MULTICHAR | SEL_OBJECT | SEL_MULTIOBJECT);
2891 if (character_count)
2893 sel_type |= SEL_TEXT;
2894 if (character_count >= 2)
2895 sel_type |= SEL_MULTICHAR;
2897 if (object_count)
2899 sel_type |= SEL_OBJECT;
2900 if (object_count >= 2)
2901 sel_type |= SEL_MULTIOBJECT;
2904 return sel_type;
2907 static BOOL ME_ShowContextMenu(ME_TextEditor *editor, int x, int y)
2909 CHARRANGE selrange;
2910 HMENU menu;
2911 int seltype;
2912 HWND hwnd, parent;
2914 if (!editor->lpOleCallback || !editor->have_texthost2) return FALSE;
2915 if (FAILED( ITextHost2_TxGetWindow( editor->texthost, &hwnd ))) return FALSE;
2916 parent = GetParent( hwnd );
2917 if (!parent) parent = hwnd;
2919 ME_GetSelectionOfs( editor, &selrange.cpMin, &selrange.cpMax );
2920 seltype = ME_GetSelectionType( editor );
2921 if (SUCCEEDED( IRichEditOleCallback_GetContextMenu( editor->lpOleCallback, seltype, NULL, &selrange, &menu ) ))
2923 TrackPopupMenu( menu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, x, y, 0, parent, NULL );
2924 DestroyMenu( menu );
2926 return TRUE;
2929 ME_TextEditor *ME_MakeEditor(ITextHost *texthost, BOOL bEmulateVersion10)
2931 ME_TextEditor *ed = heap_alloc(sizeof(*ed));
2932 int i;
2933 LONG selbarwidth;
2934 HRESULT hr;
2936 ed->sizeWindow.cx = ed->sizeWindow.cy = 0;
2937 if (ITextHost_QueryInterface( texthost, &IID_ITextHost2, (void **)&ed->texthost ) == S_OK)
2939 ITextHost_Release( texthost );
2940 ed->have_texthost2 = TRUE;
2942 else
2944 ed->texthost = (ITextHost2 *)texthost;
2945 ed->have_texthost2 = FALSE;
2948 ed->bEmulateVersion10 = bEmulateVersion10;
2949 ed->in_place_active = FALSE;
2950 ed->total_rows = 0;
2951 ITextHost_TxGetPropertyBits( ed->texthost, TXTBIT_RICHTEXT | TXTBIT_MULTILINE | TXTBIT_READONLY |
2952 TXTBIT_USEPASSWORD | TXTBIT_HIDESELECTION | TXTBIT_SAVESELECTION |
2953 TXTBIT_AUTOWORDSEL | TXTBIT_VERTICAL | TXTBIT_WORDWRAP | TXTBIT_ALLOWBEEP |
2954 TXTBIT_DISABLEDRAG,
2955 &ed->props );
2956 ITextHost_TxGetScrollBars( ed->texthost, &ed->scrollbars );
2957 ed->pBuffer = ME_MakeText();
2958 ed->nZoomNumerator = ed->nZoomDenominator = 0;
2959 ed->nAvailWidth = 0; /* wrap to client area */
2960 list_init( &ed->style_list );
2961 ME_MakeFirstParagraph(ed);
2962 /* The four cursors are for:
2963 * 0 - The position where the caret is shown
2964 * 1 - The anchored end of the selection (for normal selection)
2965 * 2 & 3 - The anchored start and end respectively for word, line,
2966 * or paragraph selection.
2968 ed->nCursors = 4;
2969 ed->pCursors = heap_alloc(ed->nCursors * sizeof(*ed->pCursors));
2970 ME_SetCursorToStart(ed, &ed->pCursors[0]);
2971 ed->pCursors[1] = ed->pCursors[0];
2972 ed->pCursors[2] = ed->pCursors[0];
2973 ed->pCursors[3] = ed->pCursors[1];
2974 ed->nLastTotalLength = ed->nTotalLength = 0;
2975 ed->nLastTotalWidth = ed->nTotalWidth = 0;
2976 ed->nUDArrowX = -1;
2977 ed->nEventMask = 0;
2978 ed->nModifyStep = 0;
2979 ed->nTextLimit = TEXT_LIMIT_DEFAULT;
2980 list_init( &ed->undo_stack );
2981 list_init( &ed->redo_stack );
2982 ed->nUndoStackSize = 0;
2983 ed->nUndoLimit = STACK_SIZE_DEFAULT;
2984 ed->nUndoMode = umAddToUndo;
2985 ed->undo_ctl_state = undoActive;
2986 ed->nParagraphs = 1;
2987 ed->nLastSelStart = ed->nLastSelEnd = 0;
2988 ed->last_sel_start_para = ed->last_sel_end_para = ed->pCursors[0].para;
2989 ed->bHideSelection = FALSE;
2990 ed->pfnWordBreak = NULL;
2991 ed->richole = NULL;
2992 ed->lpOleCallback = NULL;
2993 ed->mode = TM_MULTILEVELUNDO | TM_MULTICODEPAGE;
2994 ed->mode |= (ed->props & TXTBIT_RICHTEXT) ? TM_RICHTEXT : TM_PLAINTEXT;
2995 ed->AutoURLDetect_bEnable = FALSE;
2996 ed->bHaveFocus = FALSE;
2997 ed->freeze_count = 0;
2998 ed->bMouseCaptured = FALSE;
2999 ed->caret_hidden = FALSE;
3000 ed->caret_height = 0;
3001 for (i=0; i<HFONT_CACHE_SIZE; i++)
3003 ed->pFontCache[i].nRefs = 0;
3004 ed->pFontCache[i].nAge = 0;
3005 ed->pFontCache[i].hFont = NULL;
3008 ME_CheckCharOffsets(ed);
3009 SetRectEmpty(&ed->rcFormat);
3010 hr = ITextHost_TxGetSelectionBarWidth( ed->texthost, &selbarwidth );
3011 /* FIXME: Convert selbarwidth from HIMETRIC to pixels */
3012 if (hr == S_OK && selbarwidth) ed->selofs = SELECTIONBAR_WIDTH;
3013 else ed->selofs = 0;
3014 ed->nSelectionType = stPosition;
3016 ed->password_char = 0;
3017 if (ed->props & TXTBIT_USEPASSWORD)
3018 ITextHost_TxGetPasswordChar( ed->texthost, &ed->password_char );
3020 ed->bWordWrap = (ed->props & TXTBIT_WORDWRAP) && (ed->props & TXTBIT_MULTILINE);
3022 ed->notified_cr.cpMin = ed->notified_cr.cpMax = 0;
3024 /* Default scrollbar information */
3025 ed->vert_si.cbSize = sizeof(SCROLLINFO);
3026 ed->vert_si.nMin = 0;
3027 ed->vert_si.nMax = 0;
3028 ed->vert_si.nPage = 0;
3029 ed->vert_si.nPos = 0;
3030 ed->vert_sb_enabled = 0;
3032 ed->horz_si.cbSize = sizeof(SCROLLINFO);
3033 ed->horz_si.nMin = 0;
3034 ed->horz_si.nMax = 0;
3035 ed->horz_si.nPage = 0;
3036 ed->horz_si.nPos = 0;
3037 ed->horz_sb_enabled = 0;
3039 if (ed->scrollbars & ES_DISABLENOSCROLL)
3041 if (ed->scrollbars & WS_VSCROLL)
3043 ITextHost_TxSetScrollRange( ed->texthost, SB_VERT, 0, 1, TRUE );
3044 ITextHost_TxEnableScrollBar( ed->texthost, SB_VERT, ESB_DISABLE_BOTH );
3046 if (ed->scrollbars & WS_HSCROLL)
3048 ITextHost_TxSetScrollRange( ed->texthost, SB_HORZ, 0, 1, TRUE );
3049 ITextHost_TxEnableScrollBar( ed->texthost, SB_HORZ, ESB_DISABLE_BOTH );
3053 ed->wheel_remain = 0;
3055 ed->back_style = TXTBACK_OPAQUE;
3056 ITextHost_TxGetBackStyle( ed->texthost, &ed->back_style );
3058 list_init( &ed->reobj_list );
3059 OleInitialize(NULL);
3061 return ed;
3064 void ME_DestroyEditor(ME_TextEditor *editor)
3066 ME_DisplayItem *p = editor->pBuffer->pFirst, *pNext = NULL;
3067 ME_Style *s, *cursor2;
3068 int i;
3070 ME_ClearTempStyle(editor);
3071 ME_EmptyUndoStack(editor);
3072 editor->pBuffer->pFirst = NULL;
3073 while(p)
3075 pNext = p->next;
3076 if (p->type == diParagraph)
3077 para_destroy( editor, &p->member.para );
3078 else
3079 ME_DestroyDisplayItem(p);
3080 p = pNext;
3083 LIST_FOR_EACH_ENTRY_SAFE( s, cursor2, &editor->style_list, ME_Style, entry )
3084 ME_DestroyStyle( s );
3086 ME_ReleaseStyle(editor->pBuffer->pDefaultStyle);
3087 for (i=0; i<HFONT_CACHE_SIZE; i++)
3089 if (editor->pFontCache[i].hFont)
3090 DeleteObject(editor->pFontCache[i].hFont);
3092 if(editor->lpOleCallback)
3093 IRichEditOleCallback_Release(editor->lpOleCallback);
3095 OleUninitialize();
3097 heap_free(editor->pBuffer);
3098 heap_free(editor->pCursors);
3099 heap_free(editor);
3102 static inline int get_default_line_height( ME_TextEditor *editor )
3104 int height = 0;
3106 if (editor->pBuffer && editor->pBuffer->pDefaultStyle)
3107 height = editor->pBuffer->pDefaultStyle->tm.tmHeight;
3108 if (height <= 0) height = 24;
3110 return height;
3113 static inline int calc_wheel_change( int *remain, int amount_per_click )
3115 int change = amount_per_click * (float)*remain / WHEEL_DELTA;
3116 *remain -= WHEEL_DELTA * change / amount_per_click;
3117 return change;
3120 void link_notify(ME_TextEditor *editor, UINT msg, WPARAM wParam, LPARAM lParam)
3122 int x,y;
3123 ME_Cursor cursor; /* The start of the clicked text. */
3124 ME_Run *run;
3125 ENLINK info;
3127 x = (short)LOWORD(lParam);
3128 y = (short)HIWORD(lParam);
3129 if (!cursor_from_coords( editor, x, y, &cursor )) return;
3131 if (is_link( cursor.run ))
3132 { /* The clicked run has CFE_LINK set */
3133 info.nmhdr.hwndFrom = NULL;
3134 info.nmhdr.idFrom = 0;
3135 info.nmhdr.code = EN_LINK;
3136 info.msg = msg;
3137 info.wParam = wParam;
3138 info.lParam = lParam;
3139 cursor.nOffset = 0;
3141 /* find the first contiguous run with CFE_LINK set */
3142 info.chrg.cpMin = ME_GetCursorOfs(&cursor);
3143 run = cursor.run;
3144 while ((run = run_prev( run )) && is_link( run ))
3145 info.chrg.cpMin -= run->len;
3147 /* find the last contiguous run with CFE_LINK set */
3148 info.chrg.cpMax = ME_GetCursorOfs(&cursor) + cursor.run->len;
3149 run = cursor.run;
3150 while ((run = run_next( run )) && is_link( run ))
3151 info.chrg.cpMax += run->len;
3153 ITextHost_TxNotify(editor->texthost, info.nmhdr.code, &info);
3157 void ME_ReplaceSel(ME_TextEditor *editor, BOOL can_undo, const WCHAR *str, int len)
3159 LONG from, to;
3160 int nStartCursor;
3161 ME_Style *style;
3163 nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3164 style = ME_GetSelectionInsertStyle(editor);
3165 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to-from, FALSE);
3166 ME_InsertTextFromCursor(editor, 0, str, len, style);
3167 ME_ReleaseStyle(style);
3168 /* drop temporary style if line end */
3170 * FIXME question: does abc\n mean: put abc,
3171 * clear temp style, put \n? (would require a change)
3173 if (len>0 && str[len-1] == '\n')
3174 ME_ClearTempStyle(editor);
3175 ME_CommitUndo(editor);
3176 ME_UpdateSelectionLinkAttribute(editor);
3177 if (!can_undo)
3178 ME_EmptyUndoStack(editor);
3179 ME_UpdateRepaint(editor, FALSE);
3182 static void ME_SetText(ME_TextEditor *editor, void *text, BOOL unicode)
3184 LONG codepage = unicode ? CP_UNICODE : CP_ACP;
3185 int textLen;
3187 LPWSTR wszText = ME_ToUnicode(codepage, text, &textLen);
3188 ME_InsertTextFromCursor(editor, 0, wszText, textLen, editor->pBuffer->pDefaultStyle);
3189 ME_EndToUnicode(codepage, wszText);
3192 static LRESULT handle_EM_SETCHARFORMAT( ME_TextEditor *editor, WPARAM flags, const CHARFORMAT2W *fmt_in )
3194 CHARFORMAT2W fmt;
3195 BOOL changed = TRUE;
3196 ME_Cursor start, end;
3198 if (!cfany_to_cf2w( &fmt, fmt_in )) return 0;
3200 if (flags & SCF_ALL)
3202 if (editor->mode & TM_PLAINTEXT)
3204 ME_SetDefaultCharFormat( editor, &fmt );
3206 else
3208 ME_SetCursorToStart( editor, &start );
3209 ME_SetCharFormat( editor, &start, NULL, &fmt );
3210 editor->nModifyStep = 1;
3213 else if (flags & SCF_SELECTION)
3215 if (editor->mode & TM_PLAINTEXT) return 0;
3216 if (flags & SCF_WORD)
3218 end = editor->pCursors[0];
3219 ME_MoveCursorWords( editor, &end, +1 );
3220 start = end;
3221 ME_MoveCursorWords( editor, &start, -1 );
3222 ME_SetCharFormat( editor, &start, &end, &fmt );
3224 changed = ME_IsSelection( editor );
3225 ME_SetSelectionCharFormat( editor, &fmt );
3226 if (changed) editor->nModifyStep = 1;
3228 else /* SCF_DEFAULT */
3230 ME_SetDefaultCharFormat( editor, &fmt );
3233 ME_CommitUndo( editor );
3234 if (changed)
3236 ME_WrapMarkedParagraphs( editor );
3237 ME_UpdateScrollBar( editor );
3239 return 1;
3242 #define UNSUPPORTED_MSG(e) \
3243 case e: \
3244 FIXME(#e ": stub\n"); \
3245 *phresult = S_FALSE; \
3246 return 0;
3248 /* Handle messages for windowless and windowed richedit controls.
3250 * The LRESULT that is returned is a return value for window procs,
3251 * and the phresult parameter is the COM return code needed by the
3252 * text services interface. */
3253 LRESULT editor_handle_message( ME_TextEditor *editor, UINT msg, WPARAM wParam,
3254 LPARAM lParam, HRESULT* phresult )
3256 *phresult = S_OK;
3258 switch(msg) {
3260 UNSUPPORTED_MSG(EM_DISPLAYBAND)
3261 UNSUPPORTED_MSG(EM_FINDWORDBREAK)
3262 UNSUPPORTED_MSG(EM_FMTLINES)
3263 UNSUPPORTED_MSG(EM_FORMATRANGE)
3264 UNSUPPORTED_MSG(EM_GETBIDIOPTIONS)
3265 UNSUPPORTED_MSG(EM_GETEDITSTYLE)
3266 UNSUPPORTED_MSG(EM_GETIMECOMPMODE)
3267 UNSUPPORTED_MSG(EM_GETIMESTATUS)
3268 UNSUPPORTED_MSG(EM_SETIMESTATUS)
3269 UNSUPPORTED_MSG(EM_GETLANGOPTIONS)
3270 UNSUPPORTED_MSG(EM_GETREDONAME)
3271 UNSUPPORTED_MSG(EM_GETTYPOGRAPHYOPTIONS)
3272 UNSUPPORTED_MSG(EM_GETUNDONAME)
3273 UNSUPPORTED_MSG(EM_GETWORDBREAKPROCEX)
3274 UNSUPPORTED_MSG(EM_SETBIDIOPTIONS)
3275 UNSUPPORTED_MSG(EM_SETEDITSTYLE)
3276 UNSUPPORTED_MSG(EM_SETLANGOPTIONS)
3277 UNSUPPORTED_MSG(EM_SETMARGINS)
3278 UNSUPPORTED_MSG(EM_SETPALETTE)
3279 UNSUPPORTED_MSG(EM_SETTABSTOPS)
3280 UNSUPPORTED_MSG(EM_SETTYPOGRAPHYOPTIONS)
3281 UNSUPPORTED_MSG(EM_SETWORDBREAKPROCEX)
3283 /* Messages specific to Richedit controls */
3285 case EM_STREAMIN:
3286 return ME_StreamIn(editor, wParam, (EDITSTREAM*)lParam, TRUE);
3287 case EM_STREAMOUT:
3288 return ME_StreamOut(editor, wParam, (EDITSTREAM *)lParam);
3289 case EM_EMPTYUNDOBUFFER:
3290 ME_EmptyUndoStack(editor);
3291 return 0;
3292 case EM_GETSEL:
3294 /* Note: wParam/lParam can be NULL */
3295 LONG from, to;
3296 LONG *pfrom = wParam ? (LONG *)wParam : &from;
3297 LONG *pto = lParam ? (LONG *)lParam : &to;
3298 ME_GetSelectionOfs(editor, pfrom, pto);
3299 if ((*pfrom|*pto) & 0xFFFF0000)
3300 return -1;
3301 return MAKELONG(*pfrom,*pto);
3303 case EM_EXGETSEL:
3305 CHARRANGE *pRange = (CHARRANGE *)lParam;
3306 ME_GetSelectionOfs(editor, &pRange->cpMin, &pRange->cpMax);
3307 TRACE("EM_EXGETSEL = (%ld,%ld)\n", pRange->cpMin, pRange->cpMax);
3308 return 0;
3310 case EM_SETUNDOLIMIT:
3312 editor_enable_undo(editor);
3313 if ((int)wParam < 0)
3314 editor->nUndoLimit = STACK_SIZE_DEFAULT;
3315 else
3316 editor->nUndoLimit = min(wParam, STACK_SIZE_MAX);
3317 /* Setting a max stack size keeps wine from getting killed
3318 for hogging memory. Windows allocates all this memory at once, so
3319 no program would realistically set a value above our maximum. */
3320 return editor->nUndoLimit;
3322 case EM_CANUNDO:
3323 return !list_empty( &editor->undo_stack );
3324 case EM_CANREDO:
3325 return !list_empty( &editor->redo_stack );
3326 case WM_UNDO: /* FIXME: actually not the same */
3327 case EM_UNDO:
3328 return ME_Undo(editor);
3329 case EM_REDO:
3330 return ME_Redo(editor);
3331 case EM_SETFONTSIZE:
3333 CHARFORMAT2W cf;
3334 LONG tmp_size, size;
3335 BOOL is_increase = ((LONG)wParam > 0);
3337 if (editor->mode & TM_PLAINTEXT)
3338 return FALSE;
3340 cf.cbSize = sizeof(cf);
3341 cf.dwMask = CFM_SIZE;
3342 ME_GetSelectionCharFormat(editor, &cf);
3343 tmp_size = (cf.yHeight / 20) + wParam;
3345 if (tmp_size <= 1)
3346 size = 1;
3347 else if (tmp_size > 12 && tmp_size < 28 && tmp_size % 2)
3348 size = tmp_size + (is_increase ? 1 : -1);
3349 else if (tmp_size > 28 && tmp_size < 36)
3350 size = is_increase ? 36 : 28;
3351 else if (tmp_size > 36 && tmp_size < 48)
3352 size = is_increase ? 48 : 36;
3353 else if (tmp_size > 48 && tmp_size < 72)
3354 size = is_increase ? 72 : 48;
3355 else if (tmp_size > 72 && tmp_size < 80)
3356 size = is_increase ? 80 : 72;
3357 else if (tmp_size > 80 && tmp_size < 1638)
3358 size = 10 * (is_increase ? (tmp_size / 10 + 1) : (tmp_size / 10));
3359 else if (tmp_size >= 1638)
3360 size = 1638;
3361 else
3362 size = tmp_size;
3364 cf.yHeight = size * 20; /* convert twips to points */
3365 ME_SetSelectionCharFormat(editor, &cf);
3366 ME_CommitUndo(editor);
3367 ME_WrapMarkedParagraphs(editor);
3368 ME_UpdateScrollBar(editor);
3370 return TRUE;
3372 case EM_SETSEL:
3374 return set_selection( editor, wParam, lParam );
3376 case EM_SETSCROLLPOS:
3378 POINT *point = (POINT *)lParam;
3379 scroll_abs( editor, point->x, point->y, TRUE );
3380 return 0;
3382 case EM_AUTOURLDETECT:
3384 if (wParam==1 || wParam ==0)
3386 editor->AutoURLDetect_bEnable = (BOOL)wParam;
3387 return 0;
3389 return E_INVALIDARG;
3391 case EM_GETAUTOURLDETECT:
3393 return editor->AutoURLDetect_bEnable;
3395 case EM_EXSETSEL:
3397 CHARRANGE range = *(CHARRANGE *)lParam;
3399 return set_selection( editor, range.cpMin, range.cpMax );
3401 case EM_SETTEXTEX:
3403 LPWSTR wszText;
3404 SETTEXTEX *pStruct = (SETTEXTEX *)wParam;
3405 LONG from, to;
3406 int len;
3407 ME_Style *style;
3408 BOOL bRtf, bUnicode, bSelection, bUTF8;
3409 int oldModify = editor->nModifyStep;
3410 static const char utf8_bom[] = {0xef, 0xbb, 0xbf};
3412 if (!pStruct) return 0;
3414 /* If we detect ascii rtf at the start of the string,
3415 * we know it isn't unicode. */
3416 bRtf = (lParam && (!strncmp((char *)lParam, "{\\rtf", 5) ||
3417 !strncmp((char *)lParam, "{\\urtf", 6)));
3418 bUnicode = !bRtf && pStruct->codepage == CP_UNICODE;
3419 bUTF8 = (lParam && (!strncmp((char *)lParam, utf8_bom, 3)));
3421 TRACE("EM_SETTEXTEX - %s, flags %ld, cp %d\n",
3422 bUnicode ? debugstr_w((LPCWSTR)lParam) : debugstr_a((LPCSTR)lParam),
3423 pStruct->flags, pStruct->codepage);
3425 bSelection = (pStruct->flags & ST_SELECTION) != 0;
3426 if (bSelection) {
3427 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3428 style = ME_GetSelectionInsertStyle(editor);
3429 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to - from, FALSE);
3430 } else {
3431 ME_Cursor start;
3432 ME_SetCursorToStart(editor, &start);
3433 ME_InternalDeleteText(editor, &start, ME_GetTextLength(editor), FALSE);
3434 style = editor->pBuffer->pDefaultStyle;
3437 if (bRtf) {
3438 ME_StreamInRTFString(editor, bSelection, (char *)lParam);
3439 if (bSelection) {
3440 /* FIXME: The length returned doesn't include the rtf control
3441 * characters, only the actual text. */
3442 len = lParam ? strlen((char *)lParam) : 0;
3444 } else {
3445 if (bUTF8 && !bUnicode) {
3446 wszText = ME_ToUnicode(CP_UTF8, (void *)(lParam+3), &len);
3447 ME_InsertTextFromCursor(editor, 0, wszText, len, style);
3448 ME_EndToUnicode(CP_UTF8, wszText);
3449 } else {
3450 wszText = ME_ToUnicode(pStruct->codepage, (void *)lParam, &len);
3451 ME_InsertTextFromCursor(editor, 0, wszText, len, style);
3452 ME_EndToUnicode(pStruct->codepage, wszText);
3456 if (bSelection) {
3457 ME_ReleaseStyle(style);
3458 ME_UpdateSelectionLinkAttribute(editor);
3459 } else {
3460 ME_Cursor cursor;
3461 len = 1;
3462 ME_SetCursorToStart(editor, &cursor);
3463 ME_UpdateLinkAttribute(editor, &cursor, INT_MAX);
3465 ME_CommitUndo(editor);
3466 if (!(pStruct->flags & ST_KEEPUNDO))
3468 editor->nModifyStep = oldModify;
3469 ME_EmptyUndoStack(editor);
3471 ME_UpdateRepaint(editor, FALSE);
3472 return len;
3474 case EM_SELECTIONTYPE:
3475 return ME_GetSelectionType(editor);
3476 case EM_GETMODIFY:
3477 return editor->nModifyStep == 0 ? 0 : -1;
3478 case EM_SETMODIFY:
3480 if (wParam)
3481 editor->nModifyStep = 1;
3482 else
3483 editor->nModifyStep = 0;
3485 return 0;
3487 case EM_SETEVENTMASK:
3489 DWORD nOldMask = editor->nEventMask;
3491 editor->nEventMask = lParam;
3492 return nOldMask;
3494 case EM_GETEVENTMASK:
3495 return editor->nEventMask;
3496 case EM_SETCHARFORMAT:
3497 return handle_EM_SETCHARFORMAT( editor, wParam, (CHARFORMAT2W *)lParam );
3498 case EM_GETCHARFORMAT:
3500 CHARFORMAT2W tmp, *dst = (CHARFORMAT2W *)lParam;
3501 if (dst->cbSize != sizeof(CHARFORMATA) &&
3502 dst->cbSize != sizeof(CHARFORMATW) &&
3503 dst->cbSize != sizeof(CHARFORMAT2A) &&
3504 dst->cbSize != sizeof(CHARFORMAT2W))
3505 return 0;
3506 tmp.cbSize = sizeof(tmp);
3507 if (!wParam)
3508 ME_GetDefaultCharFormat(editor, &tmp);
3509 else
3510 ME_GetSelectionCharFormat(editor, &tmp);
3511 cf2w_to_cfany(dst, &tmp);
3512 return tmp.dwMask;
3514 case EM_SETPARAFORMAT:
3516 BOOL result = editor_set_selection_para_fmt( editor, (PARAFORMAT2 *)lParam );
3517 ME_WrapMarkedParagraphs(editor);
3518 ME_UpdateScrollBar(editor);
3519 ME_CommitUndo(editor);
3520 return result;
3522 case EM_GETPARAFORMAT:
3523 editor_get_selection_para_fmt( editor, (PARAFORMAT2 *)lParam );
3524 return ((PARAFORMAT2 *)lParam)->dwMask;
3525 case EM_GETFIRSTVISIBLELINE:
3527 ME_Paragraph *para = editor_first_para( editor );
3528 ME_Row *row;
3529 int y = editor->vert_si.nPos;
3530 int count = 0;
3532 while (para_next( para ))
3534 if (y < para->pt.y + para->nHeight) break;
3535 count += para->nRows;
3536 para = para_next( para );
3539 row = para_first_row( para );
3540 while (row)
3542 if (y < para->pt.y + row->pt.y + row->nHeight) break;
3543 count++;
3544 row = row_next( row );
3546 return count;
3548 case EM_HIDESELECTION:
3550 editor->bHideSelection = (wParam != 0);
3551 ME_InvalidateSelection(editor);
3552 return 0;
3554 case EM_LINESCROLL:
3556 if (!(editor->props & TXTBIT_MULTILINE))
3557 return FALSE;
3558 ME_ScrollDown( editor, lParam * get_default_line_height( editor ) );
3559 return TRUE;
3561 case WM_CLEAR:
3563 LONG from, to;
3564 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3565 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to-from, FALSE);
3566 ME_CommitUndo(editor);
3567 ME_UpdateRepaint(editor, TRUE);
3568 return 0;
3570 case EM_REPLACESEL:
3572 WCHAR *text = (WCHAR *)lParam;
3573 int len = text ? lstrlenW( text ) : 0;
3575 TRACE( "EM_REPLACESEL - %s\n", debugstr_w( text ) );
3576 ME_ReplaceSel( editor, !!wParam, text, len );
3577 return len;
3579 case EM_SCROLLCARET:
3580 editor_ensure_visible( editor, &editor->pCursors[0] );
3581 return 0;
3582 case WM_SETFONT:
3584 LOGFONTW lf;
3585 CHARFORMAT2W fmt;
3586 HDC hDC;
3587 BOOL bRepaint = LOWORD(lParam);
3589 if (!wParam)
3590 wParam = (WPARAM)GetStockObject(SYSTEM_FONT);
3592 if (!GetObjectW((HGDIOBJ)wParam, sizeof(LOGFONTW), &lf))
3593 return 0;
3595 hDC = ITextHost_TxGetDC(editor->texthost);
3596 ME_CharFormatFromLogFont(hDC, &lf, &fmt);
3597 ITextHost_TxReleaseDC(editor->texthost, hDC);
3598 if (editor->mode & TM_RICHTEXT) {
3599 ME_Cursor start;
3600 ME_SetCursorToStart(editor, &start);
3601 ME_SetCharFormat(editor, &start, NULL, &fmt);
3603 ME_SetDefaultCharFormat(editor, &fmt);
3605 ME_CommitUndo(editor);
3606 editor_mark_rewrap_all( editor );
3607 ME_WrapMarkedParagraphs(editor);
3608 ME_UpdateScrollBar(editor);
3609 if (bRepaint)
3610 ME_Repaint(editor);
3611 return 0;
3613 case WM_SETTEXT:
3615 ME_Cursor cursor;
3616 ME_SetCursorToStart(editor, &cursor);
3617 ME_InternalDeleteText(editor, &cursor, ME_GetTextLength(editor), FALSE);
3618 if (lParam)
3620 TRACE("WM_SETTEXT lParam==%Ix\n",lParam);
3621 if (!strncmp((char *)lParam, "{\\rtf", 5) ||
3622 !strncmp((char *)lParam, "{\\urtf", 6))
3624 /* Undocumented: WM_SETTEXT supports RTF text */
3625 ME_StreamInRTFString(editor, 0, (char *)lParam);
3627 else
3628 ME_SetText( editor, (void*)lParam, TRUE );
3630 else
3631 TRACE("WM_SETTEXT - NULL\n");
3632 ME_SetCursorToStart(editor, &cursor);
3633 ME_UpdateLinkAttribute(editor, &cursor, INT_MAX);
3634 set_selection_cursors(editor, 0, 0);
3635 editor->nModifyStep = 0;
3636 ME_CommitUndo(editor);
3637 ME_EmptyUndoStack(editor);
3638 ME_UpdateRepaint(editor, FALSE);
3639 return 1;
3641 case EM_CANPASTE:
3642 return paste_special( editor, 0, NULL, TRUE );
3643 case WM_PASTE:
3644 case WM_MBUTTONDOWN:
3645 wParam = 0;
3646 lParam = 0;
3647 /* fall through */
3648 case EM_PASTESPECIAL:
3649 paste_special( editor, wParam, (REPASTESPECIAL *)lParam, FALSE );
3650 return 0;
3651 case WM_CUT:
3652 case WM_COPY:
3653 copy_or_cut(editor, msg == WM_CUT);
3654 return 0;
3655 case WM_GETTEXTLENGTH:
3657 GETTEXTLENGTHEX how;
3658 how.flags = GTL_CLOSE | (editor->bEmulateVersion10 ? 0 : GTL_USECRLF) | GTL_NUMCHARS;
3659 how.codepage = CP_UNICODE;
3660 return ME_GetTextLengthEx(editor, &how);
3662 case EM_GETTEXTLENGTHEX:
3663 return ME_GetTextLengthEx(editor, (GETTEXTLENGTHEX *)wParam);
3664 case WM_GETTEXT:
3666 GETTEXTEX ex;
3667 ex.cb = wParam * sizeof(WCHAR);
3668 ex.flags = GT_USECRLF;
3669 ex.codepage = CP_UNICODE;
3670 ex.lpDefaultChar = NULL;
3671 ex.lpUsedDefChar = NULL;
3672 return ME_GetTextEx(editor, &ex, lParam);
3674 case EM_GETTEXTEX:
3675 return ME_GetTextEx(editor, (GETTEXTEX*)wParam, lParam);
3676 case EM_GETSELTEXT:
3678 LONG nFrom, nTo;
3679 int nStartCur = ME_GetSelectionOfs(editor, &nFrom, &nTo);
3680 ME_Cursor *from = &editor->pCursors[nStartCur];
3681 return get_text_range( editor, (WCHAR *)lParam, from, nTo - nFrom );
3683 case EM_GETSCROLLPOS:
3685 POINT *point = (POINT *)lParam;
3686 point->x = editor->horz_si.nPos;
3687 point->y = editor->vert_si.nPos;
3688 /* 16-bit scaled value is returned as stored in scrollinfo */
3689 if (editor->horz_si.nMax > 0xffff)
3690 point->x = MulDiv(point->x, 0xffff, editor->horz_si.nMax);
3691 if (editor->vert_si.nMax > 0xffff)
3692 point->y = MulDiv(point->y, 0xffff, editor->vert_si.nMax);
3693 return 1;
3695 case EM_GETTEXTRANGE:
3697 TEXTRANGEW *rng = (TEXTRANGEW *)lParam;
3698 ME_Cursor start;
3699 int nStart = rng->chrg.cpMin;
3700 int nEnd = rng->chrg.cpMax;
3701 int textlength = ME_GetTextLength(editor);
3703 TRACE( "EM_GETTEXTRANGE min = %ld max = %ld textlength = %d\n", rng->chrg.cpMin, rng->chrg.cpMax, textlength );
3704 if (nStart < 0) return 0;
3705 if ((nStart == 0 && nEnd == -1) || nEnd > textlength)
3706 nEnd = textlength;
3707 if (nStart >= nEnd) return 0;
3709 cursor_from_char_ofs( editor, nStart, &start );
3710 return get_text_range( editor, rng->lpstrText, &start, nEnd - nStart );
3712 case EM_GETLINE:
3714 ME_Row *row;
3715 ME_Run *run;
3716 const unsigned int nMaxChars = *(WORD *) lParam;
3717 unsigned int nCharsLeft = nMaxChars;
3718 char *dest = (char *) lParam;
3719 ME_Cursor start, end;
3721 TRACE( "EM_GETLINE: row=%d, nMaxChars=%d\n", (int)wParam, nMaxChars );
3723 row = row_from_row_number( editor, wParam );
3724 if (row == NULL) return 0;
3726 row_first_cursor( row, &start );
3727 row_end_cursor( row, &end, TRUE );
3728 run = start.run;
3729 while (nCharsLeft)
3731 WCHAR *str;
3732 unsigned int nCopy;
3733 int ofs = (run == start.run) ? start.nOffset : 0;
3734 int len = (run == end.run) ? end.nOffset : run->len;
3736 str = get_text( run, ofs );
3737 nCopy = min( nCharsLeft, len );
3739 memcpy(dest, str, nCopy * sizeof(WCHAR));
3740 dest += nCopy * sizeof(WCHAR);
3741 nCharsLeft -= nCopy;
3742 if (run == end.run) break;
3743 run = row_next_run( row, run );
3746 /* append line termination, space allowing */
3747 if (nCharsLeft > 0) *((WCHAR *)dest) = '\0';
3749 TRACE("EM_GETLINE: got %u characters\n", nMaxChars - nCharsLeft);
3750 return nMaxChars - nCharsLeft;
3752 case EM_GETLINECOUNT:
3754 int count = editor->total_rows;
3755 ME_Run *prev_run, *last_run;
3757 last_run = para_end_run( para_prev( editor_end_para( editor ) ) );
3758 prev_run = run_prev_all_paras( last_run );
3760 if (editor->bEmulateVersion10 && prev_run && last_run->nCharOfs == 0 &&
3761 prev_run->len == 1 && *get_text( prev_run, 0 ) == '\r')
3763 /* In 1.0 emulation, the last solitary \r at the very end of the text
3764 (if one exists) is NOT a line break.
3765 FIXME: this is an ugly hack. This should have a more regular model. */
3766 count--;
3769 count = max(1, count);
3770 TRACE("EM_GETLINECOUNT: count==%d\n", count);
3771 return count;
3773 case EM_LINEFROMCHAR:
3775 if (wParam == -1) wParam = ME_GetCursorOfs( editor->pCursors + 1 );
3776 return row_number_from_char_ofs( editor, wParam );
3778 case EM_EXLINEFROMCHAR:
3780 if (lParam == -1) lParam = ME_GetCursorOfs( editor->pCursors + 1 );
3781 return row_number_from_char_ofs( editor, lParam );
3783 case EM_LINEINDEX:
3785 ME_Row *row;
3786 ME_Cursor cursor;
3787 int ofs;
3789 if (wParam == -1) row = row_from_cursor( editor->pCursors );
3790 else row = row_from_row_number( editor, wParam );
3791 if (!row) return -1;
3793 row_first_cursor( row, &cursor );
3794 ofs = ME_GetCursorOfs( &cursor );
3795 TRACE( "EM_LINEINDEX: nCharOfs==%d\n", ofs );
3796 return ofs;
3798 case EM_LINELENGTH:
3800 ME_Row *row;
3801 int start_ofs, end_ofs;
3802 ME_Cursor cursor;
3804 if (wParam > ME_GetTextLength(editor))
3805 return 0;
3806 if (wParam == -1)
3808 FIXME("EM_LINELENGTH: returning number of unselected characters on lines with selection unsupported.\n");
3809 return 0;
3811 cursor_from_char_ofs( editor, wParam, &cursor );
3812 row = row_from_cursor( &cursor );
3813 row_first_cursor( row, &cursor );
3814 start_ofs = ME_GetCursorOfs( &cursor );
3815 row_end_cursor( row, &cursor, FALSE );
3816 end_ofs = ME_GetCursorOfs( &cursor );
3817 TRACE( "EM_LINELENGTH(%Id)==%d\n", wParam, end_ofs - start_ofs );
3818 return end_ofs - start_ofs;
3820 case EM_EXLIMITTEXT:
3822 if ((int)lParam < 0)
3823 return 0;
3824 if (lParam == 0)
3825 editor->nTextLimit = 65536;
3826 else
3827 editor->nTextLimit = (int) lParam;
3828 return 0;
3830 case EM_LIMITTEXT:
3832 if (wParam == 0)
3833 editor->nTextLimit = 65536;
3834 else
3835 editor->nTextLimit = (int) wParam;
3836 return 0;
3838 case EM_GETLIMITTEXT:
3840 return editor->nTextLimit;
3842 case EM_FINDTEXT:
3843 case EM_FINDTEXTW:
3845 FINDTEXTW *ft = (FINDTEXTW *)lParam;
3846 return ME_FindText(editor, wParam, &ft->chrg, ft->lpstrText, NULL);
3848 case EM_FINDTEXTEX:
3849 case EM_FINDTEXTEXW:
3851 FINDTEXTEXW *ex = (FINDTEXTEXW *)lParam;
3852 return ME_FindText(editor, wParam, &ex->chrg, ex->lpstrText, &ex->chrgText);
3854 case EM_GETZOOM:
3855 if (!wParam || !lParam)
3856 return FALSE;
3857 *(int *)wParam = editor->nZoomNumerator;
3858 *(int *)lParam = editor->nZoomDenominator;
3859 return TRUE;
3860 case EM_SETZOOM:
3861 return ME_SetZoom(editor, wParam, lParam);
3862 case EM_CHARFROMPOS:
3864 ME_Cursor cursor;
3865 POINTL *pt = (POINTL *)lParam;
3867 cursor_from_coords(editor, pt->x, pt->y, &cursor);
3868 return ME_GetCursorOfs(&cursor);
3870 case EM_POSFROMCHAR:
3872 ME_Cursor cursor;
3873 int nCharOfs, nLength;
3874 POINTL pt = {0,0};
3876 nCharOfs = wParam;
3877 /* detect which API version we're dealing with */
3878 if (wParam >= 0x40000)
3879 nCharOfs = lParam;
3880 nLength = ME_GetTextLength(editor);
3881 nCharOfs = min(nCharOfs, nLength);
3882 nCharOfs = max(nCharOfs, 0);
3884 cursor_from_char_ofs( editor, nCharOfs, &cursor );
3885 pt.y = cursor.run->pt.y;
3886 pt.x = cursor.run->pt.x +
3887 ME_PointFromChar( editor, cursor.run, cursor.nOffset, TRUE );
3888 pt.y += cursor.para->pt.y + editor->rcFormat.top;
3889 pt.x += editor->rcFormat.left;
3891 pt.x -= editor->horz_si.nPos;
3892 pt.y -= editor->vert_si.nPos;
3894 if (wParam >= 0x40000) *(POINTL *)wParam = pt;
3896 return (wParam >= 0x40000) ? 0 : MAKELONG( pt.x, pt.y );
3898 case WM_LBUTTONDBLCLK:
3899 case WM_LBUTTONDOWN:
3901 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
3902 ITextHost_TxSetFocus(editor->texthost);
3903 ME_LButtonDown(editor, (short)LOWORD(lParam), (short)HIWORD(lParam),
3904 ME_CalculateClickCount(editor, msg, wParam, lParam));
3905 ITextHost_TxSetCapture(editor->texthost, TRUE);
3906 editor->bMouseCaptured = TRUE;
3907 link_notify( editor, msg, wParam, lParam );
3908 break;
3910 case WM_MOUSEMOVE:
3911 if (editor->bMouseCaptured)
3912 ME_MouseMove(editor, (short)LOWORD(lParam), (short)HIWORD(lParam));
3913 else
3914 link_notify( editor, msg, wParam, lParam );
3915 break;
3916 case WM_LBUTTONUP:
3917 if (editor->bMouseCaptured) {
3918 ITextHost_TxSetCapture(editor->texthost, FALSE);
3919 editor->bMouseCaptured = FALSE;
3921 if (editor->nSelectionType == stDocument)
3922 editor->nSelectionType = stPosition;
3923 else
3925 link_notify( editor, msg, wParam, lParam );
3927 break;
3928 case WM_RBUTTONUP:
3929 case WM_RBUTTONDOWN:
3930 case WM_RBUTTONDBLCLK:
3931 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
3932 link_notify( editor, msg, wParam, lParam );
3933 goto do_default;
3934 case WM_CONTEXTMENU:
3935 if (!ME_ShowContextMenu(editor, (short)LOWORD(lParam), (short)HIWORD(lParam)))
3936 goto do_default;
3937 break;
3938 case WM_SETFOCUS:
3939 editor->bHaveFocus = TRUE;
3940 create_caret(editor);
3941 update_caret(editor);
3942 ITextHost_TxNotify( editor->texthost, EN_SETFOCUS, NULL );
3943 if (!editor->bHideSelection && (editor->props & TXTBIT_HIDESELECTION))
3944 ME_InvalidateSelection( editor );
3945 return 0;
3946 case WM_KILLFOCUS:
3947 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
3948 editor->bHaveFocus = FALSE;
3949 editor->wheel_remain = 0;
3950 hide_caret(editor);
3951 DestroyCaret();
3952 ITextHost_TxNotify( editor->texthost, EN_KILLFOCUS, NULL );
3953 if (!editor->bHideSelection && (editor->props & TXTBIT_HIDESELECTION))
3954 ME_InvalidateSelection( editor );
3955 return 0;
3956 case WM_COMMAND:
3957 TRACE("editor wnd command = %d\n", LOWORD(wParam));
3958 return 0;
3959 case WM_KEYDOWN:
3960 if (ME_KeyDown(editor, LOWORD(wParam)))
3961 return 0;
3962 goto do_default;
3963 case WM_CHAR:
3964 return handle_wm_char( editor, wParam, lParam );
3965 case WM_UNICHAR:
3966 if (wParam == UNICODE_NOCHAR) return TRUE;
3967 if (wParam <= 0x000fffff)
3969 if (wParam > 0xffff) /* convert to surrogates */
3971 wParam -= 0x10000;
3972 handle_wm_char( editor, (wParam >> 10) + 0xd800, 0 );
3973 handle_wm_char( editor, (wParam & 0x03ff) + 0xdc00, 0 );
3975 else
3976 handle_wm_char( editor, wParam, 0 );
3978 return 0;
3979 case EM_STOPGROUPTYPING:
3980 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
3981 return 0;
3982 case WM_HSCROLL:
3984 const int scrollUnit = 7;
3986 switch(LOWORD(wParam))
3988 case SB_LEFT:
3989 scroll_abs( editor, 0, 0, TRUE );
3990 break;
3991 case SB_RIGHT:
3992 scroll_abs( editor, editor->horz_si.nMax - (int)editor->horz_si.nPage,
3993 editor->vert_si.nMax - (int)editor->vert_si.nPage, TRUE );
3994 break;
3995 case SB_LINELEFT:
3996 ME_ScrollLeft(editor, scrollUnit);
3997 break;
3998 case SB_LINERIGHT:
3999 ME_ScrollRight(editor, scrollUnit);
4000 break;
4001 case SB_PAGELEFT:
4002 ME_ScrollLeft(editor, editor->sizeWindow.cx);
4003 break;
4004 case SB_PAGERIGHT:
4005 ME_ScrollRight(editor, editor->sizeWindow.cx);
4006 break;
4007 case SB_THUMBTRACK:
4008 case SB_THUMBPOSITION:
4010 int pos = HIWORD(wParam);
4011 if (editor->horz_si.nMax > 0xffff)
4012 pos = MulDiv(pos, editor->horz_si.nMax, 0xffff);
4013 scroll_h_abs( editor, pos, FALSE );
4014 break;
4017 break;
4019 case EM_SCROLL: /* fall through */
4020 case WM_VSCROLL:
4022 int origNPos;
4023 int lineHeight = get_default_line_height( editor );
4025 origNPos = editor->vert_si.nPos;
4027 switch(LOWORD(wParam))
4029 case SB_TOP:
4030 scroll_abs( editor, 0, 0, TRUE );
4031 break;
4032 case SB_BOTTOM:
4033 scroll_abs( editor, editor->horz_si.nMax - (int)editor->horz_si.nPage,
4034 editor->vert_si.nMax - (int)editor->vert_si.nPage, TRUE );
4035 break;
4036 case SB_LINEUP:
4037 ME_ScrollUp(editor,lineHeight);
4038 break;
4039 case SB_LINEDOWN:
4040 ME_ScrollDown(editor,lineHeight);
4041 break;
4042 case SB_PAGEUP:
4043 ME_ScrollUp(editor,editor->sizeWindow.cy);
4044 break;
4045 case SB_PAGEDOWN:
4046 ME_ScrollDown(editor,editor->sizeWindow.cy);
4047 break;
4048 case SB_THUMBTRACK:
4049 case SB_THUMBPOSITION:
4051 int pos = HIWORD(wParam);
4052 if (editor->vert_si.nMax > 0xffff)
4053 pos = MulDiv(pos, editor->vert_si.nMax, 0xffff);
4054 scroll_v_abs( editor, pos, FALSE );
4055 break;
4058 if (msg == EM_SCROLL)
4059 return 0x00010000 | (((editor->vert_si.nPos - origNPos)/lineHeight) & 0xffff);
4060 break;
4062 case WM_MOUSEWHEEL:
4064 int delta = GET_WHEEL_DELTA_WPARAM( wParam );
4065 BOOL ctrl_is_down = GetKeyState( VK_CONTROL ) & 0x8000;
4067 /* if scrolling changes direction, ignore left overs */
4068 if ((delta < 0 && editor->wheel_remain < 0) ||
4069 (delta > 0 && editor->wheel_remain > 0))
4070 editor->wheel_remain += delta;
4071 else
4072 editor->wheel_remain = delta;
4074 if (editor->wheel_remain)
4076 if (ctrl_is_down) {
4077 int numerator;
4078 if (!editor->nZoomNumerator || !editor->nZoomDenominator)
4080 numerator = 100;
4081 } else {
4082 numerator = editor->nZoomNumerator * 100 / editor->nZoomDenominator;
4084 numerator += calc_wheel_change( &editor->wheel_remain, 10 );
4085 if (numerator >= 10 && numerator <= 500)
4086 ME_SetZoom(editor, numerator, 100);
4087 } else {
4088 UINT max_lines = 3;
4089 int lines = 0;
4091 SystemParametersInfoW( SPI_GETWHEELSCROLLLINES, 0, &max_lines, 0 );
4092 if (max_lines)
4093 lines = calc_wheel_change( &editor->wheel_remain, (int)max_lines );
4094 if (lines)
4095 ME_ScrollDown( editor, -lines * get_default_line_height( editor ) );
4098 break;
4100 case EM_REQUESTRESIZE:
4101 ME_SendRequestResize(editor, TRUE);
4102 return 0;
4103 /* IME messages to make richedit controls IME aware */
4104 case WM_IME_SETCONTEXT:
4105 case WM_IME_CONTROL:
4106 case WM_IME_SELECT:
4107 case WM_IME_COMPOSITIONFULL:
4108 return 0;
4109 case WM_IME_STARTCOMPOSITION:
4111 ME_DeleteSelection(editor);
4112 editor->imeStartIndex = ME_GetCursorOfs(&editor->pCursors[0]);
4113 ME_CommitUndo(editor);
4114 ME_UpdateRepaint(editor, FALSE);
4115 return 0;
4117 case WM_IME_COMPOSITION:
4119 HIMC hIMC;
4121 ME_Style *style = style_get_insert_style( editor, editor->pCursors );
4122 hIMC = ITextHost_TxImmGetContext(editor->texthost);
4123 ME_DeleteSelection(editor);
4124 ME_SaveTempStyle(editor, style);
4125 if (lParam & (GCS_RESULTSTR|GCS_COMPSTR))
4127 LPWSTR lpCompStr = NULL;
4128 DWORD dwBufLen;
4129 DWORD dwIndex = lParam & GCS_RESULTSTR;
4130 if (!dwIndex)
4131 dwIndex = GCS_COMPSTR;
4133 dwBufLen = ImmGetCompositionStringW(hIMC, dwIndex, NULL, 0);
4134 lpCompStr = HeapAlloc(GetProcessHeap(),0,dwBufLen + sizeof(WCHAR));
4135 ImmGetCompositionStringW(hIMC, dwIndex, lpCompStr, dwBufLen);
4136 lpCompStr[dwBufLen/sizeof(WCHAR)] = 0;
4137 ME_InsertTextFromCursor(editor,0,lpCompStr,dwBufLen/sizeof(WCHAR),style);
4138 HeapFree(GetProcessHeap(), 0, lpCompStr);
4140 if (dwIndex == GCS_COMPSTR)
4141 set_selection_cursors(editor,editor->imeStartIndex,
4142 editor->imeStartIndex + dwBufLen/sizeof(WCHAR));
4143 else
4144 editor->imeStartIndex = ME_GetCursorOfs(&editor->pCursors[0]);
4146 ME_ReleaseStyle(style);
4147 ME_CommitUndo(editor);
4148 ME_UpdateRepaint(editor, FALSE);
4149 return 0;
4151 case WM_IME_ENDCOMPOSITION:
4153 ME_DeleteSelection(editor);
4154 editor->imeStartIndex=-1;
4155 return 0;
4157 case EM_GETOLEINTERFACE:
4158 IRichEditOle_AddRef( editor->richole );
4159 *(IRichEditOle **)lParam = editor->richole;
4160 return 1;
4162 case EM_SETOLECALLBACK:
4163 if(editor->lpOleCallback)
4164 IRichEditOleCallback_Release(editor->lpOleCallback);
4165 editor->lpOleCallback = (IRichEditOleCallback*)lParam;
4166 if(editor->lpOleCallback)
4167 IRichEditOleCallback_AddRef(editor->lpOleCallback);
4168 return TRUE;
4169 case EM_GETWORDBREAKPROC:
4170 return (LRESULT)editor->pfnWordBreak;
4171 case EM_SETWORDBREAKPROC:
4173 EDITWORDBREAKPROCW pfnOld = editor->pfnWordBreak;
4175 editor->pfnWordBreak = (EDITWORDBREAKPROCW)lParam;
4176 return (LRESULT)pfnOld;
4178 case EM_GETTEXTMODE:
4179 return editor->mode;
4180 case EM_SETTEXTMODE:
4182 int mask = 0;
4183 int changes = 0;
4185 if (ME_GetTextLength(editor) ||
4186 !list_empty( &editor->undo_stack ) || !list_empty( &editor->redo_stack ))
4187 return E_UNEXPECTED;
4189 /* Check for mutually exclusive flags in adjacent bits of wParam */
4190 if ((wParam & (TM_RICHTEXT | TM_MULTILEVELUNDO | TM_MULTICODEPAGE)) &
4191 (wParam & (TM_PLAINTEXT | TM_SINGLELEVELUNDO | TM_SINGLECODEPAGE)) << 1)
4192 return E_INVALIDARG;
4194 if (wParam & (TM_RICHTEXT | TM_PLAINTEXT))
4196 mask |= TM_RICHTEXT | TM_PLAINTEXT;
4197 changes |= wParam & (TM_RICHTEXT | TM_PLAINTEXT);
4198 if (wParam & TM_PLAINTEXT) {
4199 /* Clear selection since it should be possible to select the
4200 * end of text run for rich text */
4201 ME_InvalidateSelection(editor);
4202 ME_SetCursorToStart(editor, &editor->pCursors[0]);
4203 editor->pCursors[1] = editor->pCursors[0];
4204 /* plain text can only have the default style. */
4205 ME_ClearTempStyle(editor);
4206 ME_AddRefStyle(editor->pBuffer->pDefaultStyle);
4207 ME_ReleaseStyle( editor->pCursors[0].run->style );
4208 editor->pCursors[0].run->style = editor->pBuffer->pDefaultStyle;
4211 /* FIXME: Currently no support for undo level and code page options */
4212 editor->mode = (editor->mode & ~mask) | changes;
4213 return 0;
4215 case EM_SETTARGETDEVICE:
4216 if (wParam == 0)
4218 BOOL new = (lParam == 0 && (editor->props & TXTBIT_MULTILINE));
4219 if (editor->nAvailWidth || editor->bWordWrap != new)
4221 editor->bWordWrap = new;
4222 editor->nAvailWidth = 0; /* wrap to client area */
4223 ME_RewrapRepaint(editor);
4225 } else {
4226 int width = max(0, lParam);
4227 if ((editor->props & TXTBIT_MULTILINE) &&
4228 (!editor->bWordWrap || editor->nAvailWidth != width))
4230 editor->nAvailWidth = width;
4231 editor->bWordWrap = TRUE;
4232 ME_RewrapRepaint(editor);
4234 FIXME("EM_SETTARGETDEVICE doesn't use non-NULL target devices\n");
4236 return TRUE;
4237 default:
4238 do_default:
4239 *phresult = S_FALSE;
4240 break;
4242 return 0L;
4245 /* Fill buffer with srcChars unicode characters from the start cursor.
4247 * buffer: destination buffer
4248 * buflen: length of buffer in characters excluding the NULL terminator.
4249 * start: start of editor text to copy into buffer.
4250 * srcChars: Number of characters to use from the editor text.
4251 * bCRLF: if true, replaces all end of lines with \r\n pairs.
4253 * returns the number of characters written excluding the NULL terminator.
4255 * The written text is always NULL terminated.
4257 int ME_GetTextW(ME_TextEditor *editor, WCHAR *buffer, int buflen,
4258 const ME_Cursor *start, int srcChars, BOOL bCRLF,
4259 BOOL bEOP)
4261 ME_Run *run, *next_run;
4262 const WCHAR *pStart = buffer;
4263 const WCHAR *str;
4264 int nLen;
4266 /* bCRLF flag is only honored in 2.0 and up. 1.0 must always return text verbatim */
4267 if (editor->bEmulateVersion10) bCRLF = FALSE;
4269 run = start->run;
4270 next_run = run_next_all_paras( run );
4272 nLen = run->len - start->nOffset;
4273 str = get_text( run, start->nOffset );
4275 while (srcChars && buflen && next_run)
4277 if (bCRLF && run->nFlags & MERF_ENDPARA && ~run->nFlags & MERF_ENDCELL)
4279 if (buflen == 1) break;
4280 /* FIXME: native fails to reduce srcChars here for WM_GETTEXT or
4281 * EM_GETTEXTEX, however, this is done for copying text which
4282 * also uses this function. */
4283 srcChars -= min(nLen, srcChars);
4284 nLen = 2;
4285 str = L"\r\n";
4287 else
4289 nLen = min(nLen, srcChars);
4290 srcChars -= nLen;
4293 nLen = min(nLen, buflen);
4294 buflen -= nLen;
4296 CopyMemory(buffer, str, sizeof(WCHAR) * nLen);
4298 buffer += nLen;
4300 run = next_run;
4301 next_run = run_next_all_paras( run );
4303 nLen = run->len;
4304 str = get_text( run, 0 );
4306 /* append '\r' to the last paragraph. */
4307 if (run == para_end_run( para_prev( editor_end_para( editor ) ) ) && bEOP)
4309 *buffer = '\r';
4310 buffer ++;
4312 *buffer = 0;
4313 return buffer - pStart;
4316 static int __cdecl wchar_comp( const void *key, const void *elem )
4318 return *(const WCHAR *)key - *(const WCHAR *)elem;
4321 /* neutral characters end the url if the next non-neutral character is a space character,
4322 otherwise they are included in the url. */
4323 static BOOL isurlneutral( WCHAR c )
4325 /* NB this list is sorted */
4326 static const WCHAR neutral_chars[] = L"!\"'(),-.:;<>?[]{}";
4328 /* Some shortcuts */
4329 if (isalnum( c )) return FALSE;
4330 if (c > L'}') return FALSE;
4332 return !!bsearch( &c, neutral_chars, ARRAY_SIZE( neutral_chars ) - 1, sizeof(c), wchar_comp );
4336 * This proc takes a selection, and scans it forward in order to select the span
4337 * of a possible URL candidate. A possible URL candidate must start with isalnum
4338 * or one of the following special characters: *|/\+%#@ and must consist entirely
4339 * of the characters allowed to start the URL, plus : (colon) which may occur
4340 * at most once, and not at either end.
4342 static BOOL ME_FindNextURLCandidate(ME_TextEditor *editor,
4343 const ME_Cursor *start,
4344 int nChars,
4345 ME_Cursor *candidate_min,
4346 ME_Cursor *candidate_max)
4348 ME_Cursor cursor = *start, neutral_end, space_end;
4349 BOOL candidateStarted = FALSE, quoted = FALSE;
4350 WCHAR c;
4352 while (nChars > 0)
4354 WCHAR *str = get_text( cursor.run, 0 );
4355 int run_len = cursor.run->len;
4357 nChars -= run_len - cursor.nOffset;
4359 /* Find start of candidate */
4360 if (!candidateStarted)
4362 while (cursor.nOffset < run_len)
4364 c = str[cursor.nOffset];
4365 if (!iswspace( c ) && !isurlneutral( c ))
4367 *candidate_min = cursor;
4368 candidateStarted = TRUE;
4369 neutral_end.para = NULL;
4370 space_end.para = NULL;
4371 cursor.nOffset++;
4372 break;
4374 quoted = (c == '<');
4375 cursor.nOffset++;
4379 /* Find end of candidate */
4380 if (candidateStarted)
4382 while (cursor.nOffset < run_len)
4384 c = str[cursor.nOffset];
4385 if (iswspace( c ))
4387 if (quoted && c != '\r')
4389 if (!space_end.para)
4391 if (neutral_end.para)
4392 space_end = neutral_end;
4393 else
4394 space_end = cursor;
4397 else
4398 goto done;
4400 else if (isurlneutral( c ))
4402 if (quoted && c == '>')
4404 neutral_end.para = NULL;
4405 space_end.para = NULL;
4406 goto done;
4408 if (!neutral_end.para)
4409 neutral_end = cursor;
4411 else
4412 neutral_end.para = NULL;
4414 cursor.nOffset++;
4418 cursor.nOffset = 0;
4419 if (!cursor_next_run( &cursor, TRUE ))
4420 goto done;
4423 done:
4424 if (candidateStarted)
4426 if (space_end.para)
4427 *candidate_max = space_end;
4428 else if (neutral_end.para)
4429 *candidate_max = neutral_end;
4430 else
4431 *candidate_max = cursor;
4432 return TRUE;
4434 *candidate_max = *candidate_min = cursor;
4435 return FALSE;
4439 * This proc evaluates the selection and returns TRUE if it can be considered an URL
4441 static BOOL ME_IsCandidateAnURL(ME_TextEditor *editor, const ME_Cursor *start, int nChars)
4443 #define MAX_PREFIX_LEN 9
4444 #define X(str) str, ARRAY_SIZE(str) - 1
4445 struct prefix_s {
4446 const WCHAR text[MAX_PREFIX_LEN];
4447 int length;
4448 }prefixes[] = {
4449 {X(L"prospero:")},
4450 {X(L"telnet:")},
4451 {X(L"gopher:")},
4452 {X(L"mailto:")},
4453 {X(L"https:")},
4454 {X(L"file:")},
4455 {X(L"news:")},
4456 {X(L"wais:")},
4457 {X(L"nntp:")},
4458 {X(L"http:")},
4459 {X(L"www.")},
4460 {X(L"ftp:")},
4462 #undef X
4463 WCHAR bufferW[MAX_PREFIX_LEN + 1];
4464 unsigned int i;
4466 ME_GetTextW(editor, bufferW, MAX_PREFIX_LEN, start, nChars, FALSE, FALSE);
4467 for (i = 0; i < ARRAY_SIZE(prefixes); i++)
4469 if (nChars < prefixes[i].length) continue;
4470 if (!memcmp(prefixes[i].text, bufferW, prefixes[i].length * sizeof(WCHAR)))
4471 return TRUE;
4473 return FALSE;
4474 #undef MAX_PREFIX_LEN
4478 * This proc walks through the indicated selection and evaluates whether each
4479 * section identified by ME_FindNextURLCandidate and in-between sections have
4480 * their proper CFE_LINK attributes set or unset. If the CFE_LINK attribute is
4481 * not what it is supposed to be, this proc sets or unsets it as appropriate.
4483 * Since this function can cause runs to be split, do not depend on the value
4484 * of the start cursor at the end of the function.
4486 * nChars may be set to INT_MAX to update to the end of the text.
4488 * Returns TRUE if at least one section was modified.
4490 static BOOL ME_UpdateLinkAttribute(ME_TextEditor *editor, ME_Cursor *start, int nChars)
4492 BOOL modified = FALSE;
4493 ME_Cursor startCur = *start;
4495 if (!editor->AutoURLDetect_bEnable) return FALSE;
4499 CHARFORMAT2W link;
4500 ME_Cursor candidateStart, candidateEnd;
4502 if (ME_FindNextURLCandidate(editor, &startCur, nChars,
4503 &candidateStart, &candidateEnd))
4505 /* Section before candidate is not an URL */
4506 int cMin = ME_GetCursorOfs(&candidateStart);
4507 int cMax = ME_GetCursorOfs(&candidateEnd);
4509 if (!ME_IsCandidateAnURL(editor, &candidateStart, cMax - cMin))
4510 candidateStart = candidateEnd;
4511 nChars -= cMax - ME_GetCursorOfs(&startCur);
4513 else
4515 /* No more candidates until end of selection */
4516 nChars = 0;
4519 if (startCur.run != candidateStart.run ||
4520 startCur.nOffset != candidateStart.nOffset)
4522 /* CFE_LINK effect should be consistently unset */
4523 link.cbSize = sizeof(link);
4524 ME_GetCharFormat(editor, &startCur, &candidateStart, &link);
4525 if (!(link.dwMask & CFM_LINK) || (link.dwEffects & CFE_LINK))
4527 /* CFE_LINK must be unset from this range */
4528 memset(&link, 0, sizeof(CHARFORMAT2W));
4529 link.cbSize = sizeof(link);
4530 link.dwMask = CFM_LINK;
4531 link.dwEffects = 0;
4532 ME_SetCharFormat(editor, &startCur, &candidateStart, &link);
4533 /* Update candidateEnd since setting character formats may split
4534 * runs, which can cause a cursor to be at an invalid offset within
4535 * a split run. */
4536 while (candidateEnd.nOffset >= candidateEnd.run->len)
4538 candidateEnd.nOffset -= candidateEnd.run->len;
4539 candidateEnd.run = run_next_all_paras( candidateEnd.run );
4541 modified = TRUE;
4544 if (candidateStart.run != candidateEnd.run ||
4545 candidateStart.nOffset != candidateEnd.nOffset)
4547 /* CFE_LINK effect should be consistently set */
4548 link.cbSize = sizeof(link);
4549 ME_GetCharFormat(editor, &candidateStart, &candidateEnd, &link);
4550 if (!(link.dwMask & CFM_LINK) || !(link.dwEffects & CFE_LINK))
4552 /* CFE_LINK must be set on this range */
4553 memset(&link, 0, sizeof(CHARFORMAT2W));
4554 link.cbSize = sizeof(link);
4555 link.dwMask = CFM_LINK;
4556 link.dwEffects = CFE_LINK;
4557 ME_SetCharFormat(editor, &candidateStart, &candidateEnd, &link);
4558 modified = TRUE;
4561 startCur = candidateEnd;
4562 } while (nChars > 0);
4563 return modified;