riched20: Merge the richole object with the text services object.
[wine.git] / dlls / riched20 / editor.c
blobd4aadc325f9cced1a4b5dab64a74462ceacf9a15
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;
249 HANDLE me_heap = NULL;
251 static ME_TextBuffer *ME_MakeText(void) {
252 ME_TextBuffer *buf = heap_alloc(sizeof(*buf));
253 ME_DisplayItem *p1 = ME_MakeDI(diTextStart);
254 ME_DisplayItem *p2 = ME_MakeDI(diTextEnd);
256 p1->prev = NULL;
257 p1->next = p2;
258 p2->prev = p1;
259 p2->next = NULL;
260 p1->member.para.next_para = p2;
261 p2->member.para.prev_para = p1;
262 p2->member.para.nCharOfs = 0;
264 buf->pFirst = p1;
265 buf->pLast = p2;
266 buf->pCharStyle = NULL;
268 return buf;
271 ME_Paragraph *editor_first_para( ME_TextEditor *editor )
273 return para_next( &editor->pBuffer->pFirst->member.para );
276 /* Note, returns the diTextEnd sentinel paragraph */
277 ME_Paragraph *editor_end_para( ME_TextEditor *editor )
279 return &editor->pBuffer->pLast->member.para;
282 static BOOL editor_beep( ME_TextEditor *editor, UINT type )
284 return editor->props & TXTBIT_ALLOWBEEP && MessageBeep( type );
287 static LRESULT ME_StreamInText(ME_TextEditor *editor, DWORD dwFormat, ME_InStream *stream, ME_Style *style)
289 WCHAR *pText;
290 LRESULT total_bytes_read = 0;
291 BOOL is_read = FALSE;
292 DWORD cp = CP_ACP, copy = 0;
293 char conv_buf[4 + STREAMIN_BUFFER_SIZE]; /* up to 4 additional UTF-8 bytes */
295 static const char bom_utf8[] = {0xEF, 0xBB, 0xBF};
297 TRACE("%08x %p\n", dwFormat, stream);
299 do {
300 LONG nWideChars = 0;
301 WCHAR wszText[STREAMIN_BUFFER_SIZE+1];
303 if (!stream->dwSize)
305 ME_StreamInFill(stream);
306 if (stream->editstream->dwError)
307 break;
308 if (!stream->dwSize)
309 break;
310 total_bytes_read += stream->dwSize;
313 if (!(dwFormat & SF_UNICODE))
315 char * buf = stream->buffer;
316 DWORD size = stream->dwSize, end;
318 if (!is_read)
320 is_read = TRUE;
321 if (stream->dwSize >= 3 && !memcmp(stream->buffer, bom_utf8, 3))
323 cp = CP_UTF8;
324 buf += 3;
325 size -= 3;
329 if (cp == CP_UTF8)
331 if (copy)
333 memcpy(conv_buf + copy, buf, size);
334 buf = conv_buf;
335 size += copy;
337 end = size;
338 while ((buf[end-1] & 0xC0) == 0x80)
340 --end;
341 --total_bytes_read; /* strange, but seems to match windows */
343 if (buf[end-1] & 0x80)
345 DWORD need = 0;
346 if ((buf[end-1] & 0xE0) == 0xC0)
347 need = 1;
348 if ((buf[end-1] & 0xF0) == 0xE0)
349 need = 2;
350 if ((buf[end-1] & 0xF8) == 0xF0)
351 need = 3;
353 if (size - end >= need)
355 /* we have enough bytes for this sequence */
356 end = size;
358 else
360 /* need more bytes, so don't transcode this sequence */
361 --end;
365 else
366 end = size;
368 nWideChars = MultiByteToWideChar(cp, 0, buf, end, wszText, STREAMIN_BUFFER_SIZE);
369 pText = wszText;
371 if (cp == CP_UTF8)
373 if (end != size)
375 memcpy(conv_buf, buf + end, size - end);
376 copy = size - end;
380 else
382 nWideChars = stream->dwSize >> 1;
383 pText = (WCHAR *)stream->buffer;
386 ME_InsertTextFromCursor(editor, 0, pText, nWideChars, style);
387 if (stream->dwSize == 0)
388 break;
389 stream->dwSize = 0;
390 } while(1);
391 return total_bytes_read;
394 static void ME_ApplyBorderProperties(RTF_Info *info,
395 ME_BorderRect *borderRect,
396 RTFBorder *borderDef)
398 int i, colorNum;
399 ME_Border *pBorders[] = {&borderRect->top,
400 &borderRect->left,
401 &borderRect->bottom,
402 &borderRect->right};
403 for (i = 0; i < 4; i++)
405 RTFColor *colorDef = info->colorList;
406 pBorders[i]->width = borderDef[i].width;
407 colorNum = borderDef[i].color;
408 while (colorDef && colorDef->rtfCNum != colorNum)
409 colorDef = colorDef->rtfNextColor;
410 if (colorDef)
411 pBorders[i]->colorRef = RGB(
412 colorDef->rtfCRed >= 0 ? colorDef->rtfCRed : 0,
413 colorDef->rtfCGreen >= 0 ? colorDef->rtfCGreen : 0,
414 colorDef->rtfCBlue >= 0 ? colorDef->rtfCBlue : 0);
415 else
416 pBorders[i]->colorRef = RGB(0, 0, 0);
420 void ME_RTFCharAttrHook(RTF_Info *info)
422 CHARFORMAT2W fmt;
423 fmt.cbSize = sizeof(fmt);
424 fmt.dwMask = 0;
425 fmt.dwEffects = 0;
427 switch(info->rtfMinor)
429 case rtfPlain:
430 /* FIXME add more flags once they're implemented */
431 fmt.dwMask = CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE | CFM_UNDERLINETYPE | CFM_STRIKEOUT |
432 CFM_COLOR | CFM_BACKCOLOR | CFM_SIZE | CFM_WEIGHT;
433 fmt.dwEffects = CFE_AUTOCOLOR | CFE_AUTOBACKCOLOR;
434 fmt.yHeight = 12*20; /* 12pt */
435 fmt.wWeight = FW_NORMAL;
436 fmt.bUnderlineType = CFU_UNDERLINE;
437 break;
438 case rtfBold:
439 fmt.dwMask = CFM_BOLD | CFM_WEIGHT;
440 fmt.dwEffects = info->rtfParam ? CFE_BOLD : 0;
441 fmt.wWeight = info->rtfParam ? FW_BOLD : FW_NORMAL;
442 break;
443 case rtfItalic:
444 fmt.dwMask = CFM_ITALIC;
445 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
446 break;
447 case rtfUnderline:
448 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
449 fmt.bUnderlineType = CFU_UNDERLINE;
450 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
451 break;
452 case rtfDotUnderline:
453 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
454 fmt.bUnderlineType = CFU_UNDERLINEDOTTED;
455 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
456 break;
457 case rtfDbUnderline:
458 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
459 fmt.bUnderlineType = CFU_UNDERLINEDOUBLE;
460 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
461 break;
462 case rtfWordUnderline:
463 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
464 fmt.bUnderlineType = CFU_UNDERLINEWORD;
465 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
466 break;
467 case rtfNoUnderline:
468 fmt.dwMask = CFM_UNDERLINE;
469 fmt.dwEffects = 0;
470 break;
471 case rtfStrikeThru:
472 fmt.dwMask = CFM_STRIKEOUT;
473 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
474 break;
475 case rtfSubScript:
476 case rtfSuperScript:
477 case rtfSubScrShrink:
478 case rtfSuperScrShrink:
479 case rtfNoSuperSub:
480 fmt.dwMask = CFM_SUBSCRIPT|CFM_SUPERSCRIPT;
481 if (info->rtfMinor == rtfSubScrShrink) fmt.dwEffects = CFE_SUBSCRIPT;
482 if (info->rtfMinor == rtfSuperScrShrink) fmt.dwEffects = CFE_SUPERSCRIPT;
483 if (info->rtfMinor == rtfNoSuperSub) fmt.dwEffects = 0;
484 break;
485 case rtfInvisible:
486 fmt.dwMask = CFM_HIDDEN;
487 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
488 break;
489 case rtfBackColor:
490 fmt.dwMask = CFM_BACKCOLOR;
491 fmt.dwEffects = 0;
492 if (info->rtfParam == 0)
493 fmt.dwEffects = CFE_AUTOBACKCOLOR;
494 else if (info->rtfParam != rtfNoParam)
496 RTFColor *c = RTFGetColor(info, info->rtfParam);
497 if (c && c->rtfCBlue >= 0)
498 fmt.crBackColor = (c->rtfCBlue<<16)|(c->rtfCGreen<<8)|(c->rtfCRed);
499 else
500 fmt.dwEffects = CFE_AUTOBACKCOLOR;
502 break;
503 case rtfForeColor:
504 fmt.dwMask = CFM_COLOR;
505 fmt.dwEffects = 0;
506 if (info->rtfParam == 0)
507 fmt.dwEffects = CFE_AUTOCOLOR;
508 else if (info->rtfParam != rtfNoParam)
510 RTFColor *c = RTFGetColor(info, info->rtfParam);
511 if (c && c->rtfCBlue >= 0)
512 fmt.crTextColor = (c->rtfCBlue<<16)|(c->rtfCGreen<<8)|(c->rtfCRed);
513 else {
514 fmt.dwEffects = CFE_AUTOCOLOR;
517 break;
518 case rtfFontNum:
519 if (info->rtfParam != rtfNoParam)
521 RTFFont *f = RTFGetFont(info, info->rtfParam);
522 if (f)
524 MultiByteToWideChar(CP_ACP, 0, f->rtfFName, -1, fmt.szFaceName, ARRAY_SIZE(fmt.szFaceName));
525 fmt.szFaceName[ARRAY_SIZE(fmt.szFaceName)-1] = '\0';
526 fmt.bCharSet = f->rtfFCharSet;
527 fmt.dwMask = CFM_FACE | CFM_CHARSET;
528 fmt.bPitchAndFamily = f->rtfFPitch | (f->rtfFFamily << 4);
531 break;
532 case rtfFontSize:
533 fmt.dwMask = CFM_SIZE;
534 if (info->rtfParam != rtfNoParam)
535 fmt.yHeight = info->rtfParam*10;
536 break;
538 if (fmt.dwMask) {
539 ME_Style *style2;
540 RTFFlushOutputBuffer(info);
541 /* FIXME too slow ? how come ? */
542 style2 = ME_ApplyStyle(info->editor, info->style, &fmt);
543 ME_ReleaseStyle(info->style);
544 info->style = style2;
545 info->styleChanged = TRUE;
549 /* FIXME this function doesn't get any information about context of the RTF tag, which is very bad,
550 the same tags mean different things in different contexts */
551 void ME_RTFParAttrHook(RTF_Info *info)
553 switch(info->rtfMinor)
555 case rtfParDef: /* restores default paragraph attributes */
556 if (!info->editor->bEmulateVersion10) /* v4.1 */
557 info->borderType = RTFBorderParaLeft;
558 else /* v1.0 - 3.0 */
559 info->borderType = RTFBorderParaTop;
560 info->fmt.dwMask = PFM_ALIGNMENT | PFM_BORDER | PFM_LINESPACING | PFM_TABSTOPS |
561 PFM_OFFSET | PFM_RIGHTINDENT | PFM_SPACEAFTER | PFM_SPACEBEFORE |
562 PFM_STARTINDENT | PFM_RTLPARA | PFM_NUMBERING | PFM_NUMBERINGSTART |
563 PFM_NUMBERINGSTYLE | PFM_NUMBERINGTAB;
564 /* TODO: shading */
565 info->fmt.wAlignment = PFA_LEFT;
566 info->fmt.cTabCount = 0;
567 info->fmt.dxOffset = info->fmt.dxStartIndent = info->fmt.dxRightIndent = 0;
568 info->fmt.wBorderWidth = info->fmt.wBorders = 0;
569 info->fmt.wBorderSpace = 0;
570 info->fmt.bLineSpacingRule = 0;
571 info->fmt.dySpaceBefore = info->fmt.dySpaceAfter = 0;
572 info->fmt.dyLineSpacing = 0;
573 info->fmt.wEffects &= ~PFE_RTLPARA;
574 info->fmt.wNumbering = 0;
575 info->fmt.wNumberingStart = 0;
576 info->fmt.wNumberingStyle = 0;
577 info->fmt.wNumberingTab = 0;
579 if (!info->editor->bEmulateVersion10) /* v4.1 */
581 if (info->tableDef && info->tableDef->row_start &&
582 info->tableDef->row_start->nFlags & MEPF_ROWEND)
584 ME_Cursor cursor;
585 ME_Paragraph *para;
586 /* We are just after a table row. */
587 RTFFlushOutputBuffer(info);
588 cursor = info->editor->pCursors[0];
589 para = cursor.para;
590 if (para == para_next( info->tableDef->row_start )
591 && !cursor.nOffset && !cursor.run->nCharOfs)
593 /* Since the table row end, no text has been inserted, and the \intbl
594 * control word has not be used. We can confirm that we are not in a
595 * table anymore.
597 info->tableDef->row_start = NULL;
598 info->canInheritInTbl = FALSE;
602 else /* v1.0 - v3.0 */
604 info->fmt.dwMask |= PFM_TABLE;
605 info->fmt.wEffects &= ~PFE_TABLE;
607 break;
608 case rtfNestLevel:
609 if (!info->editor->bEmulateVersion10) /* v4.1 */
611 while (info->rtfParam > info->nestingLevel)
613 RTFTable *tableDef = heap_alloc_zero(sizeof(*tableDef));
614 tableDef->parent = info->tableDef;
615 info->tableDef = tableDef;
617 RTFFlushOutputBuffer(info);
618 if (tableDef->row_start && tableDef->row_start->nFlags & MEPF_ROWEND)
620 ME_Paragraph *para = para_next( tableDef->row_start );
621 tableDef->row_start = table_insert_row_start_at_para( info->editor, para );
623 else
625 ME_Cursor cursor;
626 cursor = info->editor->pCursors[0];
627 if (cursor.nOffset || cursor.run->nCharOfs)
628 ME_InsertTextFromCursor(info->editor, 0, L"\r", 1, info->style);
629 tableDef->row_start = table_insert_row_start( info->editor, info->editor->pCursors );
632 info->nestingLevel++;
634 info->canInheritInTbl = FALSE;
636 break;
637 case rtfInTable:
639 if (!info->editor->bEmulateVersion10) /* v4.1 */
641 if (info->nestingLevel < 1)
643 RTFTable *tableDef;
644 ME_Paragraph *para;
646 if (!info->tableDef)
647 info->tableDef = heap_alloc_zero(sizeof(*info->tableDef));
648 tableDef = info->tableDef;
649 RTFFlushOutputBuffer(info);
650 if (tableDef->row_start && tableDef->row_start->nFlags & MEPF_ROWEND)
651 para = para_next( tableDef->row_start );
652 else
653 para = info->editor->pCursors[0].para;
655 tableDef->row_start = table_insert_row_start_at_para( info->editor, para );
657 info->nestingLevel = 1;
658 info->canInheritInTbl = TRUE;
660 return;
661 } else { /* v1.0 - v3.0 */
662 info->fmt.dwMask |= PFM_TABLE;
663 info->fmt.wEffects |= PFE_TABLE;
665 break;
667 case rtfFirstIndent:
668 case rtfLeftIndent:
669 if ((info->fmt.dwMask & (PFM_STARTINDENT | PFM_OFFSET)) != (PFM_STARTINDENT | PFM_OFFSET))
671 PARAFORMAT2 fmt;
672 fmt.cbSize = sizeof(fmt);
673 editor_get_selection_para_fmt( info->editor, &fmt );
674 info->fmt.dwMask |= PFM_STARTINDENT | PFM_OFFSET;
675 info->fmt.dxStartIndent = fmt.dxStartIndent;
676 info->fmt.dxOffset = fmt.dxOffset;
678 if (info->rtfMinor == rtfFirstIndent)
680 info->fmt.dxStartIndent += info->fmt.dxOffset + info->rtfParam;
681 info->fmt.dxOffset = -info->rtfParam;
683 else
684 info->fmt.dxStartIndent = info->rtfParam - info->fmt.dxOffset;
685 break;
686 case rtfRightIndent:
687 info->fmt.dwMask |= PFM_RIGHTINDENT;
688 info->fmt.dxRightIndent = info->rtfParam;
689 break;
690 case rtfQuadLeft:
691 case rtfQuadJust:
692 info->fmt.dwMask |= PFM_ALIGNMENT;
693 info->fmt.wAlignment = PFA_LEFT;
694 break;
695 case rtfQuadRight:
696 info->fmt.dwMask |= PFM_ALIGNMENT;
697 info->fmt.wAlignment = PFA_RIGHT;
698 break;
699 case rtfQuadCenter:
700 info->fmt.dwMask |= PFM_ALIGNMENT;
701 info->fmt.wAlignment = PFA_CENTER;
702 break;
703 case rtfTabPos:
704 if (!(info->fmt.dwMask & PFM_TABSTOPS))
706 PARAFORMAT2 fmt;
707 fmt.cbSize = sizeof(fmt);
708 editor_get_selection_para_fmt( info->editor, &fmt );
709 memcpy(info->fmt.rgxTabs, fmt.rgxTabs,
710 fmt.cTabCount * sizeof(fmt.rgxTabs[0]));
711 info->fmt.cTabCount = fmt.cTabCount;
712 info->fmt.dwMask |= PFM_TABSTOPS;
714 if (info->fmt.cTabCount < MAX_TAB_STOPS && info->rtfParam < 0x1000000)
715 info->fmt.rgxTabs[info->fmt.cTabCount++] = info->rtfParam;
716 break;
717 case rtfKeep:
718 info->fmt.dwMask |= PFM_KEEP;
719 info->fmt.wEffects |= PFE_KEEP;
720 break;
721 case rtfNoWidowControl:
722 info->fmt.dwMask |= PFM_NOWIDOWCONTROL;
723 info->fmt.wEffects |= PFE_NOWIDOWCONTROL;
724 break;
725 case rtfKeepNext:
726 info->fmt.dwMask |= PFM_KEEPNEXT;
727 info->fmt.wEffects |= PFE_KEEPNEXT;
728 break;
729 case rtfSpaceAfter:
730 info->fmt.dwMask |= PFM_SPACEAFTER;
731 info->fmt.dySpaceAfter = info->rtfParam;
732 break;
733 case rtfSpaceBefore:
734 info->fmt.dwMask |= PFM_SPACEBEFORE;
735 info->fmt.dySpaceBefore = info->rtfParam;
736 break;
737 case rtfSpaceBetween:
738 info->fmt.dwMask |= PFM_LINESPACING;
739 if ((int)info->rtfParam > 0)
741 info->fmt.dyLineSpacing = info->rtfParam;
742 info->fmt.bLineSpacingRule = 3;
744 else
746 info->fmt.dyLineSpacing = info->rtfParam;
747 info->fmt.bLineSpacingRule = 4;
749 break;
750 case rtfSpaceMultiply:
751 info->fmt.dwMask |= PFM_LINESPACING;
752 info->fmt.dyLineSpacing = info->rtfParam * 20;
753 info->fmt.bLineSpacingRule = 5;
754 break;
755 case rtfParBullet:
756 info->fmt.dwMask |= PFM_NUMBERING;
757 info->fmt.wNumbering = PFN_BULLET;
758 break;
759 case rtfParSimple:
760 info->fmt.dwMask |= PFM_NUMBERING;
761 info->fmt.wNumbering = 2; /* FIXME: MSDN says it's not used ?? */
762 break;
763 case rtfBorderLeft:
764 info->borderType = RTFBorderParaLeft;
765 info->fmt.wBorders |= 1;
766 info->fmt.dwMask |= PFM_BORDER;
767 break;
768 case rtfBorderRight:
769 info->borderType = RTFBorderParaRight;
770 info->fmt.wBorders |= 2;
771 info->fmt.dwMask |= PFM_BORDER;
772 break;
773 case rtfBorderTop:
774 info->borderType = RTFBorderParaTop;
775 info->fmt.wBorders |= 4;
776 info->fmt.dwMask |= PFM_BORDER;
777 break;
778 case rtfBorderBottom:
779 info->borderType = RTFBorderParaBottom;
780 info->fmt.wBorders |= 8;
781 info->fmt.dwMask |= PFM_BORDER;
782 break;
783 case rtfBorderSingle:
784 info->fmt.wBorders &= ~0x700;
785 info->fmt.wBorders |= 1 << 8;
786 info->fmt.dwMask |= PFM_BORDER;
787 break;
788 case rtfBorderThick:
789 info->fmt.wBorders &= ~0x700;
790 info->fmt.wBorders |= 2 << 8;
791 info->fmt.dwMask |= PFM_BORDER;
792 break;
793 case rtfBorderShadow:
794 info->fmt.wBorders &= ~0x700;
795 info->fmt.wBorders |= 10 << 8;
796 info->fmt.dwMask |= PFM_BORDER;
797 break;
798 case rtfBorderDouble:
799 info->fmt.wBorders &= ~0x700;
800 info->fmt.wBorders |= 7 << 8;
801 info->fmt.dwMask |= PFM_BORDER;
802 break;
803 case rtfBorderDot:
804 info->fmt.wBorders &= ~0x700;
805 info->fmt.wBorders |= 11 << 8;
806 info->fmt.dwMask |= PFM_BORDER;
807 break;
808 case rtfBorderWidth:
810 int borderSide = info->borderType & RTFBorderSideMask;
811 RTFTable *tableDef = info->tableDef;
812 if ((info->borderType & RTFBorderTypeMask) == RTFBorderTypeCell)
814 RTFBorder *border;
815 if (!tableDef || tableDef->numCellsDefined >= MAX_TABLE_CELLS)
816 break;
817 border = &tableDef->cells[tableDef->numCellsDefined].border[borderSide];
818 border->width = info->rtfParam;
819 break;
821 info->fmt.wBorderWidth = info->rtfParam;
822 info->fmt.dwMask |= PFM_BORDER;
823 break;
825 case rtfBorderSpace:
826 info->fmt.wBorderSpace = info->rtfParam;
827 info->fmt.dwMask |= PFM_BORDER;
828 break;
829 case rtfBorderColor:
831 RTFTable *tableDef = info->tableDef;
832 int borderSide = info->borderType & RTFBorderSideMask;
833 int borderType = info->borderType & RTFBorderTypeMask;
834 switch(borderType)
836 case RTFBorderTypePara:
837 if (!info->editor->bEmulateVersion10) /* v4.1 */
838 break;
839 /* v1.0 - 3.0 treat paragraph and row borders the same. */
840 case RTFBorderTypeRow:
841 if (tableDef) {
842 tableDef->border[borderSide].color = info->rtfParam;
844 break;
845 case RTFBorderTypeCell:
846 if (tableDef && tableDef->numCellsDefined < MAX_TABLE_CELLS) {
847 tableDef->cells[tableDef->numCellsDefined].border[borderSide].color = info->rtfParam;
849 break;
851 break;
853 case rtfRTLPar:
854 info->fmt.dwMask |= PFM_RTLPARA;
855 info->fmt.wEffects |= PFE_RTLPARA;
856 break;
857 case rtfLTRPar:
858 info->fmt.dwMask |= PFM_RTLPARA;
859 info->fmt.wEffects &= ~PFE_RTLPARA;
860 break;
864 void ME_RTFTblAttrHook(RTF_Info *info)
866 switch (info->rtfMinor)
868 case rtfRowDef:
870 if (!info->editor->bEmulateVersion10) /* v4.1 */
871 info->borderType = 0; /* Not sure */
872 else /* v1.0 - 3.0 */
873 info->borderType = RTFBorderRowTop;
874 if (!info->tableDef) {
875 info->tableDef = ME_MakeTableDef(info->editor);
876 } else {
877 ME_InitTableDef(info->editor, info->tableDef);
879 break;
881 case rtfCellPos:
883 int cellNum;
884 if (!info->tableDef)
886 info->tableDef = ME_MakeTableDef(info->editor);
888 cellNum = info->tableDef->numCellsDefined;
889 if (cellNum >= MAX_TABLE_CELLS)
890 break;
891 info->tableDef->cells[cellNum].rightBoundary = info->rtfParam;
892 if (cellNum < MAX_TAB_STOPS)
894 /* Tab stops were used to store cell positions before v4.1 but v4.1
895 * still seems to set the tabstops without using them. */
896 PARAFORMAT2 *fmt = &info->editor->pCursors[0].para->fmt;
897 fmt->rgxTabs[cellNum] &= ~0x00FFFFFF;
898 fmt->rgxTabs[cellNum] |= 0x00FFFFFF & info->rtfParam;
900 info->tableDef->numCellsDefined++;
901 break;
903 case rtfRowBordTop:
904 info->borderType = RTFBorderRowTop;
905 break;
906 case rtfRowBordLeft:
907 info->borderType = RTFBorderRowLeft;
908 break;
909 case rtfRowBordBottom:
910 info->borderType = RTFBorderRowBottom;
911 break;
912 case rtfRowBordRight:
913 info->borderType = RTFBorderRowRight;
914 break;
915 case rtfCellBordTop:
916 info->borderType = RTFBorderCellTop;
917 break;
918 case rtfCellBordLeft:
919 info->borderType = RTFBorderCellLeft;
920 break;
921 case rtfCellBordBottom:
922 info->borderType = RTFBorderCellBottom;
923 break;
924 case rtfCellBordRight:
925 info->borderType = RTFBorderCellRight;
926 break;
927 case rtfRowGapH:
928 if (info->tableDef)
929 info->tableDef->gapH = info->rtfParam;
930 break;
931 case rtfRowLeftEdge:
932 if (info->tableDef)
933 info->tableDef->leftEdge = info->rtfParam;
934 break;
938 void ME_RTFSpecialCharHook(RTF_Info *info)
940 RTFTable *tableDef = info->tableDef;
941 switch (info->rtfMinor)
943 case rtfNestCell:
944 if (info->editor->bEmulateVersion10) /* v1.0 - v3.0 */
945 break;
946 /* else fall through since v4.1 treats rtfNestCell and rtfCell the same */
947 case rtfCell:
948 if (!tableDef)
949 break;
950 RTFFlushOutputBuffer(info);
951 if (!info->editor->bEmulateVersion10) /* v4.1 */
953 if (tableDef->row_start)
955 if (!info->nestingLevel && tableDef->row_start->nFlags & MEPF_ROWEND)
957 ME_Paragraph *para = para_next( tableDef->row_start );
958 tableDef->row_start = table_insert_row_start_at_para( info->editor, para );
959 info->nestingLevel = 1;
961 table_insert_cell( info->editor, info->editor->pCursors );
964 else /* v1.0 - v3.0 */
966 ME_Paragraph *para = info->editor->pCursors[0].para;
968 if (para_in_table( para ) && tableDef->numCellsInserted < tableDef->numCellsDefined)
970 WCHAR tab = '\t';
971 ME_InsertTextFromCursor(info->editor, 0, &tab, 1, info->style);
972 tableDef->numCellsInserted++;
975 break;
976 case rtfNestRow:
977 if (info->editor->bEmulateVersion10) /* v1.0 - v3.0 */
978 break;
979 /* else fall through since v4.1 treats rtfNestRow and rtfRow the same */
980 case rtfRow:
982 ME_Run *run;
983 ME_Paragraph *para;
984 ME_Cell *cell;
985 int i;
987 if (!tableDef)
988 break;
989 RTFFlushOutputBuffer(info);
990 if (!info->editor->bEmulateVersion10) /* v4.1 */
992 if (!tableDef->row_start) break;
993 if (!info->nestingLevel && tableDef->row_start->nFlags & MEPF_ROWEND)
995 para = para_next( tableDef->row_start );
996 tableDef->row_start = table_insert_row_start_at_para( info->editor, para );
997 info->nestingLevel++;
999 para = tableDef->row_start;
1000 cell = table_row_first_cell( para );
1001 assert( cell && !cell_prev( cell ) );
1002 if (tableDef->numCellsDefined < 1)
1004 /* 2000 twips appears to be the cell size that native richedit uses
1005 * when no cell sizes are specified. */
1006 const int default_size = 2000;
1007 int right_boundary = default_size;
1008 cell->nRightBoundary = right_boundary;
1009 while (cell_next( cell ))
1011 cell = cell_next( cell );
1012 right_boundary += default_size;
1013 cell->nRightBoundary = right_boundary;
1015 para = table_insert_cell( info->editor, info->editor->pCursors );
1016 cell = para_cell( para );
1017 cell->nRightBoundary = right_boundary;
1019 else
1021 for (i = 0; i < tableDef->numCellsDefined; i++)
1023 RTFCell *cellDef = &tableDef->cells[i];
1024 cell->nRightBoundary = cellDef->rightBoundary;
1025 ME_ApplyBorderProperties( info, &cell->border, cellDef->border );
1026 cell = cell_next( cell );
1027 if (!cell)
1029 para = table_insert_cell( info->editor, info->editor->pCursors );
1030 cell = para_cell( para );
1033 /* Cell for table row delimiter is empty */
1034 cell->nRightBoundary = tableDef->cells[i - 1].rightBoundary;
1037 run = para_first_run( cell_first_para( cell ) );
1038 if (info->editor->pCursors[0].run != run || info->editor->pCursors[0].nOffset)
1040 int nOfs, nChars;
1041 /* Delete inserted cells that aren't defined. */
1042 info->editor->pCursors[1].run = run;
1043 info->editor->pCursors[1].para = run->para;
1044 info->editor->pCursors[1].nOffset = 0;
1045 nOfs = ME_GetCursorOfs(&info->editor->pCursors[1]);
1046 nChars = ME_GetCursorOfs(&info->editor->pCursors[0]) - nOfs;
1047 ME_InternalDeleteText(info->editor, &info->editor->pCursors[1],
1048 nChars, TRUE);
1051 para = table_insert_row_end( info->editor, info->editor->pCursors );
1052 para->fmt.dxOffset = abs(info->tableDef->gapH);
1053 para->fmt.dxStartIndent = info->tableDef->leftEdge;
1054 ME_ApplyBorderProperties( info, &para->border, tableDef->border );
1055 info->nestingLevel--;
1056 if (!info->nestingLevel)
1058 if (info->canInheritInTbl) tableDef->row_start = para;
1059 else
1061 while (info->tableDef)
1063 tableDef = info->tableDef;
1064 info->tableDef = tableDef->parent;
1065 heap_free(tableDef);
1069 else
1071 info->tableDef = tableDef->parent;
1072 heap_free(tableDef);
1075 else /* v1.0 - v3.0 */
1077 para = info->editor->pCursors[0].para;
1078 para->fmt.dxOffset = info->tableDef->gapH;
1079 para->fmt.dxStartIndent = info->tableDef->leftEdge;
1081 ME_ApplyBorderProperties( info, &para->border, tableDef->border );
1082 while (tableDef->numCellsInserted < tableDef->numCellsDefined)
1084 WCHAR tab = '\t';
1085 ME_InsertTextFromCursor(info->editor, 0, &tab, 1, info->style);
1086 tableDef->numCellsInserted++;
1088 para->fmt.cTabCount = min(tableDef->numCellsDefined, MAX_TAB_STOPS);
1089 if (!tableDef->numCellsDefined) para->fmt.wEffects &= ~PFE_TABLE;
1090 ME_InsertTextFromCursor(info->editor, 0, L"\r", 1, info->style);
1091 tableDef->numCellsInserted = 0;
1093 break;
1095 case rtfTab:
1096 case rtfPar:
1097 if (info->editor->bEmulateVersion10) /* v1.0 - 3.0 */
1099 ME_Paragraph *para;
1101 RTFFlushOutputBuffer(info);
1102 para = info->editor->pCursors[0].para;
1103 if (para_in_table( para ))
1105 /* rtfPar is treated like a space within a table. */
1106 info->rtfClass = rtfText;
1107 info->rtfMajor = ' ';
1109 else if (info->rtfMinor == rtfPar && tableDef)
1110 tableDef->numCellsInserted = 0;
1112 break;
1116 static HRESULT insert_static_object(ME_TextEditor *editor, HENHMETAFILE hemf, HBITMAP hbmp,
1117 const SIZEL* sz)
1119 LPOLEOBJECT lpObject = NULL;
1120 LPSTORAGE lpStorage = NULL;
1121 LPOLECLIENTSITE lpClientSite = NULL;
1122 LPDATAOBJECT lpDataObject = NULL;
1123 LPOLECACHE lpOleCache = NULL;
1124 STGMEDIUM stgm;
1125 FORMATETC fm;
1126 CLSID clsid;
1127 HRESULT hr = E_FAIL;
1128 DWORD conn;
1130 if (hemf)
1132 stgm.tymed = TYMED_ENHMF;
1133 stgm.u.hEnhMetaFile = hemf;
1134 fm.cfFormat = CF_ENHMETAFILE;
1136 else if (hbmp)
1138 stgm.tymed = TYMED_GDI;
1139 stgm.u.hBitmap = hbmp;
1140 fm.cfFormat = CF_BITMAP;
1142 else return E_FAIL;
1144 stgm.pUnkForRelease = NULL;
1146 fm.ptd = NULL;
1147 fm.dwAspect = DVASPECT_CONTENT;
1148 fm.lindex = -1;
1149 fm.tymed = stgm.tymed;
1151 if (OleCreateDefaultHandler(&CLSID_NULL, NULL, &IID_IOleObject, (void**)&lpObject) == S_OK &&
1152 IRichEditOle_GetClientSite(editor->richole, &lpClientSite) == S_OK &&
1153 IOleObject_SetClientSite(lpObject, lpClientSite) == S_OK &&
1154 IOleObject_GetUserClassID(lpObject, &clsid) == S_OK &&
1155 IOleObject_QueryInterface(lpObject, &IID_IOleCache, (void**)&lpOleCache) == S_OK &&
1156 IOleCache_Cache(lpOleCache, &fm, 0, &conn) == S_OK &&
1157 IOleObject_QueryInterface(lpObject, &IID_IDataObject, (void**)&lpDataObject) == S_OK &&
1158 IDataObject_SetData(lpDataObject, &fm, &stgm, TRUE) == S_OK)
1160 REOBJECT reobject;
1162 reobject.cbStruct = sizeof(reobject);
1163 reobject.cp = REO_CP_SELECTION;
1164 reobject.clsid = clsid;
1165 reobject.poleobj = lpObject;
1166 reobject.pstg = lpStorage;
1167 reobject.polesite = lpClientSite;
1168 /* convert from twips to .01 mm */
1169 reobject.sizel.cx = MulDiv(sz->cx, 254, 144);
1170 reobject.sizel.cy = MulDiv(sz->cy, 254, 144);
1171 reobject.dvaspect = DVASPECT_CONTENT;
1172 reobject.dwFlags = 0; /* FIXME */
1173 reobject.dwUser = 0;
1175 ME_InsertOLEFromCursor(editor, &reobject, 0);
1176 hr = S_OK;
1179 if (lpObject) IOleObject_Release(lpObject);
1180 if (lpClientSite) IOleClientSite_Release(lpClientSite);
1181 if (lpStorage) IStorage_Release(lpStorage);
1182 if (lpDataObject) IDataObject_Release(lpDataObject);
1183 if (lpOleCache) IOleCache_Release(lpOleCache);
1185 return hr;
1188 static void ME_RTFReadShpPictGroup( RTF_Info *info )
1190 int level = 1;
1192 for (;;)
1194 RTFGetToken (info);
1196 if (info->rtfClass == rtfEOF) return;
1197 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1199 if (--level == 0) break;
1201 else if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1203 level++;
1205 else
1207 RTFRouteToken( info );
1208 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1209 level--;
1213 RTFRouteToken( info ); /* feed "}" back to router */
1214 return;
1217 static DWORD read_hex_data( RTF_Info *info, BYTE **out )
1219 DWORD read = 0, size = 1024;
1220 BYTE *buf, val;
1221 BOOL flip;
1223 *out = NULL;
1225 if (info->rtfClass != rtfText)
1227 ERR("Called with incorrect token\n");
1228 return 0;
1231 buf = HeapAlloc( GetProcessHeap(), 0, size );
1232 if (!buf) return 0;
1234 val = info->rtfMajor;
1235 for (flip = TRUE;; flip = !flip)
1237 RTFGetToken( info );
1238 if (info->rtfClass == rtfEOF)
1240 HeapFree( GetProcessHeap(), 0, buf );
1241 return 0;
1243 if (info->rtfClass != rtfText) break;
1244 if (flip)
1246 if (read >= size)
1248 size *= 2;
1249 buf = HeapReAlloc( GetProcessHeap(), 0, buf, size );
1250 if (!buf) return 0;
1252 buf[read++] = RTFCharToHex(val) * 16 + RTFCharToHex(info->rtfMajor);
1254 else
1255 val = info->rtfMajor;
1257 if (flip) FIXME("wrong hex string\n");
1259 *out = buf;
1260 return read;
1263 static void ME_RTFReadPictGroup(RTF_Info *info)
1265 SIZEL sz;
1266 BYTE *buffer = NULL;
1267 DWORD size = 0;
1268 METAFILEPICT mfp;
1269 HENHMETAFILE hemf;
1270 HBITMAP hbmp;
1271 enum gfxkind {gfx_unknown = 0, gfx_enhmetafile, gfx_metafile, gfx_dib} gfx = gfx_unknown;
1272 int level = 1;
1274 mfp.mm = MM_TEXT;
1275 sz.cx = sz.cy = 0;
1277 for (;;)
1279 RTFGetToken( info );
1281 if (info->rtfClass == rtfText)
1283 if (level == 1)
1285 if (!buffer)
1286 size = read_hex_data( info, &buffer );
1288 else
1290 RTFSkipGroup( info );
1292 } /* We potentially have a new token so fall through. */
1294 if (info->rtfClass == rtfEOF) return;
1296 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1298 if (--level == 0) break;
1299 continue;
1301 if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1303 level++;
1304 continue;
1306 if (!RTFCheckCM( info, rtfControl, rtfPictAttr ))
1308 RTFRouteToken( info );
1309 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1310 level--;
1311 continue;
1314 if (RTFCheckMM( info, rtfPictAttr, rtfWinMetafile ))
1316 mfp.mm = info->rtfParam;
1317 gfx = gfx_metafile;
1319 else if (RTFCheckMM( info, rtfPictAttr, rtfDevIndBitmap ))
1321 if (info->rtfParam != 0) FIXME("dibitmap should be 0 (%d)\n", info->rtfParam);
1322 gfx = gfx_dib;
1324 else if (RTFCheckMM( info, rtfPictAttr, rtfEmfBlip ))
1325 gfx = gfx_enhmetafile;
1326 else if (RTFCheckMM( info, rtfPictAttr, rtfPicWid ))
1327 mfp.xExt = info->rtfParam;
1328 else if (RTFCheckMM( info, rtfPictAttr, rtfPicHt ))
1329 mfp.yExt = info->rtfParam;
1330 else if (RTFCheckMM( info, rtfPictAttr, rtfPicGoalWid ))
1331 sz.cx = info->rtfParam;
1332 else if (RTFCheckMM( info, rtfPictAttr, rtfPicGoalHt ))
1333 sz.cy = info->rtfParam;
1334 else
1335 FIXME("Non supported attribute: %d %d %d\n", info->rtfClass, info->rtfMajor, info->rtfMinor);
1338 if (buffer)
1340 switch (gfx)
1342 case gfx_enhmetafile:
1343 if ((hemf = SetEnhMetaFileBits( size, buffer )))
1344 insert_static_object( info->editor, hemf, NULL, &sz );
1345 break;
1346 case gfx_metafile:
1347 if ((hemf = SetWinMetaFileBits( size, buffer, NULL, &mfp )))
1348 insert_static_object( info->editor, hemf, NULL, &sz );
1349 break;
1350 case gfx_dib:
1352 BITMAPINFO *bi = (BITMAPINFO*)buffer;
1353 HDC hdc = GetDC(0);
1354 unsigned nc = bi->bmiHeader.biClrUsed;
1356 /* not quite right, especially for bitfields type of compression */
1357 if (!nc && bi->bmiHeader.biBitCount <= 8)
1358 nc = 1 << bi->bmiHeader.biBitCount;
1359 if ((hbmp = CreateDIBitmap( hdc, &bi->bmiHeader,
1360 CBM_INIT, (char*)(bi + 1) + nc * sizeof(RGBQUAD),
1361 bi, DIB_RGB_COLORS)) )
1362 insert_static_object( info->editor, NULL, hbmp, &sz );
1363 ReleaseDC( 0, hdc );
1364 break;
1366 default:
1367 break;
1370 HeapFree( GetProcessHeap(), 0, buffer );
1371 RTFRouteToken( info ); /* feed "}" back to router */
1372 return;
1375 /* for now, lookup the \result part and use it, whatever the object */
1376 static void ME_RTFReadObjectGroup(RTF_Info *info)
1378 for (;;)
1380 RTFGetToken (info);
1381 if (info->rtfClass == rtfEOF)
1382 return;
1383 if (RTFCheckCM(info, rtfGroup, rtfEndGroup))
1384 break;
1385 if (RTFCheckCM(info, rtfGroup, rtfBeginGroup))
1387 RTFGetToken (info);
1388 if (info->rtfClass == rtfEOF)
1389 return;
1390 if (RTFCheckCMM(info, rtfControl, rtfDestination, rtfObjResult))
1392 int level = 1;
1394 while (RTFGetToken (info) != rtfEOF)
1396 if (info->rtfClass == rtfGroup)
1398 if (info->rtfMajor == rtfBeginGroup) level++;
1399 else if (info->rtfMajor == rtfEndGroup && --level < 0) break;
1401 RTFRouteToken(info);
1404 else RTFSkipGroup(info);
1405 continue;
1407 if (!RTFCheckCM (info, rtfControl, rtfObjAttr))
1409 FIXME("Non supported attribute: %d %d %d\n", info->rtfClass, info->rtfMajor, info->rtfMinor);
1410 return;
1413 RTFRouteToken(info); /* feed "}" back to router */
1416 static void ME_RTFReadParnumGroup( RTF_Info *info )
1418 int level = 1, type = -1;
1419 WORD indent = 0, start = 1;
1420 WCHAR txt_before = 0, txt_after = 0;
1422 for (;;)
1424 RTFGetToken( info );
1426 if (RTFCheckCMM( info, rtfControl, rtfDestination, rtfParNumTextBefore ) ||
1427 RTFCheckCMM( info, rtfControl, rtfDestination, rtfParNumTextAfter ))
1429 int loc = info->rtfMinor;
1431 RTFGetToken( info );
1432 if (info->rtfClass == rtfText)
1434 if (loc == rtfParNumTextBefore)
1435 txt_before = info->rtfMajor;
1436 else
1437 txt_after = info->rtfMajor;
1438 continue;
1440 /* falling through to catch EOFs and group level changes */
1443 if (info->rtfClass == rtfEOF)
1444 return;
1446 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1448 if (--level == 0) break;
1449 continue;
1452 if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1454 level++;
1455 continue;
1458 /* Ignore non para-attr */
1459 if (!RTFCheckCM( info, rtfControl, rtfParAttr ))
1460 continue;
1462 switch (info->rtfMinor)
1464 case rtfParLevel: /* Para level is ignored */
1465 case rtfParSimple:
1466 break;
1467 case rtfParBullet:
1468 type = PFN_BULLET;
1469 break;
1471 case rtfParNumDecimal:
1472 type = PFN_ARABIC;
1473 break;
1474 case rtfParNumULetter:
1475 type = PFN_UCLETTER;
1476 break;
1477 case rtfParNumURoman:
1478 type = PFN_UCROMAN;
1479 break;
1480 case rtfParNumLLetter:
1481 type = PFN_LCLETTER;
1482 break;
1483 case rtfParNumLRoman:
1484 type = PFN_LCROMAN;
1485 break;
1487 case rtfParNumIndent:
1488 indent = info->rtfParam;
1489 break;
1490 case rtfParNumStartAt:
1491 start = info->rtfParam;
1492 break;
1496 if (type != -1)
1498 info->fmt.dwMask |= (PFM_NUMBERING | PFM_NUMBERINGSTART | PFM_NUMBERINGSTYLE | PFM_NUMBERINGTAB);
1499 info->fmt.wNumbering = type;
1500 info->fmt.wNumberingStart = start;
1501 info->fmt.wNumberingStyle = PFNS_PAREN;
1502 if (type != PFN_BULLET)
1504 if (txt_before == 0 && txt_after == 0)
1505 info->fmt.wNumberingStyle = PFNS_PLAIN;
1506 else if (txt_after == '.')
1507 info->fmt.wNumberingStyle = PFNS_PERIOD;
1508 else if (txt_before == '(' && txt_after == ')')
1509 info->fmt.wNumberingStyle = PFNS_PARENS;
1511 info->fmt.wNumberingTab = indent;
1514 TRACE("type %d indent %d start %d txt before %04x txt after %04x\n",
1515 type, indent, start, txt_before, txt_after);
1517 RTFRouteToken( info ); /* feed "}" back to router */
1520 static void ME_RTFReadHook(RTF_Info *info)
1522 switch(info->rtfClass)
1524 case rtfGroup:
1525 switch(info->rtfMajor)
1527 case rtfBeginGroup:
1528 if (info->stackTop < maxStack) {
1529 info->stack[info->stackTop].style = info->style;
1530 ME_AddRefStyle(info->style);
1531 info->stack[info->stackTop].codePage = info->codePage;
1532 info->stack[info->stackTop].unicodeLength = info->unicodeLength;
1534 info->stackTop++;
1535 info->styleChanged = FALSE;
1536 break;
1537 case rtfEndGroup:
1539 RTFFlushOutputBuffer(info);
1540 info->stackTop--;
1541 if (info->stackTop <= 0)
1542 info->rtfClass = rtfEOF;
1543 if (info->stackTop < 0)
1544 return;
1546 ME_ReleaseStyle(info->style);
1547 info->style = info->stack[info->stackTop].style;
1548 info->codePage = info->stack[info->stackTop].codePage;
1549 info->unicodeLength = info->stack[info->stackTop].unicodeLength;
1550 break;
1553 break;
1557 void
1558 ME_StreamInFill(ME_InStream *stream)
1560 stream->editstream->dwError = stream->editstream->pfnCallback(stream->editstream->dwCookie,
1561 (BYTE *)stream->buffer,
1562 sizeof(stream->buffer),
1563 (LONG *)&stream->dwSize);
1564 stream->dwUsed = 0;
1567 static LRESULT ME_StreamIn(ME_TextEditor *editor, DWORD format, EDITSTREAM *stream, BOOL stripLastCR)
1569 RTF_Info parser;
1570 ME_Style *style;
1571 int from, to, nUndoMode;
1572 int nEventMask = editor->nEventMask;
1573 ME_InStream inStream;
1574 BOOL invalidRTF = FALSE;
1575 ME_Cursor *selStart, *selEnd;
1576 LRESULT num_read = 0; /* bytes read for SF_TEXT, non-control chars inserted for SF_RTF */
1578 TRACE("stream==%p editor==%p format==0x%X\n", stream, editor, format);
1579 editor->nEventMask = 0;
1581 ME_GetSelectionOfs(editor, &from, &to);
1582 if (format & SFF_SELECTION && editor->mode & TM_RICHTEXT)
1584 ME_GetSelection(editor, &selStart, &selEnd);
1585 style = ME_GetSelectionInsertStyle(editor);
1587 ME_InternalDeleteText(editor, selStart, to - from, FALSE);
1589 /* Don't insert text at the end of the table row */
1590 if (!editor->bEmulateVersion10) /* v4.1 */
1592 ME_Paragraph *para = editor->pCursors->para;
1593 if (para->nFlags & (MEPF_ROWSTART | MEPF_ROWEND))
1595 para = para_next( para );
1596 editor->pCursors[0].para = para;
1597 editor->pCursors[0].run = para_first_run( para );
1598 editor->pCursors[0].nOffset = 0;
1600 editor->pCursors[1] = editor->pCursors[0];
1602 else /* v1.0 - 3.0 */
1604 if (editor->pCursors[0].run->nFlags & MERF_ENDPARA &&
1605 para_in_table( editor->pCursors[0].para ))
1606 return 0;
1609 else
1611 style = editor->pBuffer->pDefaultStyle;
1612 ME_AddRefStyle(style);
1613 set_selection_cursors(editor, 0, 0);
1614 ME_InternalDeleteText(editor, &editor->pCursors[1],
1615 ME_GetTextLength(editor), FALSE);
1616 from = to = 0;
1617 ME_ClearTempStyle(editor);
1618 editor_set_default_para_fmt( editor, &editor->pCursors[0].para->fmt );
1622 /* Back up undo mode to a local variable */
1623 nUndoMode = editor->nUndoMode;
1625 /* Only create an undo if SFF_SELECTION is set */
1626 if (!(format & SFF_SELECTION))
1627 editor->nUndoMode = umIgnore;
1629 inStream.editstream = stream;
1630 inStream.editstream->dwError = 0;
1631 inStream.dwSize = 0;
1632 inStream.dwUsed = 0;
1634 if (format & SF_RTF)
1636 /* Check if it's really RTF, and if it is not, use plain text */
1637 ME_StreamInFill(&inStream);
1638 if (!inStream.editstream->dwError)
1640 if ((!editor->bEmulateVersion10 && strncmp(inStream.buffer, "{\\rtf", 5) && strncmp(inStream.buffer, "{\\urtf", 6))
1641 || (editor->bEmulateVersion10 && *inStream.buffer != '{'))
1643 invalidRTF = TRUE;
1644 inStream.editstream->dwError = -16;
1649 if (!invalidRTF && !inStream.editstream->dwError)
1651 ME_Cursor start;
1652 from = ME_GetCursorOfs(&editor->pCursors[0]);
1653 if (format & SF_RTF) {
1655 /* setup the RTF parser */
1656 memset(&parser, 0, sizeof parser);
1657 RTFSetEditStream(&parser, &inStream);
1658 parser.rtfFormat = format&(SF_TEXT|SF_RTF);
1659 parser.editor = editor;
1660 parser.style = style;
1661 WriterInit(&parser);
1662 RTFInit(&parser);
1663 RTFSetReadHook(&parser, ME_RTFReadHook);
1664 RTFSetDestinationCallback(&parser, rtfShpPict, ME_RTFReadShpPictGroup);
1665 RTFSetDestinationCallback(&parser, rtfPict, ME_RTFReadPictGroup);
1666 RTFSetDestinationCallback(&parser, rtfObject, ME_RTFReadObjectGroup);
1667 RTFSetDestinationCallback(&parser, rtfParNumbering, ME_RTFReadParnumGroup);
1668 if (!parser.editor->bEmulateVersion10) /* v4.1 */
1670 RTFSetDestinationCallback(&parser, rtfNoNestTables, RTFSkipGroup);
1671 RTFSetDestinationCallback(&parser, rtfNestTableProps, RTFReadGroup);
1673 BeginFile(&parser);
1675 /* do the parsing */
1676 RTFRead(&parser);
1677 RTFFlushOutputBuffer(&parser);
1678 if (!editor->bEmulateVersion10) /* v4.1 */
1680 if (parser.tableDef && parser.tableDef->row_start &&
1681 (parser.nestingLevel > 0 || parser.canInheritInTbl))
1683 /* Delete any incomplete table row at the end of the rich text. */
1684 int nOfs, nChars;
1685 ME_Paragraph *para;
1687 parser.rtfMinor = rtfRow;
1688 /* Complete the table row before deleting it.
1689 * By doing it this way we will have the current paragraph format set
1690 * properly to reflect that is not in the complete table, and undo items
1691 * will be added for this change to the current paragraph format. */
1692 if (parser.nestingLevel > 0)
1694 while (parser.nestingLevel > 1)
1695 ME_RTFSpecialCharHook(&parser); /* Decrements nestingLevel */
1696 para = parser.tableDef->row_start;
1697 ME_RTFSpecialCharHook(&parser);
1699 else
1701 para = parser.tableDef->row_start;
1702 ME_RTFSpecialCharHook(&parser);
1703 assert( para->nFlags & MEPF_ROWEND );
1704 para = para_next( para );
1707 editor->pCursors[1].para = para;
1708 editor->pCursors[1].run = para_first_run( para );
1709 editor->pCursors[1].nOffset = 0;
1710 nOfs = ME_GetCursorOfs(&editor->pCursors[1]);
1711 nChars = ME_GetCursorOfs(&editor->pCursors[0]) - nOfs;
1712 ME_InternalDeleteText(editor, &editor->pCursors[1], nChars, TRUE);
1713 if (parser.tableDef) parser.tableDef->row_start = NULL;
1716 RTFDestroy(&parser);
1718 if (parser.stackTop > 0)
1720 while (--parser.stackTop >= 0)
1722 ME_ReleaseStyle(parser.style);
1723 parser.style = parser.stack[parser.stackTop].style;
1725 if (!inStream.editstream->dwError)
1726 inStream.editstream->dwError = HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
1729 /* Remove last line break, as mandated by tests. This is not affected by
1730 CR/LF counters, since RTF streaming presents only \para tokens, which
1731 are converted according to the standard rules: \r for 2.0, \r\n for 1.0
1733 if (stripLastCR && !(format & SFF_SELECTION)) {
1734 int newto;
1735 ME_GetSelection(editor, &selStart, &selEnd);
1736 newto = ME_GetCursorOfs(selEnd);
1737 if (newto > to + (editor->bEmulateVersion10 ? 1 : 0)) {
1738 WCHAR lastchar[3] = {'\0', '\0'};
1739 int linebreakSize = editor->bEmulateVersion10 ? 2 : 1;
1740 ME_Cursor linebreakCursor = *selEnd, lastcharCursor = *selEnd;
1741 CHARFORMAT2W cf;
1743 /* Set the final eop to the char fmt of the last char */
1744 cf.cbSize = sizeof(cf);
1745 cf.dwMask = CFM_ALL2;
1746 ME_MoveCursorChars(editor, &lastcharCursor, -1, FALSE);
1747 ME_GetCharFormat(editor, &lastcharCursor, &linebreakCursor, &cf);
1748 set_selection_cursors(editor, newto, -1);
1749 ME_SetSelectionCharFormat(editor, &cf);
1750 set_selection_cursors(editor, newto, newto);
1752 ME_MoveCursorChars(editor, &linebreakCursor, -linebreakSize, FALSE);
1753 ME_GetTextW(editor, lastchar, 2, &linebreakCursor, linebreakSize, FALSE, FALSE);
1754 if (lastchar[0] == '\r' && (lastchar[1] == '\n' || lastchar[1] == '\0')) {
1755 ME_InternalDeleteText(editor, &linebreakCursor, linebreakSize, FALSE);
1759 to = ME_GetCursorOfs(&editor->pCursors[0]);
1760 num_read = to - from;
1762 style = parser.style;
1764 else if (format & SF_TEXT)
1766 num_read = ME_StreamInText(editor, format, &inStream, style);
1767 to = ME_GetCursorOfs(&editor->pCursors[0]);
1769 else
1770 ERR("EM_STREAMIN without SF_TEXT or SF_RTF\n");
1771 /* put the cursor at the top */
1772 if (!(format & SFF_SELECTION))
1773 set_selection_cursors(editor, 0, 0);
1774 cursor_from_char_ofs( editor, from, &start );
1775 ME_UpdateLinkAttribute(editor, &start, to - from);
1778 /* Restore saved undo mode */
1779 editor->nUndoMode = nUndoMode;
1781 /* even if we didn't add an undo, we need to commit anything on the stack */
1782 ME_CommitUndo(editor);
1784 /* If SFF_SELECTION isn't set, delete any undos from before we started too */
1785 if (!(format & SFF_SELECTION))
1786 ME_EmptyUndoStack(editor);
1788 ME_ReleaseStyle(style);
1789 editor->nEventMask = nEventMask;
1790 ME_UpdateRepaint(editor, FALSE);
1791 if (!(format & SFF_SELECTION)) {
1792 ME_ClearTempStyle(editor);
1794 ME_SendSelChange(editor);
1795 ME_SendRequestResize(editor, FALSE);
1797 return num_read;
1801 typedef struct tagME_RTFStringStreamStruct
1803 char *string;
1804 int pos;
1805 int length;
1806 } ME_RTFStringStreamStruct;
1808 static DWORD CALLBACK ME_ReadFromRTFString(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
1810 ME_RTFStringStreamStruct *pStruct = (ME_RTFStringStreamStruct *)dwCookie;
1811 int count;
1813 count = min(cb, pStruct->length - pStruct->pos);
1814 memmove(lpBuff, pStruct->string + pStruct->pos, count);
1815 pStruct->pos += count;
1816 *pcb = count;
1817 return 0;
1820 static void
1821 ME_StreamInRTFString(ME_TextEditor *editor, BOOL selection, char *string)
1823 EDITSTREAM es;
1824 ME_RTFStringStreamStruct data;
1826 data.string = string;
1827 data.length = strlen(string);
1828 data.pos = 0;
1829 es.dwCookie = (DWORD_PTR)&data;
1830 es.pfnCallback = ME_ReadFromRTFString;
1831 ME_StreamIn(editor, SF_RTF | (selection ? SFF_SELECTION : 0), &es, TRUE);
1835 static int
1836 ME_FindText(ME_TextEditor *editor, DWORD flags, const CHARRANGE *chrg, const WCHAR *text, CHARRANGE *chrgText)
1838 const int nLen = lstrlenW(text);
1839 const int nTextLen = ME_GetTextLength(editor);
1840 int nMin, nMax;
1841 ME_Cursor cursor;
1842 WCHAR wLastChar = ' ';
1844 TRACE("flags==0x%08x, chrg->cpMin==%d, chrg->cpMax==%d text==%s\n",
1845 flags, chrg->cpMin, chrg->cpMax, debugstr_w(text));
1847 if (flags & ~(FR_DOWN | FR_MATCHCASE | FR_WHOLEWORD))
1848 FIXME("Flags 0x%08x not implemented\n",
1849 flags & ~(FR_DOWN | FR_MATCHCASE | FR_WHOLEWORD));
1851 nMin = chrg->cpMin;
1852 if (chrg->cpMax == -1)
1853 nMax = nTextLen;
1854 else
1855 nMax = chrg->cpMax > nTextLen ? nTextLen : chrg->cpMax;
1857 /* In 1.0 emulation, if cpMax reaches end of text, add the FR_DOWN flag */
1858 if (editor->bEmulateVersion10 && nMax == nTextLen)
1860 flags |= FR_DOWN;
1863 /* In 1.0 emulation, cpMin must always be no greater than cpMax */
1864 if (editor->bEmulateVersion10 && nMax < nMin)
1866 if (chrgText)
1868 chrgText->cpMin = -1;
1869 chrgText->cpMax = -1;
1871 return -1;
1874 /* when searching up, if cpMin < cpMax, then instead of searching
1875 * on [cpMin,cpMax], we search on [0,cpMin], otherwise, search on
1876 * [cpMax, cpMin]. The exception is when cpMax is -1, in which
1877 * case, it is always bigger than cpMin.
1879 if (!editor->bEmulateVersion10 && !(flags & FR_DOWN))
1881 int nSwap = nMax;
1883 nMax = nMin > nTextLen ? nTextLen : nMin;
1884 if (nMin < nSwap || chrg->cpMax == -1)
1885 nMin = 0;
1886 else
1887 nMin = nSwap;
1890 if (!nLen || nMin < 0 || nMax < 0 || nMax < nMin)
1892 if (chrgText)
1893 chrgText->cpMin = chrgText->cpMax = -1;
1894 return -1;
1897 if (flags & FR_DOWN) /* Forward search */
1899 /* If possible, find the character before where the search starts */
1900 if ((flags & FR_WHOLEWORD) && nMin)
1902 cursor_from_char_ofs( editor, nMin - 1, &cursor );
1903 wLastChar = *get_text( cursor.run, cursor.nOffset );
1904 ME_MoveCursorChars(editor, &cursor, 1, FALSE);
1906 else cursor_from_char_ofs( editor, nMin, &cursor );
1908 while (cursor.run && ME_GetCursorOfs(&cursor) + nLen <= nMax)
1910 ME_Run *run = cursor.run;
1911 int nCurStart = cursor.nOffset;
1912 int nMatched = 0;
1914 while (run && ME_CharCompare( *get_text( run, nCurStart + nMatched ), text[nMatched], (flags & FR_MATCHCASE)))
1916 if ((flags & FR_WHOLEWORD) && iswalnum(wLastChar))
1917 break;
1919 nMatched++;
1920 if (nMatched == nLen)
1922 ME_Run *next_run = run;
1923 int nNextStart = nCurStart;
1924 WCHAR wNextChar;
1926 /* Check to see if next character is a whitespace */
1927 if (flags & FR_WHOLEWORD)
1929 if (nCurStart + nMatched == run->len)
1931 next_run = run_next_all_paras( run );
1932 nNextStart = -nMatched;
1935 if (next_run)
1936 wNextChar = *get_text( next_run, nNextStart + nMatched );
1937 else
1938 wNextChar = ' ';
1940 if (iswalnum(wNextChar))
1941 break;
1944 cursor.nOffset += cursor.para->nCharOfs + cursor.run->nCharOfs;
1945 if (chrgText)
1947 chrgText->cpMin = cursor.nOffset;
1948 chrgText->cpMax = cursor.nOffset + nLen;
1950 TRACE("found at %d-%d\n", cursor.nOffset, cursor.nOffset + nLen);
1951 return cursor.nOffset;
1953 if (nCurStart + nMatched == run->len)
1955 run = run_next_all_paras( run );
1956 nCurStart = -nMatched;
1959 if (run)
1960 wLastChar = *get_text( run, nCurStart + nMatched );
1961 else
1962 wLastChar = ' ';
1964 cursor.nOffset++;
1965 if (cursor.nOffset == cursor.run->len)
1967 if (run_next_all_paras( cursor.run ))
1969 cursor.run = run_next_all_paras( cursor.run );
1970 cursor.para = cursor.run->para;
1971 cursor.nOffset = 0;
1973 else
1974 cursor.run = NULL;
1978 else /* Backward search */
1980 /* If possible, find the character after where the search ends */
1981 if ((flags & FR_WHOLEWORD) && nMax < nTextLen - 1)
1983 cursor_from_char_ofs( editor, nMax + 1, &cursor );
1984 wLastChar = *get_text( cursor.run, cursor.nOffset );
1985 ME_MoveCursorChars(editor, &cursor, -1, FALSE);
1987 else cursor_from_char_ofs( editor, nMax, &cursor );
1989 while (cursor.run && ME_GetCursorOfs(&cursor) - nLen >= nMin)
1991 ME_Run *run = cursor.run;
1992 ME_Paragraph *para = cursor.para;
1993 int nCurEnd = cursor.nOffset;
1994 int nMatched = 0;
1996 if (nCurEnd == 0 && run_prev_all_paras( run ))
1998 run = run_prev_all_paras( run );
1999 para = run->para;
2000 nCurEnd = run->len;
2003 while (run && ME_CharCompare( *get_text( run, nCurEnd - nMatched - 1 ),
2004 text[nLen - nMatched - 1], (flags & FR_MATCHCASE) ))
2006 if ((flags & FR_WHOLEWORD) && iswalnum(wLastChar))
2007 break;
2009 nMatched++;
2010 if (nMatched == nLen)
2012 ME_Run *prev_run = run;
2013 int nPrevEnd = nCurEnd;
2014 WCHAR wPrevChar;
2015 int nStart;
2017 /* Check to see if previous character is a whitespace */
2018 if (flags & FR_WHOLEWORD)
2020 if (nPrevEnd - nMatched == 0)
2022 prev_run = run_prev_all_paras( run );
2023 if (prev_run) nPrevEnd = prev_run->len + nMatched;
2026 if (prev_run) wPrevChar = *get_text( prev_run, nPrevEnd - nMatched - 1 );
2027 else wPrevChar = ' ';
2029 if (iswalnum(wPrevChar))
2030 break;
2033 nStart = para->nCharOfs + run->nCharOfs + nCurEnd - nMatched;
2034 if (chrgText)
2036 chrgText->cpMin = nStart;
2037 chrgText->cpMax = nStart + nLen;
2039 TRACE("found at %d-%d\n", nStart, nStart + nLen);
2040 return nStart;
2042 if (nCurEnd - nMatched == 0)
2044 if (run_prev_all_paras( run ))
2046 run = run_prev_all_paras( run );
2047 para = run->para;
2049 /* Don't care about pCurItem becoming NULL here; it's already taken
2050 * care of in the exterior loop condition */
2051 nCurEnd = run->len + nMatched;
2054 if (run)
2055 wLastChar = *get_text( run, nCurEnd - nMatched - 1 );
2056 else
2057 wLastChar = ' ';
2059 cursor.nOffset--;
2060 if (cursor.nOffset < 0)
2062 if (run_prev_all_paras( cursor.run ) )
2064 cursor.run = run_prev_all_paras( cursor.run );
2065 cursor.para = cursor.run->para;
2066 cursor.nOffset = cursor.run->len;
2068 else
2069 cursor.run = NULL;
2073 TRACE("not found\n");
2074 if (chrgText)
2075 chrgText->cpMin = chrgText->cpMax = -1;
2076 return -1;
2079 static int ME_GetTextEx(ME_TextEditor *editor, GETTEXTEX *ex, LPARAM pText)
2081 int nChars;
2082 ME_Cursor start;
2084 if (!ex->cb || !pText) return 0;
2086 if (ex->flags & ~(GT_SELECTION | GT_USECRLF))
2087 FIXME("GETTEXTEX flags 0x%08x not supported\n", ex->flags & ~(GT_SELECTION | GT_USECRLF));
2089 if (ex->flags & GT_SELECTION)
2091 int from, to;
2092 int nStartCur = ME_GetSelectionOfs(editor, &from, &to);
2093 start = editor->pCursors[nStartCur];
2094 nChars = to - from;
2096 else
2098 ME_SetCursorToStart(editor, &start);
2099 nChars = INT_MAX;
2101 if (ex->codepage == CP_UNICODE)
2103 return ME_GetTextW(editor, (LPWSTR)pText, ex->cb / sizeof(WCHAR) - 1,
2104 &start, nChars, ex->flags & GT_USECRLF, FALSE);
2106 else
2108 /* potentially each char may be a CR, why calculate the exact value with O(N) when
2109 we can just take a bigger buffer? :)
2110 The above assumption still holds with CR/LF counters, since CR->CRLF expansion
2111 occurs only in richedit 2.0 mode, in which line breaks have only one CR
2113 int crlfmul = (ex->flags & GT_USECRLF) ? 2 : 1;
2114 DWORD buflen;
2115 LPWSTR buffer;
2116 LRESULT rc;
2118 buflen = min(crlfmul * nChars, ex->cb - 1);
2119 buffer = heap_alloc((buflen + 1) * sizeof(WCHAR));
2121 nChars = ME_GetTextW(editor, buffer, buflen, &start, nChars, ex->flags & GT_USECRLF, FALSE);
2122 rc = WideCharToMultiByte(ex->codepage, 0, buffer, nChars + 1,
2123 (LPSTR)pText, ex->cb, ex->lpDefaultChar, ex->lpUsedDefChar);
2124 if (rc) rc--; /* do not count 0 terminator */
2126 heap_free(buffer);
2127 return rc;
2131 static int get_text_range( ME_TextEditor *editor, WCHAR *buffer,
2132 const ME_Cursor *start, int len )
2134 if (!buffer) return 0;
2135 return ME_GetTextW( editor, buffer, INT_MAX, start, len, FALSE, FALSE );
2138 int set_selection( ME_TextEditor *editor, int to, int from )
2140 int end;
2142 TRACE("%d - %d\n", to, from );
2144 if (!editor->bHideSelection) ME_InvalidateSelection( editor );
2145 end = set_selection_cursors( editor, to, from );
2146 if (!editor->bHideSelection) ME_InvalidateSelection( editor );
2147 update_caret( editor );
2148 ME_SendSelChange( editor );
2150 return end;
2153 typedef struct tagME_GlobalDestStruct
2155 HGLOBAL hData;
2156 int nLength;
2157 } ME_GlobalDestStruct;
2159 static DWORD CALLBACK ME_ReadFromHGLOBALUnicode(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
2161 ME_GlobalDestStruct *pData = (ME_GlobalDestStruct *)dwCookie;
2162 int i;
2163 WORD *pSrc, *pDest;
2165 cb = cb >> 1;
2166 pDest = (WORD *)lpBuff;
2167 pSrc = GlobalLock(pData->hData);
2168 for (i = 0; i<cb && pSrc[pData->nLength+i]; i++) {
2169 pDest[i] = pSrc[pData->nLength+i];
2171 pData->nLength += i;
2172 *pcb = 2*i;
2173 GlobalUnlock(pData->hData);
2174 return 0;
2177 static DWORD CALLBACK ME_ReadFromHGLOBALRTF(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
2179 ME_GlobalDestStruct *pData = (ME_GlobalDestStruct *)dwCookie;
2180 int i;
2181 BYTE *pSrc, *pDest;
2183 pDest = lpBuff;
2184 pSrc = GlobalLock(pData->hData);
2185 for (i = 0; i<cb && pSrc[pData->nLength+i]; i++) {
2186 pDest[i] = pSrc[pData->nLength+i];
2188 pData->nLength += i;
2189 *pcb = i;
2190 GlobalUnlock(pData->hData);
2191 return 0;
2194 static HRESULT paste_rtf(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2196 EDITSTREAM es;
2197 ME_GlobalDestStruct gds;
2198 HRESULT hr;
2200 gds.hData = med->u.hGlobal;
2201 gds.nLength = 0;
2202 es.dwCookie = (DWORD_PTR)&gds;
2203 es.pfnCallback = ME_ReadFromHGLOBALRTF;
2204 hr = ME_StreamIn( editor, SF_RTF | SFF_SELECTION, &es, FALSE ) == 0 ? E_FAIL : S_OK;
2205 ReleaseStgMedium( med );
2206 return hr;
2209 static HRESULT paste_text(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2211 EDITSTREAM es;
2212 ME_GlobalDestStruct gds;
2213 HRESULT hr;
2215 gds.hData = med->u.hGlobal;
2216 gds.nLength = 0;
2217 es.dwCookie = (DWORD_PTR)&gds;
2218 es.pfnCallback = ME_ReadFromHGLOBALUnicode;
2219 hr = ME_StreamIn( editor, SF_TEXT | SF_UNICODE | SFF_SELECTION, &es, FALSE ) == 0 ? E_FAIL : S_OK;
2220 ReleaseStgMedium( med );
2221 return hr;
2224 static HRESULT paste_emf(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2226 HRESULT hr;
2227 SIZEL sz = {0, 0};
2229 hr = insert_static_object( editor, med->u.hEnhMetaFile, NULL, &sz );
2230 if (SUCCEEDED(hr))
2232 ME_CommitUndo( editor );
2233 ME_UpdateRepaint( editor, FALSE );
2235 else
2236 ReleaseStgMedium( med );
2238 return hr;
2241 static struct paste_format
2243 FORMATETC fmt;
2244 HRESULT (*paste)(ME_TextEditor *, FORMATETC *, STGMEDIUM *);
2245 const WCHAR *name;
2246 } paste_formats[] =
2248 {{ -1, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, paste_rtf, L"Rich Text Format" },
2249 {{ CF_UNICODETEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, paste_text },
2250 {{ CF_ENHMETAFILE, NULL, DVASPECT_CONTENT, -1, TYMED_ENHMF }, paste_emf },
2251 {{ 0 }}
2254 static void init_paste_formats(void)
2256 struct paste_format *format;
2257 static int done;
2259 if (!done)
2261 for (format = paste_formats; format->fmt.cfFormat; format++)
2263 if (format->name)
2264 format->fmt.cfFormat = RegisterClipboardFormatW( format->name );
2266 done = 1;
2270 static BOOL paste_special(ME_TextEditor *editor, UINT cf, REPASTESPECIAL *ps, BOOL check_only)
2272 HRESULT hr;
2273 STGMEDIUM med;
2274 struct paste_format *format;
2275 IDataObject *data;
2277 /* Protect read-only edit control from modification */
2278 if (editor->props & TXTBIT_READONLY)
2280 if (!check_only) editor_beep( editor, MB_ICONERROR );
2281 return FALSE;
2284 init_paste_formats();
2286 if (ps && ps->dwAspect != DVASPECT_CONTENT)
2287 FIXME("Ignoring aspect %x\n", ps->dwAspect);
2289 hr = OleGetClipboard( &data );
2290 if (hr != S_OK) return FALSE;
2292 if (cf == CF_TEXT) cf = CF_UNICODETEXT;
2294 hr = S_FALSE;
2295 for (format = paste_formats; format->fmt.cfFormat; format++)
2297 if (cf && cf != format->fmt.cfFormat) continue;
2298 hr = IDataObject_QueryGetData( data, &format->fmt );
2299 if (hr == S_OK)
2301 if (!check_only)
2303 hr = IDataObject_GetData( data, &format->fmt, &med );
2304 if (hr != S_OK) goto done;
2305 hr = format->paste( editor, &format->fmt, &med );
2307 break;
2311 done:
2312 IDataObject_Release( data );
2314 return hr == S_OK;
2317 static HRESULT editor_copy( ME_TextEditor *editor, ME_Cursor *start, int chars, IDataObject **data_out )
2319 IDataObject *data = NULL;
2320 HRESULT hr = S_OK;
2322 if (editor->lpOleCallback)
2324 CHARRANGE range;
2325 range.cpMin = ME_GetCursorOfs( start );
2326 range.cpMax = range.cpMin + chars;
2327 hr = IRichEditOleCallback_GetClipboardData( editor->lpOleCallback, &range, RECO_COPY, &data );
2330 if (FAILED( hr ) || !data)
2331 hr = ME_GetDataObject( editor, start, chars, &data );
2333 if (SUCCEEDED( hr ))
2335 if (data_out)
2336 *data_out = data;
2337 else
2339 hr = OleSetClipboard( data );
2340 IDataObject_Release( data );
2344 return hr;
2347 HRESULT editor_copy_or_cut( ME_TextEditor *editor, BOOL cut, ME_Cursor *start, int count,
2348 IDataObject **data_out )
2350 HRESULT hr;
2352 if (cut && (editor->props & TXTBIT_READONLY))
2354 return E_ACCESSDENIED;
2357 hr = editor_copy( editor, start, count, data_out );
2358 if (SUCCEEDED(hr) && cut)
2360 ME_InternalDeleteText( editor, start, count, FALSE );
2361 ME_CommitUndo( editor );
2362 ME_UpdateRepaint( editor, TRUE );
2364 return hr;
2367 static BOOL copy_or_cut( ME_TextEditor *editor, BOOL cut )
2369 HRESULT hr;
2370 int offs, count;
2371 int start_cursor = ME_GetSelectionOfs( editor, &offs, &count );
2372 ME_Cursor *sel_start = &editor->pCursors[start_cursor];
2374 if (editor->password_char) return FALSE;
2376 count -= offs;
2377 hr = editor_copy_or_cut( editor, cut, sel_start, count, NULL );
2378 if (FAILED( hr )) editor_beep( editor, MB_ICONERROR );
2380 return SUCCEEDED( hr );
2383 static void ME_UpdateSelectionLinkAttribute(ME_TextEditor *editor)
2385 ME_Paragraph *start_para, *end_para;
2386 ME_Cursor *from, *to, start;
2387 int num_chars;
2389 if (!editor->AutoURLDetect_bEnable) return;
2391 ME_GetSelection(editor, &from, &to);
2393 /* Find paragraph previous to the one that contains start cursor */
2394 start_para = from->para;
2395 if (para_prev( start_para )) start_para = para_prev( start_para );
2397 /* Find paragraph that contains end cursor */
2398 end_para = para_next( to->para );
2400 start.para = start_para;
2401 start.run = para_first_run( start_para );
2402 start.nOffset = 0;
2403 num_chars = end_para->nCharOfs - start_para->nCharOfs;
2405 ME_UpdateLinkAttribute( editor, &start, num_chars );
2408 static BOOL handle_enter(ME_TextEditor *editor)
2410 BOOL shift_is_down = GetKeyState(VK_SHIFT) & 0x8000;
2412 if (editor->props & TXTBIT_MULTILINE)
2414 ME_Cursor cursor = editor->pCursors[0];
2415 ME_Paragraph *para = cursor.para;
2416 int from, to;
2417 ME_Style *style, *eop_style;
2419 if (editor->props & TXTBIT_READONLY)
2421 editor_beep( editor, MB_ICONERROR );
2422 return TRUE;
2425 ME_GetSelectionOfs(editor, &from, &to);
2426 if (editor->nTextLimit > ME_GetTextLength(editor) - (to-from))
2428 if (!editor->bEmulateVersion10) /* v4.1 */
2430 if (para->nFlags & MEPF_ROWEND)
2432 /* Add a new table row after this row. */
2433 para = table_append_row( editor, para );
2434 para = para_next( para );
2435 editor->pCursors[0].para = para;
2436 editor->pCursors[0].run = para_first_run( para );
2437 editor->pCursors[0].nOffset = 0;
2438 editor->pCursors[1] = editor->pCursors[0];
2439 ME_CommitUndo(editor);
2440 ME_UpdateRepaint(editor, FALSE);
2441 return TRUE;
2443 else if (para == editor->pCursors[1].para &&
2444 cursor.nOffset + cursor.run->nCharOfs == 0 &&
2445 para_prev( para ) && para_prev( para )->nFlags & MEPF_ROWSTART &&
2446 !para_prev( para )->nCharOfs)
2448 /* Insert a newline before the table. */
2449 para = para_prev( para );
2450 para->nFlags &= ~MEPF_ROWSTART;
2451 editor->pCursors[0].para = para;
2452 editor->pCursors[0].run = para_first_run( para );
2453 editor->pCursors[1] = editor->pCursors[0];
2454 ME_InsertTextFromCursor( editor, 0, L"\r", 1, editor->pCursors[0].run->style );
2455 para = editor_first_para( editor );
2456 editor_set_default_para_fmt( editor, &para->fmt );
2457 para->nFlags = 0;
2458 para_mark_rewrap( editor, para );
2459 editor->pCursors[0].para = para;
2460 editor->pCursors[0].run = para_first_run( para );
2461 editor->pCursors[1] = editor->pCursors[0];
2462 para_next( para )->nFlags |= MEPF_ROWSTART;
2463 ME_CommitCoalescingUndo(editor);
2464 ME_UpdateRepaint(editor, FALSE);
2465 return TRUE;
2468 else /* v1.0 - 3.0 */
2470 ME_Paragraph *para = cursor.para;
2471 if (para_in_table( para ))
2473 if (cursor.run->nFlags & MERF_ENDPARA)
2475 if (from == to)
2477 ME_ContinueCoalescingTransaction(editor);
2478 para = table_append_row( editor, para );
2479 editor->pCursors[0].para = para;
2480 editor->pCursors[0].run = para_first_run( para );
2481 editor->pCursors[0].nOffset = 0;
2482 editor->pCursors[1] = editor->pCursors[0];
2483 ME_CommitCoalescingUndo(editor);
2484 ME_UpdateRepaint(editor, FALSE);
2485 return TRUE;
2488 else
2490 ME_ContinueCoalescingTransaction(editor);
2491 if (cursor.run->nCharOfs + cursor.nOffset == 0 &&
2492 para_prev( para ) && !para_in_table( para_prev( para ) ))
2494 /* Insert newline before table */
2495 cursor.run = para_end_run( para_prev( para ) );
2496 if (cursor.run)
2498 editor->pCursors[0].run = cursor.run;
2499 editor->pCursors[0].para = para_prev( para );
2501 editor->pCursors[0].nOffset = 0;
2502 editor->pCursors[1] = editor->pCursors[0];
2503 ME_InsertTextFromCursor( editor, 0, L"\r", 1, editor->pCursors[0].run->style );
2505 else
2507 editor->pCursors[1] = editor->pCursors[0];
2508 para = table_append_row( editor, para );
2509 editor->pCursors[0].para = para;
2510 editor->pCursors[0].run = para_first_run( para );
2511 editor->pCursors[0].nOffset = 0;
2512 editor->pCursors[1] = editor->pCursors[0];
2514 ME_CommitCoalescingUndo(editor);
2515 ME_UpdateRepaint(editor, FALSE);
2516 return TRUE;
2521 style = style_get_insert_style( editor, editor->pCursors );
2523 /* Normally the new eop style is the insert style, however in a list it is copied from the existing
2524 eop style (this prevents the list label style changing when the new eop is inserted).
2525 No extra ref is taken here on eop_style. */
2526 if (para->fmt.wNumbering)
2527 eop_style = para->eop_run->style;
2528 else
2529 eop_style = style;
2530 ME_ContinueCoalescingTransaction(editor);
2531 if (shift_is_down)
2532 ME_InsertEndRowFromCursor(editor, 0);
2533 else
2534 if (!editor->bEmulateVersion10)
2535 ME_InsertTextFromCursor(editor, 0, L"\r", 1, eop_style);
2536 else
2537 ME_InsertTextFromCursor(editor, 0, L"\r\n", 2, eop_style);
2538 ME_CommitCoalescingUndo(editor);
2539 SetCursor(NULL);
2541 ME_UpdateSelectionLinkAttribute(editor);
2542 ME_UpdateRepaint(editor, FALSE);
2543 ME_SaveTempStyle(editor, style); /* set the temp insert style for the new para */
2544 ME_ReleaseStyle(style);
2546 return TRUE;
2548 return FALSE;
2551 static BOOL
2552 ME_KeyDown(ME_TextEditor *editor, WORD nKey)
2554 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2555 BOOL shift_is_down = GetKeyState(VK_SHIFT) & 0x8000;
2557 if (editor->bMouseCaptured)
2558 return FALSE;
2559 if (nKey != VK_SHIFT && nKey != VK_CONTROL && nKey != VK_MENU)
2560 editor->nSelectionType = stPosition;
2562 switch (nKey)
2564 case VK_LEFT:
2565 case VK_RIGHT:
2566 case VK_HOME:
2567 case VK_END:
2568 editor->nUDArrowX = -1;
2569 /* fall through */
2570 case VK_UP:
2571 case VK_DOWN:
2572 case VK_PRIOR:
2573 case VK_NEXT:
2574 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
2575 ME_ArrowKey(editor, nKey, shift_is_down, ctrl_is_down);
2576 return TRUE;
2577 case VK_BACK:
2578 case VK_DELETE:
2579 editor->nUDArrowX = -1;
2580 /* FIXME backspace and delete aren't the same, they act different wrt paragraph style of the merged paragraph */
2581 if (editor->props & TXTBIT_READONLY)
2582 return FALSE;
2583 if (ME_IsSelection(editor))
2585 ME_DeleteSelection(editor);
2586 ME_CommitUndo(editor);
2588 else if (nKey == VK_DELETE)
2590 /* Delete stops group typing.
2591 * (See MSDN remarks on EM_STOPGROUPTYPING message) */
2592 ME_DeleteTextAtCursor(editor, 1, 1);
2593 ME_CommitUndo(editor);
2595 else if (ME_ArrowKey(editor, VK_LEFT, FALSE, FALSE))
2597 BOOL bDeletionSucceeded;
2598 /* Backspace can be grouped for a single undo */
2599 ME_ContinueCoalescingTransaction(editor);
2600 bDeletionSucceeded = ME_DeleteTextAtCursor(editor, 1, 1);
2601 if (!bDeletionSucceeded && !editor->bEmulateVersion10) { /* v4.1 */
2602 /* Deletion was prevented so the cursor is moved back to where it was.
2603 * (e.g. this happens when trying to delete cell boundaries)
2605 ME_ArrowKey(editor, VK_RIGHT, FALSE, FALSE);
2607 ME_CommitCoalescingUndo(editor);
2609 else
2610 return TRUE;
2611 table_move_from_row_start( editor );
2612 ME_UpdateSelectionLinkAttribute(editor);
2613 ME_UpdateRepaint(editor, FALSE);
2614 ME_SendRequestResize(editor, FALSE);
2615 return TRUE;
2616 case VK_RETURN:
2617 if (!editor->bEmulateVersion10)
2618 return handle_enter(editor);
2619 break;
2620 case 'A':
2621 if (ctrl_is_down)
2623 set_selection( editor, 0, -1 );
2624 return TRUE;
2626 break;
2627 case 'V':
2628 if (ctrl_is_down)
2629 return paste_special( editor, 0, NULL, FALSE );
2630 break;
2631 case 'C':
2632 case 'X':
2633 if (ctrl_is_down)
2634 return copy_or_cut(editor, nKey == 'X');
2635 break;
2636 case 'Z':
2637 if (ctrl_is_down)
2639 ME_Undo(editor);
2640 return TRUE;
2642 break;
2643 case 'Y':
2644 if (ctrl_is_down)
2646 ME_Redo(editor);
2647 return TRUE;
2649 break;
2651 default:
2652 if (nKey != VK_SHIFT && nKey != VK_CONTROL && nKey && nKey != VK_MENU)
2653 editor->nUDArrowX = -1;
2654 if (ctrl_is_down)
2656 if (nKey == 'W')
2658 CHARFORMAT2W chf;
2659 char buf[2048];
2660 chf.cbSize = sizeof(chf);
2662 ME_GetSelectionCharFormat(editor, &chf);
2663 ME_DumpStyleToBuf(&chf, buf);
2664 MessageBoxA(NULL, buf, "Style dump", MB_OK);
2666 if (nKey == 'Q')
2668 ME_CheckCharOffsets(editor);
2672 return FALSE;
2675 static LRESULT handle_wm_char( ME_TextEditor *editor, WCHAR wstr, LPARAM flags )
2677 if (editor->bMouseCaptured)
2678 return 0;
2680 if (editor->props & TXTBIT_READONLY)
2682 editor_beep( editor, MB_ICONERROR );
2683 return 0; /* FIXME really 0 ? */
2686 if (editor->bEmulateVersion10 && wstr == '\r')
2687 handle_enter(editor);
2689 if ((unsigned)wstr >= ' ' || wstr == '\t')
2691 ME_Cursor cursor = editor->pCursors[0];
2692 ME_Paragraph *para = cursor.para;
2693 int from, to;
2694 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2695 ME_GetSelectionOfs(editor, &from, &to);
2696 if (wstr == '\t' &&
2697 /* v4.1 allows tabs to be inserted with ctrl key down */
2698 !(ctrl_is_down && !editor->bEmulateVersion10))
2700 BOOL selected_row = FALSE;
2702 if (ME_IsSelection(editor) &&
2703 cursor.run->nCharOfs + cursor.nOffset == 0 &&
2704 to == ME_GetCursorOfs(&editor->pCursors[0]) && para_prev( para ))
2706 para = para_prev( para );
2707 selected_row = TRUE;
2709 if (para_in_table( para ))
2711 table_handle_tab( editor, selected_row );
2712 ME_CommitUndo(editor);
2713 return 0;
2716 else if (!editor->bEmulateVersion10) /* v4.1 */
2718 if (para->nFlags & MEPF_ROWEND)
2720 if (from == to)
2722 para = para_next( para );
2723 if (para->nFlags & MEPF_ROWSTART) para = para_next( para );
2724 editor->pCursors[0].para = para;
2725 editor->pCursors[0].run = para_first_run( para );
2726 editor->pCursors[0].nOffset = 0;
2727 editor->pCursors[1] = editor->pCursors[0];
2731 else /* v1.0 - 3.0 */
2733 if (para_in_table( para ) && cursor.run->nFlags & MERF_ENDPARA && from == to)
2735 /* Text should not be inserted at the end of the table. */
2736 editor_beep( editor, -1 );
2737 return 0;
2740 /* FIXME maybe it would make sense to call EM_REPLACESEL instead ? */
2741 /* WM_CHAR is restricted to nTextLimit */
2742 if(editor->nTextLimit > ME_GetTextLength(editor) - (to-from))
2744 ME_Style *style = style_get_insert_style( editor, editor->pCursors );
2745 ME_ContinueCoalescingTransaction(editor);
2746 ME_InsertTextFromCursor(editor, 0, &wstr, 1, style);
2747 ME_ReleaseStyle(style);
2748 ME_CommitCoalescingUndo(editor);
2749 ITextHost_TxSetCursor(editor->texthost, NULL, FALSE);
2752 ME_UpdateSelectionLinkAttribute(editor);
2753 ME_UpdateRepaint(editor, FALSE);
2755 return 0;
2758 /* Process the message and calculate the new click count.
2760 * returns: The click count if it is mouse down event, else returns 0. */
2761 static int ME_CalculateClickCount(ME_TextEditor *editor, UINT msg, WPARAM wParam,
2762 LPARAM lParam)
2764 static int clickNum = 0;
2765 if (msg < WM_MOUSEFIRST || msg > WM_MOUSELAST)
2766 return 0;
2768 if ((msg == WM_LBUTTONDBLCLK) ||
2769 (msg == WM_RBUTTONDBLCLK) ||
2770 (msg == WM_MBUTTONDBLCLK) ||
2771 (msg == WM_XBUTTONDBLCLK))
2773 msg -= (WM_LBUTTONDBLCLK - WM_LBUTTONDOWN);
2776 if ((msg == WM_LBUTTONDOWN) ||
2777 (msg == WM_RBUTTONDOWN) ||
2778 (msg == WM_MBUTTONDOWN) ||
2779 (msg == WM_XBUTTONDOWN))
2781 static MSG prevClickMsg;
2782 MSG clickMsg;
2783 /* Compare the editor instead of the hwnd so that the this
2784 * can still be done for windowless richedit controls. */
2785 clickMsg.hwnd = (HWND)editor;
2786 clickMsg.message = msg;
2787 clickMsg.wParam = wParam;
2788 clickMsg.lParam = lParam;
2789 clickMsg.time = GetMessageTime();
2790 clickMsg.pt.x = (short)LOWORD(lParam);
2791 clickMsg.pt.y = (short)HIWORD(lParam);
2792 if ((clickNum != 0) &&
2793 (clickMsg.message == prevClickMsg.message) &&
2794 (clickMsg.hwnd == prevClickMsg.hwnd) &&
2795 (clickMsg.wParam == prevClickMsg.wParam) &&
2796 (clickMsg.time - prevClickMsg.time < GetDoubleClickTime()) &&
2797 (abs(clickMsg.pt.x - prevClickMsg.pt.x) < GetSystemMetrics(SM_CXDOUBLECLK)/2) &&
2798 (abs(clickMsg.pt.y - prevClickMsg.pt.y) < GetSystemMetrics(SM_CYDOUBLECLK)/2))
2800 clickNum++;
2801 } else {
2802 clickNum = 1;
2804 prevClickMsg = clickMsg;
2805 } else {
2806 return 0;
2808 return clickNum;
2811 static BOOL is_link( ME_Run *run )
2813 return (run->style->fmt.dwMask & CFM_LINK) && (run->style->fmt.dwEffects & CFE_LINK);
2816 void editor_set_cursor( ME_TextEditor *editor, int x, int y )
2818 ME_Cursor pos;
2819 BOOL is_exact;
2820 static HCURSOR cursor_arrow, cursor_hand, cursor_ibeam, cursor_reverse;
2821 HCURSOR cursor;
2823 if (!cursor_arrow)
2825 cursor_arrow = LoadCursorW( NULL, MAKEINTRESOURCEW( IDC_ARROW ) );
2826 cursor_hand = LoadCursorW( NULL, MAKEINTRESOURCEW( IDC_HAND ) );
2827 cursor_ibeam = LoadCursorW( NULL, MAKEINTRESOURCEW( IDC_IBEAM ) );
2828 cursor_reverse = LoadCursorW( dll_instance, MAKEINTRESOURCEW( OCR_REVERSE ) );
2831 cursor = cursor_ibeam;
2833 if ((editor->nSelectionType == stLine && editor->bMouseCaptured) ||
2834 (!editor->bEmulateVersion10 && y < editor->rcFormat.top && x < editor->rcFormat.left))
2835 cursor = cursor_reverse;
2836 else if (y < editor->rcFormat.top || y > editor->rcFormat.bottom)
2838 if (editor->bEmulateVersion10) cursor = cursor_arrow;
2839 else cursor = cursor_ibeam;
2841 else if (x < editor->rcFormat.left) cursor = cursor_reverse;
2842 else
2844 ME_CharFromPos( editor, x, y, &pos, &is_exact );
2845 if (is_exact)
2847 ME_Run *run = pos.run;
2849 if (is_link( run )) cursor = cursor_hand;
2851 else if (ME_IsSelection( editor ))
2853 int start, end, offset = ME_GetCursorOfs( &pos );
2855 ME_GetSelectionOfs( editor, &start, &end );
2856 if (start <= offset && end >= offset) cursor = cursor_arrow;
2861 ITextHost_TxSetCursor( editor->texthost, cursor, cursor == cursor_ibeam );
2864 static LONG ME_GetSelectionType(ME_TextEditor *editor)
2866 LONG sel_type = SEL_EMPTY;
2867 LONG start, end;
2869 ME_GetSelectionOfs(editor, &start, &end);
2870 if (start == end)
2871 sel_type = SEL_EMPTY;
2872 else
2874 LONG object_count = 0, character_count = 0;
2875 int i;
2877 for (i = 0; i < end - start; i++)
2879 ME_Cursor cursor;
2881 cursor_from_char_ofs( editor, start + i, &cursor );
2882 if (cursor.run->reobj) object_count++;
2883 else character_count++;
2884 if (character_count >= 2 && object_count >= 2)
2885 return (SEL_TEXT | SEL_MULTICHAR | SEL_OBJECT | SEL_MULTIOBJECT);
2887 if (character_count)
2889 sel_type |= SEL_TEXT;
2890 if (character_count >= 2)
2891 sel_type |= SEL_MULTICHAR;
2893 if (object_count)
2895 sel_type |= SEL_OBJECT;
2896 if (object_count >= 2)
2897 sel_type |= SEL_MULTIOBJECT;
2900 return sel_type;
2903 static BOOL ME_ShowContextMenu(ME_TextEditor *editor, int x, int y)
2905 CHARRANGE selrange;
2906 HMENU menu;
2907 int seltype;
2908 HWND hwnd, parent;
2910 if (!editor->lpOleCallback || !editor->have_texthost2) return FALSE;
2911 if (FAILED( ITextHost2_TxGetWindow( editor->texthost, &hwnd ))) return FALSE;
2912 parent = GetParent( hwnd );
2913 if (!parent) parent = hwnd;
2915 ME_GetSelectionOfs( editor, &selrange.cpMin, &selrange.cpMax );
2916 seltype = ME_GetSelectionType( editor );
2917 if (SUCCEEDED( IRichEditOleCallback_GetContextMenu( editor->lpOleCallback, seltype, NULL, &selrange, &menu ) ))
2919 TrackPopupMenu( menu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, x, y, 0, parent, NULL );
2920 DestroyMenu( menu );
2922 return TRUE;
2925 ME_TextEditor *ME_MakeEditor(ITextHost *texthost, BOOL bEmulateVersion10)
2927 ME_TextEditor *ed = heap_alloc(sizeof(*ed));
2928 int i;
2929 LONG selbarwidth;
2930 HRESULT hr;
2932 ed->sizeWindow.cx = ed->sizeWindow.cy = 0;
2933 if (ITextHost_QueryInterface( texthost, &IID_ITextHost2, (void **)&ed->texthost ) == S_OK)
2935 ITextHost_Release( texthost );
2936 ed->have_texthost2 = TRUE;
2938 else
2940 ed->texthost = (ITextHost2 *)texthost;
2941 ed->have_texthost2 = FALSE;
2944 ed->bEmulateVersion10 = bEmulateVersion10;
2945 ed->in_place_active = FALSE;
2946 ed->total_rows = 0;
2947 ITextHost_TxGetPropertyBits( ed->texthost, TXTBIT_RICHTEXT | TXTBIT_MULTILINE | TXTBIT_READONLY |
2948 TXTBIT_USEPASSWORD | TXTBIT_HIDESELECTION | TXTBIT_SAVESELECTION |
2949 TXTBIT_AUTOWORDSEL | TXTBIT_VERTICAL | TXTBIT_WORDWRAP | TXTBIT_ALLOWBEEP |
2950 TXTBIT_DISABLEDRAG,
2951 &ed->props );
2952 ITextHost_TxGetScrollBars( ed->texthost, &ed->scrollbars );
2953 ed->pBuffer = ME_MakeText();
2954 ed->nZoomNumerator = ed->nZoomDenominator = 0;
2955 ed->nAvailWidth = 0; /* wrap to client area */
2956 list_init( &ed->style_list );
2957 ME_MakeFirstParagraph(ed);
2958 /* The four cursors are for:
2959 * 0 - The position where the caret is shown
2960 * 1 - The anchored end of the selection (for normal selection)
2961 * 2 & 3 - The anchored start and end respectively for word, line,
2962 * or paragraph selection.
2964 ed->nCursors = 4;
2965 ed->pCursors = heap_alloc(ed->nCursors * sizeof(*ed->pCursors));
2966 ME_SetCursorToStart(ed, &ed->pCursors[0]);
2967 ed->pCursors[1] = ed->pCursors[0];
2968 ed->pCursors[2] = ed->pCursors[0];
2969 ed->pCursors[3] = ed->pCursors[1];
2970 ed->nLastTotalLength = ed->nTotalLength = 0;
2971 ed->nLastTotalWidth = ed->nTotalWidth = 0;
2972 ed->nUDArrowX = -1;
2973 ed->nEventMask = 0;
2974 ed->nModifyStep = 0;
2975 ed->nTextLimit = TEXT_LIMIT_DEFAULT;
2976 list_init( &ed->undo_stack );
2977 list_init( &ed->redo_stack );
2978 ed->nUndoStackSize = 0;
2979 ed->nUndoLimit = STACK_SIZE_DEFAULT;
2980 ed->nUndoMode = umAddToUndo;
2981 ed->nParagraphs = 1;
2982 ed->nLastSelStart = ed->nLastSelEnd = 0;
2983 ed->last_sel_start_para = ed->last_sel_end_para = ed->pCursors[0].para;
2984 ed->bHideSelection = FALSE;
2985 ed->pfnWordBreak = NULL;
2986 ed->richole = NULL;
2987 ed->lpOleCallback = NULL;
2988 ed->mode = TM_MULTILEVELUNDO | TM_MULTICODEPAGE;
2989 ed->mode |= (ed->props & TXTBIT_RICHTEXT) ? TM_RICHTEXT : TM_PLAINTEXT;
2990 ed->AutoURLDetect_bEnable = FALSE;
2991 ed->bHaveFocus = FALSE;
2992 ed->bMouseCaptured = FALSE;
2993 ed->caret_hidden = FALSE;
2994 ed->caret_height = 0;
2995 for (i=0; i<HFONT_CACHE_SIZE; i++)
2997 ed->pFontCache[i].nRefs = 0;
2998 ed->pFontCache[i].nAge = 0;
2999 ed->pFontCache[i].hFont = NULL;
3002 ME_CheckCharOffsets(ed);
3003 SetRectEmpty(&ed->rcFormat);
3004 hr = ITextHost_TxGetSelectionBarWidth( ed->texthost, &selbarwidth );
3005 /* FIXME: Convert selbarwidth from HIMETRIC to pixels */
3006 if (hr == S_OK && selbarwidth) ed->selofs = SELECTIONBAR_WIDTH;
3007 else ed->selofs = 0;
3008 ed->nSelectionType = stPosition;
3010 ed->password_char = 0;
3011 if (ed->props & TXTBIT_USEPASSWORD)
3012 ITextHost_TxGetPasswordChar( ed->texthost, &ed->password_char );
3014 ed->bWordWrap = (ed->props & TXTBIT_WORDWRAP) && (ed->props & TXTBIT_MULTILINE);
3016 ed->notified_cr.cpMin = ed->notified_cr.cpMax = 0;
3018 /* Default scrollbar information */
3019 ed->vert_si.cbSize = sizeof(SCROLLINFO);
3020 ed->vert_si.nMin = 0;
3021 ed->vert_si.nMax = 0;
3022 ed->vert_si.nPage = 0;
3023 ed->vert_si.nPos = 0;
3024 ed->vert_sb_enabled = 0;
3026 ed->horz_si.cbSize = sizeof(SCROLLINFO);
3027 ed->horz_si.nMin = 0;
3028 ed->horz_si.nMax = 0;
3029 ed->horz_si.nPage = 0;
3030 ed->horz_si.nPos = 0;
3031 ed->horz_sb_enabled = 0;
3033 if (ed->scrollbars & ES_DISABLENOSCROLL)
3035 if (ed->scrollbars & WS_VSCROLL)
3037 ITextHost_TxSetScrollRange( ed->texthost, SB_VERT, 0, 1, TRUE );
3038 ITextHost_TxEnableScrollBar( ed->texthost, SB_VERT, ESB_DISABLE_BOTH );
3040 if (ed->scrollbars & WS_HSCROLL)
3042 ITextHost_TxSetScrollRange( ed->texthost, SB_HORZ, 0, 1, TRUE );
3043 ITextHost_TxEnableScrollBar( ed->texthost, SB_HORZ, ESB_DISABLE_BOTH );
3047 ed->wheel_remain = 0;
3049 ed->back_style = TXTBACK_OPAQUE;
3050 ITextHost_TxGetBackStyle( ed->texthost, &ed->back_style );
3052 list_init( &ed->reobj_list );
3053 OleInitialize(NULL);
3055 return ed;
3058 void ME_DestroyEditor(ME_TextEditor *editor)
3060 ME_DisplayItem *p = editor->pBuffer->pFirst, *pNext = NULL;
3061 ME_Style *s, *cursor2;
3062 int i;
3064 ME_ClearTempStyle(editor);
3065 ME_EmptyUndoStack(editor);
3066 editor->pBuffer->pFirst = NULL;
3067 while(p)
3069 pNext = p->next;
3070 if (p->type == diParagraph)
3071 para_destroy( editor, &p->member.para );
3072 else
3073 ME_DestroyDisplayItem(p);
3074 p = pNext;
3077 LIST_FOR_EACH_ENTRY_SAFE( s, cursor2, &editor->style_list, ME_Style, entry )
3078 ME_DestroyStyle( s );
3080 ME_ReleaseStyle(editor->pBuffer->pDefaultStyle);
3081 for (i=0; i<HFONT_CACHE_SIZE; i++)
3083 if (editor->pFontCache[i].hFont)
3084 DeleteObject(editor->pFontCache[i].hFont);
3086 if(editor->lpOleCallback)
3087 IRichEditOleCallback_Release(editor->lpOleCallback);
3089 OleUninitialize();
3091 heap_free(editor->pBuffer);
3092 heap_free(editor->pCursors);
3093 heap_free(editor);
3096 static inline int get_default_line_height( ME_TextEditor *editor )
3098 int height = 0;
3100 if (editor->pBuffer && editor->pBuffer->pDefaultStyle)
3101 height = editor->pBuffer->pDefaultStyle->tm.tmHeight;
3102 if (height <= 0) height = 24;
3104 return height;
3107 static inline int calc_wheel_change( int *remain, int amount_per_click )
3109 int change = amount_per_click * (float)*remain / WHEEL_DELTA;
3110 *remain -= WHEEL_DELTA * change / amount_per_click;
3111 return change;
3114 void link_notify(ME_TextEditor *editor, UINT msg, WPARAM wParam, LPARAM lParam)
3116 int x,y;
3117 BOOL isExact;
3118 ME_Cursor cursor; /* The start of the clicked text. */
3119 ME_Run *run;
3120 ENLINK info;
3122 x = (short)LOWORD(lParam);
3123 y = (short)HIWORD(lParam);
3124 ME_CharFromPos(editor, x, y, &cursor, &isExact);
3125 if (!isExact) return;
3127 if (is_link( cursor.run ))
3128 { /* The clicked run has CFE_LINK set */
3129 info.nmhdr.hwndFrom = NULL;
3130 info.nmhdr.idFrom = 0;
3131 info.nmhdr.code = EN_LINK;
3132 info.msg = msg;
3133 info.wParam = wParam;
3134 info.lParam = lParam;
3135 cursor.nOffset = 0;
3137 /* find the first contiguous run with CFE_LINK set */
3138 info.chrg.cpMin = ME_GetCursorOfs(&cursor);
3139 run = cursor.run;
3140 while ((run = run_prev( run )) && is_link( run ))
3141 info.chrg.cpMin -= run->len;
3143 /* find the last contiguous run with CFE_LINK set */
3144 info.chrg.cpMax = ME_GetCursorOfs(&cursor) + cursor.run->len;
3145 run = cursor.run;
3146 while ((run = run_next( run )) && is_link( run ))
3147 info.chrg.cpMax += run->len;
3149 ITextHost_TxNotify(editor->texthost, info.nmhdr.code, &info);
3153 void ME_ReplaceSel(ME_TextEditor *editor, BOOL can_undo, const WCHAR *str, int len)
3155 int from, to, nStartCursor;
3156 ME_Style *style;
3158 nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3159 style = ME_GetSelectionInsertStyle(editor);
3160 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to-from, FALSE);
3161 ME_InsertTextFromCursor(editor, 0, str, len, style);
3162 ME_ReleaseStyle(style);
3163 /* drop temporary style if line end */
3165 * FIXME question: does abc\n mean: put abc,
3166 * clear temp style, put \n? (would require a change)
3168 if (len>0 && str[len-1] == '\n')
3169 ME_ClearTempStyle(editor);
3170 ME_CommitUndo(editor);
3171 ME_UpdateSelectionLinkAttribute(editor);
3172 if (!can_undo)
3173 ME_EmptyUndoStack(editor);
3174 ME_UpdateRepaint(editor, FALSE);
3177 static void ME_SetText(ME_TextEditor *editor, void *text, BOOL unicode)
3179 LONG codepage = unicode ? CP_UNICODE : CP_ACP;
3180 int textLen;
3182 LPWSTR wszText = ME_ToUnicode(codepage, text, &textLen);
3183 ME_InsertTextFromCursor(editor, 0, wszText, textLen, editor->pBuffer->pDefaultStyle);
3184 ME_EndToUnicode(codepage, wszText);
3187 static LRESULT handle_EM_SETCHARFORMAT( ME_TextEditor *editor, WPARAM flags, const CHARFORMAT2W *fmt_in )
3189 CHARFORMAT2W fmt;
3190 BOOL changed = TRUE;
3191 ME_Cursor start, end;
3193 if (!cfany_to_cf2w( &fmt, fmt_in )) return 0;
3195 if (flags & SCF_ALL)
3197 if (editor->mode & TM_PLAINTEXT)
3199 ME_SetDefaultCharFormat( editor, &fmt );
3201 else
3203 ME_SetCursorToStart( editor, &start );
3204 ME_SetCharFormat( editor, &start, NULL, &fmt );
3205 editor->nModifyStep = 1;
3208 else if (flags & SCF_SELECTION)
3210 if (editor->mode & TM_PLAINTEXT) return 0;
3211 if (flags & SCF_WORD)
3213 end = editor->pCursors[0];
3214 ME_MoveCursorWords( editor, &end, +1 );
3215 start = end;
3216 ME_MoveCursorWords( editor, &start, -1 );
3217 ME_SetCharFormat( editor, &start, &end, &fmt );
3219 changed = ME_IsSelection( editor );
3220 ME_SetSelectionCharFormat( editor, &fmt );
3221 if (changed) editor->nModifyStep = 1;
3223 else /* SCF_DEFAULT */
3225 ME_SetDefaultCharFormat( editor, &fmt );
3228 ME_CommitUndo( editor );
3229 if (changed)
3231 ME_WrapMarkedParagraphs( editor );
3232 ME_UpdateScrollBar( editor );
3234 return 1;
3237 #define UNSUPPORTED_MSG(e) \
3238 case e: \
3239 FIXME(#e ": stub\n"); \
3240 *phresult = S_FALSE; \
3241 return 0;
3243 /* Handle messages for windowless and windowed richedit controls.
3245 * The LRESULT that is returned is a return value for window procs,
3246 * and the phresult parameter is the COM return code needed by the
3247 * text services interface. */
3248 LRESULT editor_handle_message( ME_TextEditor *editor, UINT msg, WPARAM wParam,
3249 LPARAM lParam, HRESULT* phresult )
3251 *phresult = S_OK;
3253 switch(msg) {
3255 UNSUPPORTED_MSG(EM_DISPLAYBAND)
3256 UNSUPPORTED_MSG(EM_FINDWORDBREAK)
3257 UNSUPPORTED_MSG(EM_FMTLINES)
3258 UNSUPPORTED_MSG(EM_FORMATRANGE)
3259 UNSUPPORTED_MSG(EM_GETBIDIOPTIONS)
3260 UNSUPPORTED_MSG(EM_GETEDITSTYLE)
3261 UNSUPPORTED_MSG(EM_GETIMECOMPMODE)
3262 UNSUPPORTED_MSG(EM_GETIMESTATUS)
3263 UNSUPPORTED_MSG(EM_SETIMESTATUS)
3264 UNSUPPORTED_MSG(EM_GETLANGOPTIONS)
3265 UNSUPPORTED_MSG(EM_GETREDONAME)
3266 UNSUPPORTED_MSG(EM_GETTYPOGRAPHYOPTIONS)
3267 UNSUPPORTED_MSG(EM_GETUNDONAME)
3268 UNSUPPORTED_MSG(EM_GETWORDBREAKPROCEX)
3269 UNSUPPORTED_MSG(EM_SETBIDIOPTIONS)
3270 UNSUPPORTED_MSG(EM_SETEDITSTYLE)
3271 UNSUPPORTED_MSG(EM_SETLANGOPTIONS)
3272 UNSUPPORTED_MSG(EM_SETMARGINS)
3273 UNSUPPORTED_MSG(EM_SETPALETTE)
3274 UNSUPPORTED_MSG(EM_SETTABSTOPS)
3275 UNSUPPORTED_MSG(EM_SETTYPOGRAPHYOPTIONS)
3276 UNSUPPORTED_MSG(EM_SETWORDBREAKPROCEX)
3278 /* Messages specific to Richedit controls */
3280 case EM_STREAMIN:
3281 return ME_StreamIn(editor, wParam, (EDITSTREAM*)lParam, TRUE);
3282 case EM_STREAMOUT:
3283 return ME_StreamOut(editor, wParam, (EDITSTREAM *)lParam);
3284 case EM_EMPTYUNDOBUFFER:
3285 ME_EmptyUndoStack(editor);
3286 return 0;
3287 case EM_GETSEL:
3289 /* Note: wParam/lParam can be NULL */
3290 UINT from, to;
3291 PUINT pfrom = wParam ? (PUINT)wParam : &from;
3292 PUINT pto = lParam ? (PUINT)lParam : &to;
3293 ME_GetSelectionOfs(editor, (int *)pfrom, (int *)pto);
3294 if ((*pfrom|*pto) & 0xFFFF0000)
3295 return -1;
3296 return MAKELONG(*pfrom,*pto);
3298 case EM_EXGETSEL:
3300 CHARRANGE *pRange = (CHARRANGE *)lParam;
3301 ME_GetSelectionOfs(editor, &pRange->cpMin, &pRange->cpMax);
3302 TRACE("EM_EXGETSEL = (%d,%d)\n", pRange->cpMin, pRange->cpMax);
3303 return 0;
3305 case EM_SETUNDOLIMIT:
3307 if ((int)wParam < 0)
3308 editor->nUndoLimit = STACK_SIZE_DEFAULT;
3309 else
3310 editor->nUndoLimit = min(wParam, STACK_SIZE_MAX);
3311 /* Setting a max stack size keeps wine from getting killed
3312 for hogging memory. Windows allocates all this memory at once, so
3313 no program would realistically set a value above our maximum. */
3314 return editor->nUndoLimit;
3316 case EM_CANUNDO:
3317 return !list_empty( &editor->undo_stack );
3318 case EM_CANREDO:
3319 return !list_empty( &editor->redo_stack );
3320 case WM_UNDO: /* FIXME: actually not the same */
3321 case EM_UNDO:
3322 return ME_Undo(editor);
3323 case EM_REDO:
3324 return ME_Redo(editor);
3325 case EM_SETFONTSIZE:
3327 CHARFORMAT2W cf;
3328 LONG tmp_size, size;
3329 BOOL is_increase = ((LONG)wParam > 0);
3331 if (editor->mode & TM_PLAINTEXT)
3332 return FALSE;
3334 cf.cbSize = sizeof(cf);
3335 cf.dwMask = CFM_SIZE;
3336 ME_GetSelectionCharFormat(editor, &cf);
3337 tmp_size = (cf.yHeight / 20) + wParam;
3339 if (tmp_size <= 1)
3340 size = 1;
3341 else if (tmp_size > 12 && tmp_size < 28 && tmp_size % 2)
3342 size = tmp_size + (is_increase ? 1 : -1);
3343 else if (tmp_size > 28 && tmp_size < 36)
3344 size = is_increase ? 36 : 28;
3345 else if (tmp_size > 36 && tmp_size < 48)
3346 size = is_increase ? 48 : 36;
3347 else if (tmp_size > 48 && tmp_size < 72)
3348 size = is_increase ? 72 : 48;
3349 else if (tmp_size > 72 && tmp_size < 80)
3350 size = is_increase ? 80 : 72;
3351 else if (tmp_size > 80 && tmp_size < 1638)
3352 size = 10 * (is_increase ? (tmp_size / 10 + 1) : (tmp_size / 10));
3353 else if (tmp_size >= 1638)
3354 size = 1638;
3355 else
3356 size = tmp_size;
3358 cf.yHeight = size * 20; /* convert twips to points */
3359 ME_SetSelectionCharFormat(editor, &cf);
3360 ME_CommitUndo(editor);
3361 ME_WrapMarkedParagraphs(editor);
3362 ME_UpdateScrollBar(editor);
3364 return TRUE;
3366 case EM_SETSEL:
3368 return set_selection( editor, wParam, lParam );
3370 case EM_SETSCROLLPOS:
3372 POINT *point = (POINT *)lParam;
3373 scroll_abs( editor, point->x, point->y, TRUE );
3374 return 0;
3376 case EM_AUTOURLDETECT:
3378 if (wParam==1 || wParam ==0)
3380 editor->AutoURLDetect_bEnable = (BOOL)wParam;
3381 return 0;
3383 return E_INVALIDARG;
3385 case EM_GETAUTOURLDETECT:
3387 return editor->AutoURLDetect_bEnable;
3389 case EM_EXSETSEL:
3391 CHARRANGE range = *(CHARRANGE *)lParam;
3393 return set_selection( editor, range.cpMin, range.cpMax );
3395 case EM_SETTEXTEX:
3397 LPWSTR wszText;
3398 SETTEXTEX *pStruct = (SETTEXTEX *)wParam;
3399 int from, to, len;
3400 ME_Style *style;
3401 BOOL bRtf, bUnicode, bSelection, bUTF8;
3402 int oldModify = editor->nModifyStep;
3403 static const char utf8_bom[] = {0xef, 0xbb, 0xbf};
3405 if (!pStruct) return 0;
3407 /* If we detect ascii rtf at the start of the string,
3408 * we know it isn't unicode. */
3409 bRtf = (lParam && (!strncmp((char *)lParam, "{\\rtf", 5) ||
3410 !strncmp((char *)lParam, "{\\urtf", 6)));
3411 bUnicode = !bRtf && pStruct->codepage == CP_UNICODE;
3412 bUTF8 = (lParam && (!strncmp((char *)lParam, utf8_bom, 3)));
3414 TRACE("EM_SETTEXTEX - %s, flags %d, cp %d\n",
3415 bUnicode ? debugstr_w((LPCWSTR)lParam) : debugstr_a((LPCSTR)lParam),
3416 pStruct->flags, pStruct->codepage);
3418 bSelection = (pStruct->flags & ST_SELECTION) != 0;
3419 if (bSelection) {
3420 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3421 style = ME_GetSelectionInsertStyle(editor);
3422 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to - from, FALSE);
3423 } else {
3424 ME_Cursor start;
3425 ME_SetCursorToStart(editor, &start);
3426 ME_InternalDeleteText(editor, &start, ME_GetTextLength(editor), FALSE);
3427 style = editor->pBuffer->pDefaultStyle;
3430 if (bRtf) {
3431 ME_StreamInRTFString(editor, bSelection, (char *)lParam);
3432 if (bSelection) {
3433 /* FIXME: The length returned doesn't include the rtf control
3434 * characters, only the actual text. */
3435 len = lParam ? strlen((char *)lParam) : 0;
3437 } else {
3438 if (bUTF8 && !bUnicode) {
3439 wszText = ME_ToUnicode(CP_UTF8, (void *)(lParam+3), &len);
3440 ME_InsertTextFromCursor(editor, 0, wszText, len, style);
3441 ME_EndToUnicode(CP_UTF8, wszText);
3442 } else {
3443 wszText = ME_ToUnicode(pStruct->codepage, (void *)lParam, &len);
3444 ME_InsertTextFromCursor(editor, 0, wszText, len, style);
3445 ME_EndToUnicode(pStruct->codepage, wszText);
3449 if (bSelection) {
3450 ME_ReleaseStyle(style);
3451 ME_UpdateSelectionLinkAttribute(editor);
3452 } else {
3453 ME_Cursor cursor;
3454 len = 1;
3455 ME_SetCursorToStart(editor, &cursor);
3456 ME_UpdateLinkAttribute(editor, &cursor, INT_MAX);
3458 ME_CommitUndo(editor);
3459 if (!(pStruct->flags & ST_KEEPUNDO))
3461 editor->nModifyStep = oldModify;
3462 ME_EmptyUndoStack(editor);
3464 ME_UpdateRepaint(editor, FALSE);
3465 return len;
3467 case EM_SELECTIONTYPE:
3468 return ME_GetSelectionType(editor);
3469 case EM_GETMODIFY:
3470 return editor->nModifyStep == 0 ? 0 : -1;
3471 case EM_SETMODIFY:
3473 if (wParam)
3474 editor->nModifyStep = 1;
3475 else
3476 editor->nModifyStep = 0;
3478 return 0;
3480 case EM_SETEVENTMASK:
3482 DWORD nOldMask = editor->nEventMask;
3484 editor->nEventMask = lParam;
3485 return nOldMask;
3487 case EM_GETEVENTMASK:
3488 return editor->nEventMask;
3489 case EM_SETCHARFORMAT:
3490 return handle_EM_SETCHARFORMAT( editor, wParam, (CHARFORMAT2W *)lParam );
3491 case EM_GETCHARFORMAT:
3493 CHARFORMAT2W tmp, *dst = (CHARFORMAT2W *)lParam;
3494 if (dst->cbSize != sizeof(CHARFORMATA) &&
3495 dst->cbSize != sizeof(CHARFORMATW) &&
3496 dst->cbSize != sizeof(CHARFORMAT2A) &&
3497 dst->cbSize != sizeof(CHARFORMAT2W))
3498 return 0;
3499 tmp.cbSize = sizeof(tmp);
3500 if (!wParam)
3501 ME_GetDefaultCharFormat(editor, &tmp);
3502 else
3503 ME_GetSelectionCharFormat(editor, &tmp);
3504 cf2w_to_cfany(dst, &tmp);
3505 return tmp.dwMask;
3507 case EM_SETPARAFORMAT:
3509 BOOL result = editor_set_selection_para_fmt( editor, (PARAFORMAT2 *)lParam );
3510 ME_WrapMarkedParagraphs(editor);
3511 ME_UpdateScrollBar(editor);
3512 ME_CommitUndo(editor);
3513 return result;
3515 case EM_GETPARAFORMAT:
3516 editor_get_selection_para_fmt( editor, (PARAFORMAT2 *)lParam );
3517 return ((PARAFORMAT2 *)lParam)->dwMask;
3518 case EM_GETFIRSTVISIBLELINE:
3520 ME_Paragraph *para = editor_first_para( editor );
3521 ME_Row *row;
3522 int y = editor->vert_si.nPos;
3523 int count = 0;
3525 while (para_next( para ))
3527 if (y < para->pt.y + para->nHeight) break;
3528 count += para->nRows;
3529 para = para_next( para );
3532 row = para_first_row( para );
3533 while (row)
3535 if (y < para->pt.y + row->pt.y + row->nHeight) break;
3536 count++;
3537 row = row_next( row );
3539 return count;
3541 case EM_HIDESELECTION:
3543 editor->bHideSelection = (wParam != 0);
3544 ME_InvalidateSelection(editor);
3545 return 0;
3547 case EM_LINESCROLL:
3549 if (!(editor->props & TXTBIT_MULTILINE))
3550 return FALSE;
3551 ME_ScrollDown( editor, lParam * get_default_line_height( editor ) );
3552 return TRUE;
3554 case WM_CLEAR:
3556 int from, to;
3557 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3558 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to-from, FALSE);
3559 ME_CommitUndo(editor);
3560 ME_UpdateRepaint(editor, TRUE);
3561 return 0;
3563 case EM_REPLACESEL:
3565 WCHAR *text = (WCHAR *)lParam;
3566 int len = text ? lstrlenW( text ) : 0;
3568 TRACE( "EM_REPLACESEL - %s\n", debugstr_w( text ) );
3569 ME_ReplaceSel( editor, !!wParam, text, len );
3570 return len;
3572 case EM_SCROLLCARET:
3573 editor_ensure_visible( editor, &editor->pCursors[0] );
3574 return 0;
3575 case WM_SETFONT:
3577 LOGFONTW lf;
3578 CHARFORMAT2W fmt;
3579 HDC hDC;
3580 BOOL bRepaint = LOWORD(lParam);
3582 if (!wParam)
3583 wParam = (WPARAM)GetStockObject(SYSTEM_FONT);
3585 if (!GetObjectW((HGDIOBJ)wParam, sizeof(LOGFONTW), &lf))
3586 return 0;
3588 hDC = ITextHost_TxGetDC(editor->texthost);
3589 ME_CharFormatFromLogFont(hDC, &lf, &fmt);
3590 ITextHost_TxReleaseDC(editor->texthost, hDC);
3591 if (editor->mode & TM_RICHTEXT) {
3592 ME_Cursor start;
3593 ME_SetCursorToStart(editor, &start);
3594 ME_SetCharFormat(editor, &start, NULL, &fmt);
3596 ME_SetDefaultCharFormat(editor, &fmt);
3598 ME_CommitUndo(editor);
3599 editor_mark_rewrap_all( editor );
3600 ME_WrapMarkedParagraphs(editor);
3601 ME_UpdateScrollBar(editor);
3602 if (bRepaint)
3603 ME_Repaint(editor);
3604 return 0;
3606 case WM_SETTEXT:
3608 ME_Cursor cursor;
3609 ME_SetCursorToStart(editor, &cursor);
3610 ME_InternalDeleteText(editor, &cursor, ME_GetTextLength(editor), FALSE);
3611 if (lParam)
3613 TRACE("WM_SETTEXT lParam==%lx\n",lParam);
3614 if (!strncmp((char *)lParam, "{\\rtf", 5) ||
3615 !strncmp((char *)lParam, "{\\urtf", 6))
3617 /* Undocumented: WM_SETTEXT supports RTF text */
3618 ME_StreamInRTFString(editor, 0, (char *)lParam);
3620 else
3621 ME_SetText( editor, (void*)lParam, TRUE );
3623 else
3624 TRACE("WM_SETTEXT - NULL\n");
3625 ME_SetCursorToStart(editor, &cursor);
3626 ME_UpdateLinkAttribute(editor, &cursor, INT_MAX);
3627 set_selection_cursors(editor, 0, 0);
3628 editor->nModifyStep = 0;
3629 ME_CommitUndo(editor);
3630 ME_EmptyUndoStack(editor);
3631 ME_UpdateRepaint(editor, FALSE);
3632 return 1;
3634 case EM_CANPASTE:
3635 return paste_special( editor, 0, NULL, TRUE );
3636 case WM_PASTE:
3637 case WM_MBUTTONDOWN:
3638 wParam = 0;
3639 lParam = 0;
3640 /* fall through */
3641 case EM_PASTESPECIAL:
3642 paste_special( editor, wParam, (REPASTESPECIAL *)lParam, FALSE );
3643 return 0;
3644 case WM_CUT:
3645 case WM_COPY:
3646 copy_or_cut(editor, msg == WM_CUT);
3647 return 0;
3648 case WM_GETTEXTLENGTH:
3650 GETTEXTLENGTHEX how;
3651 how.flags = GTL_CLOSE | (editor->bEmulateVersion10 ? 0 : GTL_USECRLF) | GTL_NUMCHARS;
3652 how.codepage = CP_UNICODE;
3653 return ME_GetTextLengthEx(editor, &how);
3655 case EM_GETTEXTLENGTHEX:
3656 return ME_GetTextLengthEx(editor, (GETTEXTLENGTHEX *)wParam);
3657 case WM_GETTEXT:
3659 GETTEXTEX ex;
3660 ex.cb = wParam * sizeof(WCHAR);
3661 ex.flags = GT_USECRLF;
3662 ex.codepage = CP_UNICODE;
3663 ex.lpDefaultChar = NULL;
3664 ex.lpUsedDefChar = NULL;
3665 return ME_GetTextEx(editor, &ex, lParam);
3667 case EM_GETTEXTEX:
3668 return ME_GetTextEx(editor, (GETTEXTEX*)wParam, lParam);
3669 case EM_GETSELTEXT:
3671 int nFrom, nTo, nStartCur = ME_GetSelectionOfs(editor, &nFrom, &nTo);
3672 ME_Cursor *from = &editor->pCursors[nStartCur];
3673 return get_text_range( editor, (WCHAR *)lParam, from, nTo - nFrom );
3675 case EM_GETSCROLLPOS:
3677 POINT *point = (POINT *)lParam;
3678 point->x = editor->horz_si.nPos;
3679 point->y = editor->vert_si.nPos;
3680 /* 16-bit scaled value is returned as stored in scrollinfo */
3681 if (editor->horz_si.nMax > 0xffff)
3682 point->x = MulDiv(point->x, 0xffff, editor->horz_si.nMax);
3683 if (editor->vert_si.nMax > 0xffff)
3684 point->y = MulDiv(point->y, 0xffff, editor->vert_si.nMax);
3685 return 1;
3687 case EM_GETTEXTRANGE:
3689 TEXTRANGEW *rng = (TEXTRANGEW *)lParam;
3690 ME_Cursor start;
3691 int nStart = rng->chrg.cpMin;
3692 int nEnd = rng->chrg.cpMax;
3693 int textlength = ME_GetTextLength(editor);
3695 TRACE( "EM_GETTEXTRANGE min = %d max = %d textlength = %d\n", rng->chrg.cpMin, rng->chrg.cpMax, textlength );
3696 if (nStart < 0) return 0;
3697 if ((nStart == 0 && nEnd == -1) || nEnd > textlength)
3698 nEnd = textlength;
3699 if (nStart >= nEnd) return 0;
3701 cursor_from_char_ofs( editor, nStart, &start );
3702 return get_text_range( editor, rng->lpstrText, &start, nEnd - nStart );
3704 case EM_GETLINE:
3706 ME_Row *row;
3707 ME_Run *run;
3708 const unsigned int nMaxChars = *(WORD *) lParam;
3709 unsigned int nCharsLeft = nMaxChars;
3710 char *dest = (char *) lParam;
3711 ME_Cursor start, end;
3713 TRACE( "EM_GETLINE: row=%d, nMaxChars=%d\n", (int)wParam, nMaxChars );
3715 row = row_from_row_number( editor, wParam );
3716 if (row == NULL) return 0;
3718 row_first_cursor( row, &start );
3719 row_end_cursor( row, &end, TRUE );
3720 run = start.run;
3721 while (nCharsLeft)
3723 WCHAR *str;
3724 unsigned int nCopy;
3725 int ofs = (run == start.run) ? start.nOffset : 0;
3726 int len = (run == end.run) ? end.nOffset : run->len;
3728 str = get_text( run, ofs );
3729 nCopy = min( nCharsLeft, len );
3731 memcpy(dest, str, nCopy * sizeof(WCHAR));
3732 dest += nCopy * sizeof(WCHAR);
3733 nCharsLeft -= nCopy;
3734 if (run == end.run) break;
3735 run = row_next_run( row, run );
3738 /* append line termination, space allowing */
3739 if (nCharsLeft > 0) *((WCHAR *)dest) = '\0';
3741 TRACE("EM_GETLINE: got %u characters\n", nMaxChars - nCharsLeft);
3742 return nMaxChars - nCharsLeft;
3744 case EM_GETLINECOUNT:
3746 int count = editor->total_rows;
3747 ME_Run *prev_run, *last_run;
3749 last_run = para_end_run( para_prev( editor_end_para( editor ) ) );
3750 prev_run = run_prev_all_paras( last_run );
3752 if (editor->bEmulateVersion10 && prev_run && last_run->nCharOfs == 0 &&
3753 prev_run->len == 1 && *get_text( prev_run, 0 ) == '\r')
3755 /* In 1.0 emulation, the last solitary \r at the very end of the text
3756 (if one exists) is NOT a line break.
3757 FIXME: this is an ugly hack. This should have a more regular model. */
3758 count--;
3761 count = max(1, count);
3762 TRACE("EM_GETLINECOUNT: count==%d\n", count);
3763 return count;
3765 case EM_LINEFROMCHAR:
3767 if (wParam == -1) wParam = ME_GetCursorOfs( editor->pCursors + 1 );
3768 return row_number_from_char_ofs( editor, wParam );
3770 case EM_EXLINEFROMCHAR:
3772 if (lParam == -1) lParam = ME_GetCursorOfs( editor->pCursors + 1 );
3773 return row_number_from_char_ofs( editor, lParam );
3775 case EM_LINEINDEX:
3777 ME_Row *row;
3778 ME_Cursor cursor;
3779 int ofs;
3781 if (wParam == -1) row = row_from_cursor( editor->pCursors );
3782 else row = row_from_row_number( editor, wParam );
3783 if (!row) return -1;
3785 row_first_cursor( row, &cursor );
3786 ofs = ME_GetCursorOfs( &cursor );
3787 TRACE( "EM_LINEINDEX: nCharOfs==%d\n", ofs );
3788 return ofs;
3790 case EM_LINELENGTH:
3792 ME_Row *row;
3793 int start_ofs, end_ofs;
3794 ME_Cursor cursor;
3796 if (wParam > ME_GetTextLength(editor))
3797 return 0;
3798 if (wParam == -1)
3800 FIXME("EM_LINELENGTH: returning number of unselected characters on lines with selection unsupported.\n");
3801 return 0;
3803 cursor_from_char_ofs( editor, wParam, &cursor );
3804 row = row_from_cursor( &cursor );
3805 row_first_cursor( row, &cursor );
3806 start_ofs = ME_GetCursorOfs( &cursor );
3807 row_end_cursor( row, &cursor, FALSE );
3808 end_ofs = ME_GetCursorOfs( &cursor );
3809 TRACE( "EM_LINELENGTH(%ld)==%d\n", wParam, end_ofs - start_ofs );
3810 return end_ofs - start_ofs;
3812 case EM_EXLIMITTEXT:
3814 if ((int)lParam < 0)
3815 return 0;
3816 if (lParam == 0)
3817 editor->nTextLimit = 65536;
3818 else
3819 editor->nTextLimit = (int) lParam;
3820 return 0;
3822 case EM_LIMITTEXT:
3824 if (wParam == 0)
3825 editor->nTextLimit = 65536;
3826 else
3827 editor->nTextLimit = (int) wParam;
3828 return 0;
3830 case EM_GETLIMITTEXT:
3832 return editor->nTextLimit;
3834 case EM_FINDTEXT:
3835 case EM_FINDTEXTW:
3837 FINDTEXTW *ft = (FINDTEXTW *)lParam;
3838 return ME_FindText(editor, wParam, &ft->chrg, ft->lpstrText, NULL);
3840 case EM_FINDTEXTEX:
3841 case EM_FINDTEXTEXW:
3843 FINDTEXTEXW *ex = (FINDTEXTEXW *)lParam;
3844 return ME_FindText(editor, wParam, &ex->chrg, ex->lpstrText, &ex->chrgText);
3846 case EM_GETZOOM:
3847 if (!wParam || !lParam)
3848 return FALSE;
3849 *(int *)wParam = editor->nZoomNumerator;
3850 *(int *)lParam = editor->nZoomDenominator;
3851 return TRUE;
3852 case EM_SETZOOM:
3853 return ME_SetZoom(editor, wParam, lParam);
3854 case EM_CHARFROMPOS:
3856 ME_Cursor cursor;
3857 if (ME_CharFromPos(editor, ((POINTL *)lParam)->x, ((POINTL *)lParam)->y,
3858 &cursor, NULL))
3859 return ME_GetCursorOfs(&cursor);
3860 else
3861 return -1;
3863 case EM_POSFROMCHAR:
3865 ME_Cursor cursor;
3866 int nCharOfs, nLength;
3867 POINTL pt = {0,0};
3869 nCharOfs = wParam;
3870 /* detect which API version we're dealing with */
3871 if (wParam >= 0x40000)
3872 nCharOfs = lParam;
3873 nLength = ME_GetTextLength(editor);
3874 nCharOfs = min(nCharOfs, nLength);
3875 nCharOfs = max(nCharOfs, 0);
3877 cursor_from_char_ofs( editor, nCharOfs, &cursor );
3878 pt.y = cursor.run->pt.y;
3879 pt.x = cursor.run->pt.x +
3880 ME_PointFromChar( editor, cursor.run, cursor.nOffset, TRUE );
3881 pt.y += cursor.para->pt.y + editor->rcFormat.top;
3882 pt.x += editor->rcFormat.left;
3884 pt.x -= editor->horz_si.nPos;
3885 pt.y -= editor->vert_si.nPos;
3887 if (wParam >= 0x40000) *(POINTL *)wParam = pt;
3889 return (wParam >= 0x40000) ? 0 : MAKELONG( pt.x, pt.y );
3891 case WM_LBUTTONDBLCLK:
3892 case WM_LBUTTONDOWN:
3894 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
3895 ITextHost_TxSetFocus(editor->texthost);
3896 ME_LButtonDown(editor, (short)LOWORD(lParam), (short)HIWORD(lParam),
3897 ME_CalculateClickCount(editor, msg, wParam, lParam));
3898 ITextHost_TxSetCapture(editor->texthost, TRUE);
3899 editor->bMouseCaptured = TRUE;
3900 link_notify( editor, msg, wParam, lParam );
3901 break;
3903 case WM_MOUSEMOVE:
3904 if (editor->bMouseCaptured)
3905 ME_MouseMove(editor, (short)LOWORD(lParam), (short)HIWORD(lParam));
3906 else
3907 link_notify( editor, msg, wParam, lParam );
3908 break;
3909 case WM_LBUTTONUP:
3910 if (editor->bMouseCaptured) {
3911 ITextHost_TxSetCapture(editor->texthost, FALSE);
3912 editor->bMouseCaptured = FALSE;
3914 if (editor->nSelectionType == stDocument)
3915 editor->nSelectionType = stPosition;
3916 else
3918 link_notify( editor, msg, wParam, lParam );
3920 break;
3921 case WM_RBUTTONUP:
3922 case WM_RBUTTONDOWN:
3923 case WM_RBUTTONDBLCLK:
3924 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
3925 link_notify( editor, msg, wParam, lParam );
3926 goto do_default;
3927 case WM_CONTEXTMENU:
3928 if (!ME_ShowContextMenu(editor, (short)LOWORD(lParam), (short)HIWORD(lParam)))
3929 goto do_default;
3930 break;
3931 case WM_SETFOCUS:
3932 editor->bHaveFocus = TRUE;
3933 create_caret(editor);
3934 update_caret(editor);
3935 ITextHost_TxNotify( editor->texthost, EN_SETFOCUS, NULL );
3936 if (!editor->bHideSelection && (editor->props & TXTBIT_HIDESELECTION))
3937 ME_InvalidateSelection( editor );
3938 return 0;
3939 case WM_KILLFOCUS:
3940 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
3941 editor->bHaveFocus = FALSE;
3942 editor->wheel_remain = 0;
3943 hide_caret(editor);
3944 DestroyCaret();
3945 ITextHost_TxNotify( editor->texthost, EN_KILLFOCUS, NULL );
3946 if (!editor->bHideSelection && (editor->props & TXTBIT_HIDESELECTION))
3947 ME_InvalidateSelection( editor );
3948 return 0;
3949 case WM_COMMAND:
3950 TRACE("editor wnd command = %d\n", LOWORD(wParam));
3951 return 0;
3952 case WM_KEYDOWN:
3953 if (ME_KeyDown(editor, LOWORD(wParam)))
3954 return 0;
3955 goto do_default;
3956 case WM_CHAR:
3957 return handle_wm_char( editor, wParam, lParam );
3958 case WM_UNICHAR:
3959 if (wParam == UNICODE_NOCHAR) return TRUE;
3960 if (wParam <= 0x000fffff)
3962 if (wParam > 0xffff) /* convert to surrogates */
3964 wParam -= 0x10000;
3965 handle_wm_char( editor, (wParam >> 10) + 0xd800, 0 );
3966 handle_wm_char( editor, (wParam & 0x03ff) + 0xdc00, 0 );
3968 else
3969 handle_wm_char( editor, wParam, 0 );
3971 return 0;
3972 case EM_STOPGROUPTYPING:
3973 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
3974 return 0;
3975 case WM_HSCROLL:
3977 const int scrollUnit = 7;
3979 switch(LOWORD(wParam))
3981 case SB_LEFT:
3982 scroll_abs( editor, 0, 0, TRUE );
3983 break;
3984 case SB_RIGHT:
3985 scroll_abs( editor, editor->horz_si.nMax - (int)editor->horz_si.nPage,
3986 editor->vert_si.nMax - (int)editor->vert_si.nPage, TRUE );
3987 break;
3988 case SB_LINELEFT:
3989 ME_ScrollLeft(editor, scrollUnit);
3990 break;
3991 case SB_LINERIGHT:
3992 ME_ScrollRight(editor, scrollUnit);
3993 break;
3994 case SB_PAGELEFT:
3995 ME_ScrollLeft(editor, editor->sizeWindow.cx);
3996 break;
3997 case SB_PAGERIGHT:
3998 ME_ScrollRight(editor, editor->sizeWindow.cx);
3999 break;
4000 case SB_THUMBTRACK:
4001 case SB_THUMBPOSITION:
4003 int pos = HIWORD(wParam);
4004 if (editor->horz_si.nMax > 0xffff)
4005 pos = MulDiv(pos, editor->horz_si.nMax, 0xffff);
4006 scroll_h_abs( editor, pos, FALSE );
4007 break;
4010 break;
4012 case EM_SCROLL: /* fall through */
4013 case WM_VSCROLL:
4015 int origNPos;
4016 int lineHeight = get_default_line_height( editor );
4018 origNPos = editor->vert_si.nPos;
4020 switch(LOWORD(wParam))
4022 case SB_TOP:
4023 scroll_abs( editor, 0, 0, TRUE );
4024 break;
4025 case SB_BOTTOM:
4026 scroll_abs( editor, editor->horz_si.nMax - (int)editor->horz_si.nPage,
4027 editor->vert_si.nMax - (int)editor->vert_si.nPage, TRUE );
4028 break;
4029 case SB_LINEUP:
4030 ME_ScrollUp(editor,lineHeight);
4031 break;
4032 case SB_LINEDOWN:
4033 ME_ScrollDown(editor,lineHeight);
4034 break;
4035 case SB_PAGEUP:
4036 ME_ScrollUp(editor,editor->sizeWindow.cy);
4037 break;
4038 case SB_PAGEDOWN:
4039 ME_ScrollDown(editor,editor->sizeWindow.cy);
4040 break;
4041 case SB_THUMBTRACK:
4042 case SB_THUMBPOSITION:
4044 int pos = HIWORD(wParam);
4045 if (editor->vert_si.nMax > 0xffff)
4046 pos = MulDiv(pos, editor->vert_si.nMax, 0xffff);
4047 scroll_v_abs( editor, pos, FALSE );
4048 break;
4051 if (msg == EM_SCROLL)
4052 return 0x00010000 | (((editor->vert_si.nPos - origNPos)/lineHeight) & 0xffff);
4053 break;
4055 case WM_MOUSEWHEEL:
4057 int delta = GET_WHEEL_DELTA_WPARAM( wParam );
4058 BOOL ctrl_is_down = GetKeyState( VK_CONTROL ) & 0x8000;
4060 /* if scrolling changes direction, ignore left overs */
4061 if ((delta < 0 && editor->wheel_remain < 0) ||
4062 (delta > 0 && editor->wheel_remain > 0))
4063 editor->wheel_remain += delta;
4064 else
4065 editor->wheel_remain = delta;
4067 if (editor->wheel_remain)
4069 if (ctrl_is_down) {
4070 int numerator;
4071 if (!editor->nZoomNumerator || !editor->nZoomDenominator)
4073 numerator = 100;
4074 } else {
4075 numerator = editor->nZoomNumerator * 100 / editor->nZoomDenominator;
4077 numerator += calc_wheel_change( &editor->wheel_remain, 10 );
4078 if (numerator >= 10 && numerator <= 500)
4079 ME_SetZoom(editor, numerator, 100);
4080 } else {
4081 UINT max_lines = 3;
4082 int lines = 0;
4084 SystemParametersInfoW( SPI_GETWHEELSCROLLLINES, 0, &max_lines, 0 );
4085 if (max_lines)
4086 lines = calc_wheel_change( &editor->wheel_remain, (int)max_lines );
4087 if (lines)
4088 ME_ScrollDown( editor, -lines * get_default_line_height( editor ) );
4091 break;
4093 case EM_REQUESTRESIZE:
4094 ME_SendRequestResize(editor, TRUE);
4095 return 0;
4096 /* IME messages to make richedit controls IME aware */
4097 case WM_IME_SETCONTEXT:
4098 case WM_IME_CONTROL:
4099 case WM_IME_SELECT:
4100 case WM_IME_COMPOSITIONFULL:
4101 return 0;
4102 case WM_IME_STARTCOMPOSITION:
4104 editor->imeStartIndex=ME_GetCursorOfs(&editor->pCursors[0]);
4105 ME_DeleteSelection(editor);
4106 ME_CommitUndo(editor);
4107 ME_UpdateRepaint(editor, FALSE);
4108 return 0;
4110 case WM_IME_COMPOSITION:
4112 HIMC hIMC;
4114 ME_Style *style = style_get_insert_style( editor, editor->pCursors );
4115 hIMC = ITextHost_TxImmGetContext(editor->texthost);
4116 ME_DeleteSelection(editor);
4117 ME_SaveTempStyle(editor, style);
4118 if (lParam & (GCS_RESULTSTR|GCS_COMPSTR))
4120 LPWSTR lpCompStr = NULL;
4121 DWORD dwBufLen;
4122 DWORD dwIndex = lParam & GCS_RESULTSTR;
4123 if (!dwIndex)
4124 dwIndex = GCS_COMPSTR;
4126 dwBufLen = ImmGetCompositionStringW(hIMC, dwIndex, NULL, 0);
4127 lpCompStr = HeapAlloc(GetProcessHeap(),0,dwBufLen + sizeof(WCHAR));
4128 ImmGetCompositionStringW(hIMC, dwIndex, lpCompStr, dwBufLen);
4129 lpCompStr[dwBufLen/sizeof(WCHAR)] = 0;
4130 ME_InsertTextFromCursor(editor,0,lpCompStr,dwBufLen/sizeof(WCHAR),style);
4131 HeapFree(GetProcessHeap(), 0, lpCompStr);
4133 if (dwIndex == GCS_COMPSTR)
4134 set_selection_cursors(editor,editor->imeStartIndex,
4135 editor->imeStartIndex + dwBufLen/sizeof(WCHAR));
4137 ME_ReleaseStyle(style);
4138 ME_CommitUndo(editor);
4139 ME_UpdateRepaint(editor, FALSE);
4140 return 0;
4142 case WM_IME_ENDCOMPOSITION:
4144 ME_DeleteSelection(editor);
4145 editor->imeStartIndex=-1;
4146 return 0;
4148 case EM_GETOLEINTERFACE:
4149 IRichEditOle_AddRef( editor->richole );
4150 *(IRichEditOle **)lParam = editor->richole;
4151 return 1;
4153 case EM_SETOLECALLBACK:
4154 if(editor->lpOleCallback)
4155 IRichEditOleCallback_Release(editor->lpOleCallback);
4156 editor->lpOleCallback = (IRichEditOleCallback*)lParam;
4157 if(editor->lpOleCallback)
4158 IRichEditOleCallback_AddRef(editor->lpOleCallback);
4159 return TRUE;
4160 case EM_GETWORDBREAKPROC:
4161 return (LRESULT)editor->pfnWordBreak;
4162 case EM_SETWORDBREAKPROC:
4164 EDITWORDBREAKPROCW pfnOld = editor->pfnWordBreak;
4166 editor->pfnWordBreak = (EDITWORDBREAKPROCW)lParam;
4167 return (LRESULT)pfnOld;
4169 case EM_GETTEXTMODE:
4170 return editor->mode;
4171 case EM_SETTEXTMODE:
4173 int mask = 0;
4174 int changes = 0;
4176 if (ME_GetTextLength(editor) ||
4177 !list_empty( &editor->undo_stack ) || !list_empty( &editor->redo_stack ))
4178 return E_UNEXPECTED;
4180 /* Check for mutually exclusive flags in adjacent bits of wParam */
4181 if ((wParam & (TM_RICHTEXT | TM_MULTILEVELUNDO | TM_MULTICODEPAGE)) &
4182 (wParam & (TM_PLAINTEXT | TM_SINGLELEVELUNDO | TM_SINGLECODEPAGE)) << 1)
4183 return E_INVALIDARG;
4185 if (wParam & (TM_RICHTEXT | TM_PLAINTEXT))
4187 mask |= TM_RICHTEXT | TM_PLAINTEXT;
4188 changes |= wParam & (TM_RICHTEXT | TM_PLAINTEXT);
4189 if (wParam & TM_PLAINTEXT) {
4190 /* Clear selection since it should be possible to select the
4191 * end of text run for rich text */
4192 ME_InvalidateSelection(editor);
4193 ME_SetCursorToStart(editor, &editor->pCursors[0]);
4194 editor->pCursors[1] = editor->pCursors[0];
4195 /* plain text can only have the default style. */
4196 ME_ClearTempStyle(editor);
4197 ME_AddRefStyle(editor->pBuffer->pDefaultStyle);
4198 ME_ReleaseStyle( editor->pCursors[0].run->style );
4199 editor->pCursors[0].run->style = editor->pBuffer->pDefaultStyle;
4202 /* FIXME: Currently no support for undo level and code page options */
4203 editor->mode = (editor->mode & ~mask) | changes;
4204 return 0;
4206 case EM_SETTARGETDEVICE:
4207 if (wParam == 0)
4209 BOOL new = (lParam == 0 && (editor->props & TXTBIT_MULTILINE));
4210 if (editor->nAvailWidth || editor->bWordWrap != new)
4212 editor->bWordWrap = new;
4213 editor->nAvailWidth = 0; /* wrap to client area */
4214 ME_RewrapRepaint(editor);
4216 } else {
4217 int width = max(0, lParam);
4218 if ((editor->props & TXTBIT_MULTILINE) &&
4219 (!editor->bWordWrap || editor->nAvailWidth != width))
4221 editor->nAvailWidth = width;
4222 editor->bWordWrap = TRUE;
4223 ME_RewrapRepaint(editor);
4225 FIXME("EM_SETTARGETDEVICE doesn't use non-NULL target devices\n");
4227 return TRUE;
4228 default:
4229 do_default:
4230 *phresult = S_FALSE;
4231 break;
4233 return 0L;
4236 /* Fill buffer with srcChars unicode characters from the start cursor.
4238 * buffer: destination buffer
4239 * buflen: length of buffer in characters excluding the NULL terminator.
4240 * start: start of editor text to copy into buffer.
4241 * srcChars: Number of characters to use from the editor text.
4242 * bCRLF: if true, replaces all end of lines with \r\n pairs.
4244 * returns the number of characters written excluding the NULL terminator.
4246 * The written text is always NULL terminated.
4248 int ME_GetTextW(ME_TextEditor *editor, WCHAR *buffer, int buflen,
4249 const ME_Cursor *start, int srcChars, BOOL bCRLF,
4250 BOOL bEOP)
4252 ME_Run *run, *next_run;
4253 const WCHAR *pStart = buffer;
4254 const WCHAR *str;
4255 int nLen;
4257 /* bCRLF flag is only honored in 2.0 and up. 1.0 must always return text verbatim */
4258 if (editor->bEmulateVersion10) bCRLF = FALSE;
4260 run = start->run;
4261 next_run = run_next_all_paras( run );
4263 nLen = run->len - start->nOffset;
4264 str = get_text( run, start->nOffset );
4266 while (srcChars && buflen && next_run)
4268 if (bCRLF && run->nFlags & MERF_ENDPARA && ~run->nFlags & MERF_ENDCELL)
4270 if (buflen == 1) break;
4271 /* FIXME: native fails to reduce srcChars here for WM_GETTEXT or
4272 * EM_GETTEXTEX, however, this is done for copying text which
4273 * also uses this function. */
4274 srcChars -= min(nLen, srcChars);
4275 nLen = 2;
4276 str = L"\r\n";
4278 else
4280 nLen = min(nLen, srcChars);
4281 srcChars -= nLen;
4284 nLen = min(nLen, buflen);
4285 buflen -= nLen;
4287 CopyMemory(buffer, str, sizeof(WCHAR) * nLen);
4289 buffer += nLen;
4291 run = next_run;
4292 next_run = run_next_all_paras( run );
4294 nLen = run->len;
4295 str = get_text( run, 0 );
4297 /* append '\r' to the last paragraph. */
4298 if (run == para_end_run( para_prev( editor_end_para( editor ) ) ) && bEOP)
4300 *buffer = '\r';
4301 buffer ++;
4303 *buffer = 0;
4304 return buffer - pStart;
4307 static int __cdecl wchar_comp( const void *key, const void *elem )
4309 return *(const WCHAR *)key - *(const WCHAR *)elem;
4312 /* neutral characters end the url if the next non-neutral character is a space character,
4313 otherwise they are included in the url. */
4314 static BOOL isurlneutral( WCHAR c )
4316 /* NB this list is sorted */
4317 static const WCHAR neutral_chars[] = L"!\"'(),-.:;<>?[]{}";
4319 /* Some shortcuts */
4320 if (isalnum( c )) return FALSE;
4321 if (c > L'}') return FALSE;
4323 return !!bsearch( &c, neutral_chars, ARRAY_SIZE( neutral_chars ) - 1, sizeof(c), wchar_comp );
4327 * This proc takes a selection, and scans it forward in order to select the span
4328 * of a possible URL candidate. A possible URL candidate must start with isalnum
4329 * or one of the following special characters: *|/\+%#@ and must consist entirely
4330 * of the characters allowed to start the URL, plus : (colon) which may occur
4331 * at most once, and not at either end.
4333 static BOOL ME_FindNextURLCandidate(ME_TextEditor *editor,
4334 const ME_Cursor *start,
4335 int nChars,
4336 ME_Cursor *candidate_min,
4337 ME_Cursor *candidate_max)
4339 ME_Cursor cursor = *start, neutral_end, space_end;
4340 BOOL candidateStarted = FALSE, quoted = FALSE;
4341 WCHAR c;
4343 while (nChars > 0)
4345 WCHAR *str = get_text( cursor.run, 0 );
4346 int run_len = cursor.run->len;
4348 nChars -= run_len - cursor.nOffset;
4350 /* Find start of candidate */
4351 if (!candidateStarted)
4353 while (cursor.nOffset < run_len)
4355 c = str[cursor.nOffset];
4356 if (!iswspace( c ) && !isurlneutral( c ))
4358 *candidate_min = cursor;
4359 candidateStarted = TRUE;
4360 neutral_end.para = NULL;
4361 space_end.para = NULL;
4362 cursor.nOffset++;
4363 break;
4365 quoted = (c == '<');
4366 cursor.nOffset++;
4370 /* Find end of candidate */
4371 if (candidateStarted)
4373 while (cursor.nOffset < run_len)
4375 c = str[cursor.nOffset];
4376 if (iswspace( c ))
4378 if (quoted && c != '\r')
4380 if (!space_end.para)
4382 if (neutral_end.para)
4383 space_end = neutral_end;
4384 else
4385 space_end = cursor;
4388 else
4389 goto done;
4391 else if (isurlneutral( c ))
4393 if (quoted && c == '>')
4395 neutral_end.para = NULL;
4396 space_end.para = NULL;
4397 goto done;
4399 if (!neutral_end.para)
4400 neutral_end = cursor;
4402 else
4403 neutral_end.para = NULL;
4405 cursor.nOffset++;
4409 cursor.nOffset = 0;
4410 if (!cursor_next_run( &cursor, TRUE ))
4411 goto done;
4414 done:
4415 if (candidateStarted)
4417 if (space_end.para)
4418 *candidate_max = space_end;
4419 else if (neutral_end.para)
4420 *candidate_max = neutral_end;
4421 else
4422 *candidate_max = cursor;
4423 return TRUE;
4425 *candidate_max = *candidate_min = cursor;
4426 return FALSE;
4430 * This proc evaluates the selection and returns TRUE if it can be considered an URL
4432 static BOOL ME_IsCandidateAnURL(ME_TextEditor *editor, const ME_Cursor *start, int nChars)
4434 #define MAX_PREFIX_LEN 9
4435 #define X(str) str, ARRAY_SIZE(str) - 1
4436 struct prefix_s {
4437 const WCHAR text[MAX_PREFIX_LEN];
4438 int length;
4439 }prefixes[] = {
4440 {X(L"prospero:")},
4441 {X(L"telnet:")},
4442 {X(L"gopher:")},
4443 {X(L"mailto:")},
4444 {X(L"https:")},
4445 {X(L"file:")},
4446 {X(L"news:")},
4447 {X(L"wais:")},
4448 {X(L"nntp:")},
4449 {X(L"http:")},
4450 {X(L"www.")},
4451 {X(L"ftp:")},
4453 #undef X
4454 WCHAR bufferW[MAX_PREFIX_LEN + 1];
4455 unsigned int i;
4457 ME_GetTextW(editor, bufferW, MAX_PREFIX_LEN, start, nChars, FALSE, FALSE);
4458 for (i = 0; i < ARRAY_SIZE(prefixes); i++)
4460 if (nChars < prefixes[i].length) continue;
4461 if (!memcmp(prefixes[i].text, bufferW, prefixes[i].length * sizeof(WCHAR)))
4462 return TRUE;
4464 return FALSE;
4465 #undef MAX_PREFIX_LEN
4469 * This proc walks through the indicated selection and evaluates whether each
4470 * section identified by ME_FindNextURLCandidate and in-between sections have
4471 * their proper CFE_LINK attributes set or unset. If the CFE_LINK attribute is
4472 * not what it is supposed to be, this proc sets or unsets it as appropriate.
4474 * Since this function can cause runs to be split, do not depend on the value
4475 * of the start cursor at the end of the function.
4477 * nChars may be set to INT_MAX to update to the end of the text.
4479 * Returns TRUE if at least one section was modified.
4481 static BOOL ME_UpdateLinkAttribute(ME_TextEditor *editor, ME_Cursor *start, int nChars)
4483 BOOL modified = FALSE;
4484 ME_Cursor startCur = *start;
4486 if (!editor->AutoURLDetect_bEnable) return FALSE;
4490 CHARFORMAT2W link;
4491 ME_Cursor candidateStart, candidateEnd;
4493 if (ME_FindNextURLCandidate(editor, &startCur, nChars,
4494 &candidateStart, &candidateEnd))
4496 /* Section before candidate is not an URL */
4497 int cMin = ME_GetCursorOfs(&candidateStart);
4498 int cMax = ME_GetCursorOfs(&candidateEnd);
4500 if (!ME_IsCandidateAnURL(editor, &candidateStart, cMax - cMin))
4501 candidateStart = candidateEnd;
4502 nChars -= cMax - ME_GetCursorOfs(&startCur);
4504 else
4506 /* No more candidates until end of selection */
4507 nChars = 0;
4510 if (startCur.run != candidateStart.run ||
4511 startCur.nOffset != candidateStart.nOffset)
4513 /* CFE_LINK effect should be consistently unset */
4514 link.cbSize = sizeof(link);
4515 ME_GetCharFormat(editor, &startCur, &candidateStart, &link);
4516 if (!(link.dwMask & CFM_LINK) || (link.dwEffects & CFE_LINK))
4518 /* CFE_LINK must be unset from this range */
4519 memset(&link, 0, sizeof(CHARFORMAT2W));
4520 link.cbSize = sizeof(link);
4521 link.dwMask = CFM_LINK;
4522 link.dwEffects = 0;
4523 ME_SetCharFormat(editor, &startCur, &candidateStart, &link);
4524 /* Update candidateEnd since setting character formats may split
4525 * runs, which can cause a cursor to be at an invalid offset within
4526 * a split run. */
4527 while (candidateEnd.nOffset >= candidateEnd.run->len)
4529 candidateEnd.nOffset -= candidateEnd.run->len;
4530 candidateEnd.run = run_next_all_paras( candidateEnd.run );
4532 modified = TRUE;
4535 if (candidateStart.run != candidateEnd.run ||
4536 candidateStart.nOffset != candidateEnd.nOffset)
4538 /* CFE_LINK effect should be consistently set */
4539 link.cbSize = sizeof(link);
4540 ME_GetCharFormat(editor, &candidateStart, &candidateEnd, &link);
4541 if (!(link.dwMask & CFM_LINK) || !(link.dwEffects & CFE_LINK))
4543 /* CFE_LINK must be set on this range */
4544 memset(&link, 0, sizeof(CHARFORMAT2W));
4545 link.cbSize = sizeof(link);
4546 link.dwMask = CFM_LINK;
4547 link.dwEffects = CFE_LINK;
4548 ME_SetCharFormat(editor, &candidateStart, &candidateEnd, &link);
4549 modified = TRUE;
4552 startCur = candidateEnd;
4553 } while (nChars > 0);
4554 return modified;