mfplat: Add MFCreateAMMediaTypeFromMFMediaType stub.
[wine.git] / dlls / riched20 / editor.c
blob4b246dda9c539ce7188bda8a6e0a5e5873220f9b
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 #include "editor.h"
228 #include "commdlg.h"
229 #include "winreg.h"
230 #define NO_SHLWAPI_STREAM
231 #include "shlwapi.h"
232 #include "rtf.h"
233 #include "imm.h"
234 #include "res.h"
236 #define STACK_SIZE_DEFAULT 100
237 #define STACK_SIZE_MAX 1000
239 #define TEXT_LIMIT_DEFAULT 32767
241 WINE_DEFAULT_DEBUG_CHANNEL(richedit);
243 static BOOL ME_UpdateLinkAttribute(ME_TextEditor *editor, ME_Cursor *start, int nChars);
245 HINSTANCE dll_instance = NULL;
246 BOOL me_debug = FALSE;
248 static ME_TextBuffer *ME_MakeText(void) {
249 ME_TextBuffer *buf = malloc(sizeof(*buf));
250 ME_DisplayItem *p1 = ME_MakeDI(diTextStart);
251 ME_DisplayItem *p2 = ME_MakeDI(diTextEnd);
253 p1->prev = NULL;
254 p1->next = p2;
255 p2->prev = p1;
256 p2->next = NULL;
257 p1->member.para.next_para = p2;
258 p2->member.para.prev_para = p1;
259 p2->member.para.nCharOfs = 0;
261 buf->pFirst = p1;
262 buf->pLast = p2;
263 buf->pCharStyle = NULL;
265 return buf;
268 ME_Paragraph *editor_first_para( ME_TextEditor *editor )
270 return para_next( &editor->pBuffer->pFirst->member.para );
273 /* Note, returns the diTextEnd sentinel paragraph */
274 ME_Paragraph *editor_end_para( ME_TextEditor *editor )
276 return &editor->pBuffer->pLast->member.para;
279 static BOOL editor_beep( ME_TextEditor *editor, UINT type )
281 return editor->props & TXTBIT_ALLOWBEEP && MessageBeep( type );
284 static LRESULT ME_StreamInText(ME_TextEditor *editor, DWORD dwFormat, ME_InStream *stream, ME_Style *style)
286 WCHAR *pText;
287 LRESULT total_bytes_read = 0;
288 BOOL is_read = FALSE;
289 DWORD cp = CP_ACP, copy = 0;
290 char conv_buf[4 + STREAMIN_BUFFER_SIZE]; /* up to 4 additional UTF-8 bytes */
292 static const char bom_utf8[] = {0xEF, 0xBB, 0xBF};
294 TRACE("%08lx %p\n", dwFormat, stream);
296 do {
297 LONG nWideChars = 0;
298 WCHAR wszText[STREAMIN_BUFFER_SIZE+1];
300 if (!stream->dwSize)
302 ME_StreamInFill(stream);
303 if (stream->editstream->dwError)
304 break;
305 if (!stream->dwSize)
306 break;
307 total_bytes_read += stream->dwSize;
310 if (!(dwFormat & SF_UNICODE))
312 char * buf = stream->buffer;
313 DWORD size = stream->dwSize, end;
315 if (!is_read)
317 is_read = TRUE;
318 if (stream->dwSize >= 3 && !memcmp(stream->buffer, bom_utf8, 3))
320 cp = CP_UTF8;
321 buf += 3;
322 size -= 3;
326 if (cp == CP_UTF8)
328 if (copy)
330 memcpy(conv_buf + copy, buf, size);
331 buf = conv_buf;
332 size += copy;
334 end = size;
335 while ((buf[end-1] & 0xC0) == 0x80)
337 --end;
338 --total_bytes_read; /* strange, but seems to match windows */
340 if (buf[end-1] & 0x80)
342 DWORD need = 0;
343 if ((buf[end-1] & 0xE0) == 0xC0)
344 need = 1;
345 if ((buf[end-1] & 0xF0) == 0xE0)
346 need = 2;
347 if ((buf[end-1] & 0xF8) == 0xF0)
348 need = 3;
350 if (size - end >= need)
352 /* we have enough bytes for this sequence */
353 end = size;
355 else
357 /* need more bytes, so don't transcode this sequence */
358 --end;
362 else
363 end = size;
365 nWideChars = MultiByteToWideChar(cp, 0, buf, end, wszText, STREAMIN_BUFFER_SIZE);
366 pText = wszText;
368 if (cp == CP_UTF8)
370 if (end != size)
372 memcpy(conv_buf, buf + end, size - end);
373 copy = size - end;
377 else
379 nWideChars = stream->dwSize >> 1;
380 pText = (WCHAR *)stream->buffer;
383 ME_InsertTextFromCursor(editor, 0, pText, nWideChars, style);
384 if (stream->dwSize == 0)
385 break;
386 stream->dwSize = 0;
387 } while(1);
388 return total_bytes_read;
391 static void ME_ApplyBorderProperties(RTF_Info *info,
392 ME_BorderRect *borderRect,
393 RTFBorder *borderDef)
395 int i, colorNum;
396 ME_Border *pBorders[] = {&borderRect->top,
397 &borderRect->left,
398 &borderRect->bottom,
399 &borderRect->right};
400 for (i = 0; i < 4; i++)
402 RTFColor *colorDef = info->colorList;
403 pBorders[i]->width = borderDef[i].width;
404 colorNum = borderDef[i].color;
405 while (colorDef && colorDef->rtfCNum != colorNum)
406 colorDef = colorDef->rtfNextColor;
407 if (colorDef)
408 pBorders[i]->colorRef = RGB(
409 colorDef->rtfCRed >= 0 ? colorDef->rtfCRed : 0,
410 colorDef->rtfCGreen >= 0 ? colorDef->rtfCGreen : 0,
411 colorDef->rtfCBlue >= 0 ? colorDef->rtfCBlue : 0);
412 else
413 pBorders[i]->colorRef = RGB(0, 0, 0);
417 void ME_RTFCharAttrHook(RTF_Info *info)
419 CHARFORMAT2W fmt;
420 fmt.cbSize = sizeof(fmt);
421 fmt.dwMask = 0;
422 fmt.dwEffects = 0;
424 switch(info->rtfMinor)
426 case rtfPlain:
427 /* FIXME add more flags once they're implemented */
428 fmt.dwMask = CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE | CFM_UNDERLINETYPE | CFM_STRIKEOUT |
429 CFM_COLOR | CFM_BACKCOLOR | CFM_SIZE | CFM_WEIGHT;
430 fmt.dwEffects = CFE_AUTOCOLOR | CFE_AUTOBACKCOLOR;
431 fmt.yHeight = 12*20; /* 12pt */
432 fmt.wWeight = FW_NORMAL;
433 fmt.bUnderlineType = CFU_UNDERLINE;
434 break;
435 case rtfBold:
436 fmt.dwMask = CFM_BOLD | CFM_WEIGHT;
437 fmt.dwEffects = info->rtfParam ? CFE_BOLD : 0;
438 fmt.wWeight = info->rtfParam ? FW_BOLD : FW_NORMAL;
439 break;
440 case rtfItalic:
441 fmt.dwMask = CFM_ITALIC;
442 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
443 break;
444 case rtfUnderline:
445 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
446 fmt.bUnderlineType = CFU_UNDERLINE;
447 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
448 break;
449 case rtfDotUnderline:
450 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
451 fmt.bUnderlineType = CFU_UNDERLINEDOTTED;
452 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
453 break;
454 case rtfDbUnderline:
455 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
456 fmt.bUnderlineType = CFU_UNDERLINEDOUBLE;
457 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
458 break;
459 case rtfWordUnderline:
460 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
461 fmt.bUnderlineType = CFU_UNDERLINEWORD;
462 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
463 break;
464 case rtfNoUnderline:
465 fmt.dwMask = CFM_UNDERLINE;
466 fmt.dwEffects = 0;
467 break;
468 case rtfStrikeThru:
469 fmt.dwMask = CFM_STRIKEOUT;
470 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
471 break;
472 case rtfSubScript:
473 case rtfSuperScript:
474 case rtfSubScrShrink:
475 case rtfSuperScrShrink:
476 case rtfNoSuperSub:
477 fmt.dwMask = CFM_SUBSCRIPT|CFM_SUPERSCRIPT;
478 if (info->rtfMinor == rtfSubScrShrink) fmt.dwEffects = CFE_SUBSCRIPT;
479 if (info->rtfMinor == rtfSuperScrShrink) fmt.dwEffects = CFE_SUPERSCRIPT;
480 if (info->rtfMinor == rtfNoSuperSub) fmt.dwEffects = 0;
481 break;
482 case rtfInvisible:
483 fmt.dwMask = CFM_HIDDEN;
484 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
485 break;
486 case rtfBackColor:
487 fmt.dwMask = CFM_BACKCOLOR;
488 fmt.dwEffects = 0;
489 if (info->rtfParam == 0)
490 fmt.dwEffects = CFE_AUTOBACKCOLOR;
491 else if (info->rtfParam != rtfNoParam)
493 RTFColor *c = RTFGetColor(info, info->rtfParam);
494 if (c && c->rtfCBlue >= 0)
495 fmt.crBackColor = (c->rtfCBlue<<16)|(c->rtfCGreen<<8)|(c->rtfCRed);
496 else
497 fmt.dwEffects = CFE_AUTOBACKCOLOR;
499 break;
500 case rtfForeColor:
501 fmt.dwMask = CFM_COLOR;
502 fmt.dwEffects = 0;
503 if (info->rtfParam == 0)
504 fmt.dwEffects = CFE_AUTOCOLOR;
505 else if (info->rtfParam != rtfNoParam)
507 RTFColor *c = RTFGetColor(info, info->rtfParam);
508 if (c && c->rtfCBlue >= 0)
509 fmt.crTextColor = (c->rtfCBlue<<16)|(c->rtfCGreen<<8)|(c->rtfCRed);
510 else {
511 fmt.dwEffects = CFE_AUTOCOLOR;
514 break;
515 case rtfFontNum:
516 if (info->rtfParam != rtfNoParam)
518 RTFFont *f = RTFGetFont(info, info->rtfParam);
519 if (f)
521 MultiByteToWideChar(CP_ACP, 0, f->rtfFName, -1, fmt.szFaceName, ARRAY_SIZE(fmt.szFaceName));
522 fmt.szFaceName[ARRAY_SIZE(fmt.szFaceName)-1] = '\0';
523 fmt.bCharSet = f->rtfFCharSet;
524 fmt.dwMask = CFM_FACE | CFM_CHARSET;
525 fmt.bPitchAndFamily = f->rtfFPitch | (f->rtfFFamily << 4);
528 break;
529 case rtfFontSize:
530 fmt.dwMask = CFM_SIZE;
531 if (info->rtfParam != rtfNoParam)
532 fmt.yHeight = info->rtfParam*10;
533 break;
535 if (fmt.dwMask) {
536 ME_Style *style2;
537 RTFFlushOutputBuffer(info);
538 /* FIXME too slow ? how come ? */
539 style2 = ME_ApplyStyle(info->editor, info->style, &fmt);
540 ME_ReleaseStyle(info->style);
541 info->style = style2;
542 info->styleChanged = TRUE;
546 /* FIXME this function doesn't get any information about context of the RTF tag, which is very bad,
547 the same tags mean different things in different contexts */
548 void ME_RTFParAttrHook(RTF_Info *info)
550 switch(info->rtfMinor)
552 case rtfParDef: /* restores default paragraph attributes */
553 if (!info->editor->bEmulateVersion10) /* v4.1 */
554 info->borderType = RTFBorderParaLeft;
555 else /* v1.0 - 3.0 */
556 info->borderType = RTFBorderParaTop;
557 info->fmt.dwMask = PFM_ALIGNMENT | PFM_BORDER | PFM_LINESPACING | PFM_TABSTOPS |
558 PFM_OFFSET | PFM_RIGHTINDENT | PFM_SPACEAFTER | PFM_SPACEBEFORE |
559 PFM_STARTINDENT | PFM_RTLPARA | PFM_NUMBERING | PFM_NUMBERINGSTART |
560 PFM_NUMBERINGSTYLE | PFM_NUMBERINGTAB;
561 /* TODO: shading */
562 info->fmt.wAlignment = PFA_LEFT;
563 info->fmt.cTabCount = 0;
564 info->fmt.dxOffset = info->fmt.dxStartIndent = info->fmt.dxRightIndent = 0;
565 info->fmt.wBorderWidth = info->fmt.wBorders = 0;
566 info->fmt.wBorderSpace = 0;
567 info->fmt.bLineSpacingRule = 0;
568 info->fmt.dySpaceBefore = info->fmt.dySpaceAfter = 0;
569 info->fmt.dyLineSpacing = 0;
570 info->fmt.wEffects &= ~PFE_RTLPARA;
571 info->fmt.wNumbering = 0;
572 info->fmt.wNumberingStart = 0;
573 info->fmt.wNumberingStyle = 0;
574 info->fmt.wNumberingTab = 0;
576 if (!info->editor->bEmulateVersion10) /* v4.1 */
578 if (info->tableDef && info->tableDef->row_start &&
579 info->tableDef->row_start->nFlags & MEPF_ROWEND)
581 ME_Cursor cursor;
582 ME_Paragraph *para;
583 /* We are just after a table row. */
584 RTFFlushOutputBuffer(info);
585 cursor = info->editor->pCursors[0];
586 para = cursor.para;
587 if (para == para_next( info->tableDef->row_start )
588 && !cursor.nOffset && !cursor.run->nCharOfs)
590 /* Since the table row end, no text has been inserted, and the \intbl
591 * control word has not be used. We can confirm that we are not in a
592 * table anymore.
594 info->tableDef->row_start = NULL;
595 info->canInheritInTbl = FALSE;
599 else /* v1.0 - v3.0 */
601 info->fmt.dwMask |= PFM_TABLE;
602 info->fmt.wEffects &= ~PFE_TABLE;
604 break;
605 case rtfNestLevel:
606 if (!info->editor->bEmulateVersion10) /* v4.1 */
608 while (info->rtfParam > info->nestingLevel)
610 RTFTable *tableDef = calloc(1, sizeof(*tableDef));
611 tableDef->parent = info->tableDef;
612 info->tableDef = tableDef;
614 RTFFlushOutputBuffer(info);
615 if (tableDef->row_start && tableDef->row_start->nFlags & MEPF_ROWEND)
617 ME_Paragraph *para = para_next( tableDef->row_start );
618 tableDef->row_start = table_insert_row_start_at_para( info->editor, para );
620 else
622 ME_Cursor cursor;
623 cursor = info->editor->pCursors[0];
624 if (cursor.nOffset || cursor.run->nCharOfs)
625 ME_InsertTextFromCursor(info->editor, 0, L"\r", 1, info->style);
626 tableDef->row_start = table_insert_row_start( info->editor, info->editor->pCursors );
629 info->nestingLevel++;
631 info->canInheritInTbl = FALSE;
633 break;
634 case rtfInTable:
636 if (!info->editor->bEmulateVersion10) /* v4.1 */
638 if (info->nestingLevel < 1)
640 RTFTable *tableDef;
641 ME_Paragraph *para;
643 if (!info->tableDef)
644 info->tableDef = calloc(1, sizeof(*info->tableDef));
645 tableDef = info->tableDef;
646 RTFFlushOutputBuffer(info);
647 if (tableDef->row_start && tableDef->row_start->nFlags & MEPF_ROWEND)
648 para = para_next( tableDef->row_start );
649 else
650 para = info->editor->pCursors[0].para;
652 tableDef->row_start = table_insert_row_start_at_para( info->editor, para );
654 info->nestingLevel = 1;
655 info->canInheritInTbl = TRUE;
657 return;
658 } else { /* v1.0 - v3.0 */
659 info->fmt.dwMask |= PFM_TABLE;
660 info->fmt.wEffects |= PFE_TABLE;
662 break;
664 case rtfFirstIndent:
665 case rtfLeftIndent:
666 if ((info->fmt.dwMask & (PFM_STARTINDENT | PFM_OFFSET)) != (PFM_STARTINDENT | PFM_OFFSET))
668 PARAFORMAT2 fmt;
669 fmt.cbSize = sizeof(fmt);
670 editor_get_selection_para_fmt( info->editor, &fmt );
671 info->fmt.dwMask |= PFM_STARTINDENT | PFM_OFFSET;
672 info->fmt.dxStartIndent = fmt.dxStartIndent;
673 info->fmt.dxOffset = fmt.dxOffset;
675 if (info->rtfMinor == rtfFirstIndent)
677 info->fmt.dxStartIndent += info->fmt.dxOffset + info->rtfParam;
678 info->fmt.dxOffset = -info->rtfParam;
680 else
681 info->fmt.dxStartIndent = info->rtfParam - info->fmt.dxOffset;
682 break;
683 case rtfRightIndent:
684 info->fmt.dwMask |= PFM_RIGHTINDENT;
685 info->fmt.dxRightIndent = info->rtfParam;
686 break;
687 case rtfQuadLeft:
688 case rtfQuadJust:
689 info->fmt.dwMask |= PFM_ALIGNMENT;
690 info->fmt.wAlignment = PFA_LEFT;
691 break;
692 case rtfQuadRight:
693 info->fmt.dwMask |= PFM_ALIGNMENT;
694 info->fmt.wAlignment = PFA_RIGHT;
695 break;
696 case rtfQuadCenter:
697 info->fmt.dwMask |= PFM_ALIGNMENT;
698 info->fmt.wAlignment = PFA_CENTER;
699 break;
700 case rtfTabPos:
701 if (!(info->fmt.dwMask & PFM_TABSTOPS))
703 PARAFORMAT2 fmt;
704 fmt.cbSize = sizeof(fmt);
705 editor_get_selection_para_fmt( info->editor, &fmt );
706 memcpy(info->fmt.rgxTabs, fmt.rgxTabs,
707 fmt.cTabCount * sizeof(fmt.rgxTabs[0]));
708 info->fmt.cTabCount = fmt.cTabCount;
709 info->fmt.dwMask |= PFM_TABSTOPS;
711 if (info->fmt.cTabCount < MAX_TAB_STOPS && info->rtfParam < 0x1000000)
712 info->fmt.rgxTabs[info->fmt.cTabCount++] = info->rtfParam;
713 break;
714 case rtfKeep:
715 info->fmt.dwMask |= PFM_KEEP;
716 info->fmt.wEffects |= PFE_KEEP;
717 break;
718 case rtfNoWidowControl:
719 info->fmt.dwMask |= PFM_NOWIDOWCONTROL;
720 info->fmt.wEffects |= PFE_NOWIDOWCONTROL;
721 break;
722 case rtfKeepNext:
723 info->fmt.dwMask |= PFM_KEEPNEXT;
724 info->fmt.wEffects |= PFE_KEEPNEXT;
725 break;
726 case rtfSpaceAfter:
727 info->fmt.dwMask |= PFM_SPACEAFTER;
728 info->fmt.dySpaceAfter = info->rtfParam;
729 break;
730 case rtfSpaceBefore:
731 info->fmt.dwMask |= PFM_SPACEBEFORE;
732 info->fmt.dySpaceBefore = info->rtfParam;
733 break;
734 case rtfSpaceBetween:
735 info->fmt.dwMask |= PFM_LINESPACING;
736 if ((int)info->rtfParam > 0)
738 info->fmt.dyLineSpacing = info->rtfParam;
739 info->fmt.bLineSpacingRule = 3;
741 else
743 info->fmt.dyLineSpacing = info->rtfParam;
744 info->fmt.bLineSpacingRule = 4;
746 break;
747 case rtfSpaceMultiply:
748 info->fmt.dwMask |= PFM_LINESPACING;
749 info->fmt.dyLineSpacing = info->rtfParam * 20;
750 info->fmt.bLineSpacingRule = 5;
751 break;
752 case rtfParBullet:
753 info->fmt.dwMask |= PFM_NUMBERING;
754 info->fmt.wNumbering = PFN_BULLET;
755 break;
756 case rtfParSimple:
757 info->fmt.dwMask |= PFM_NUMBERING;
758 info->fmt.wNumbering = 2; /* FIXME: MSDN says it's not used ?? */
759 break;
760 case rtfBorderLeft:
761 info->borderType = RTFBorderParaLeft;
762 info->fmt.wBorders |= 1;
763 info->fmt.dwMask |= PFM_BORDER;
764 break;
765 case rtfBorderRight:
766 info->borderType = RTFBorderParaRight;
767 info->fmt.wBorders |= 2;
768 info->fmt.dwMask |= PFM_BORDER;
769 break;
770 case rtfBorderTop:
771 info->borderType = RTFBorderParaTop;
772 info->fmt.wBorders |= 4;
773 info->fmt.dwMask |= PFM_BORDER;
774 break;
775 case rtfBorderBottom:
776 info->borderType = RTFBorderParaBottom;
777 info->fmt.wBorders |= 8;
778 info->fmt.dwMask |= PFM_BORDER;
779 break;
780 case rtfBorderSingle:
781 info->fmt.wBorders &= ~0x700;
782 info->fmt.wBorders |= 1 << 8;
783 info->fmt.dwMask |= PFM_BORDER;
784 break;
785 case rtfBorderThick:
786 info->fmt.wBorders &= ~0x700;
787 info->fmt.wBorders |= 2 << 8;
788 info->fmt.dwMask |= PFM_BORDER;
789 break;
790 case rtfBorderShadow:
791 info->fmt.wBorders &= ~0x700;
792 info->fmt.wBorders |= 10 << 8;
793 info->fmt.dwMask |= PFM_BORDER;
794 break;
795 case rtfBorderDouble:
796 info->fmt.wBorders &= ~0x700;
797 info->fmt.wBorders |= 7 << 8;
798 info->fmt.dwMask |= PFM_BORDER;
799 break;
800 case rtfBorderDot:
801 info->fmt.wBorders &= ~0x700;
802 info->fmt.wBorders |= 11 << 8;
803 info->fmt.dwMask |= PFM_BORDER;
804 break;
805 case rtfBorderWidth:
807 int borderSide = info->borderType & RTFBorderSideMask;
808 RTFTable *tableDef = info->tableDef;
809 if ((info->borderType & RTFBorderTypeMask) == RTFBorderTypeCell)
811 RTFBorder *border;
812 if (!tableDef || tableDef->numCellsDefined >= MAX_TABLE_CELLS)
813 break;
814 border = &tableDef->cells[tableDef->numCellsDefined].border[borderSide];
815 border->width = info->rtfParam;
816 break;
818 info->fmt.wBorderWidth = info->rtfParam;
819 info->fmt.dwMask |= PFM_BORDER;
820 break;
822 case rtfBorderSpace:
823 info->fmt.wBorderSpace = info->rtfParam;
824 info->fmt.dwMask |= PFM_BORDER;
825 break;
826 case rtfBorderColor:
828 RTFTable *tableDef = info->tableDef;
829 int borderSide = info->borderType & RTFBorderSideMask;
830 int borderType = info->borderType & RTFBorderTypeMask;
831 switch(borderType)
833 case RTFBorderTypePara:
834 if (!info->editor->bEmulateVersion10) /* v4.1 */
835 break;
836 /* v1.0 - 3.0 treat paragraph and row borders the same. */
837 case RTFBorderTypeRow:
838 if (tableDef) {
839 tableDef->border[borderSide].color = info->rtfParam;
841 break;
842 case RTFBorderTypeCell:
843 if (tableDef && tableDef->numCellsDefined < MAX_TABLE_CELLS) {
844 tableDef->cells[tableDef->numCellsDefined].border[borderSide].color = info->rtfParam;
846 break;
848 break;
850 case rtfRTLPar:
851 info->fmt.dwMask |= PFM_RTLPARA;
852 info->fmt.wEffects |= PFE_RTLPARA;
853 break;
854 case rtfLTRPar:
855 info->fmt.dwMask |= PFM_RTLPARA;
856 info->fmt.wEffects &= ~PFE_RTLPARA;
857 break;
861 void ME_RTFTblAttrHook(RTF_Info *info)
863 switch (info->rtfMinor)
865 case rtfRowDef:
867 if (!info->editor->bEmulateVersion10) /* v4.1 */
868 info->borderType = 0; /* Not sure */
869 else /* v1.0 - 3.0 */
870 info->borderType = RTFBorderRowTop;
871 if (!info->tableDef) {
872 info->tableDef = ME_MakeTableDef(info->editor);
873 } else {
874 ME_InitTableDef(info->editor, info->tableDef);
876 break;
878 case rtfCellPos:
880 int cellNum;
881 if (!info->tableDef)
883 info->tableDef = ME_MakeTableDef(info->editor);
885 cellNum = info->tableDef->numCellsDefined;
886 if (cellNum >= MAX_TABLE_CELLS)
887 break;
888 info->tableDef->cells[cellNum].rightBoundary = info->rtfParam;
889 if (cellNum < MAX_TAB_STOPS)
891 /* Tab stops were used to store cell positions before v4.1 but v4.1
892 * still seems to set the tabstops without using them. */
893 PARAFORMAT2 *fmt = &info->editor->pCursors[0].para->fmt;
894 fmt->rgxTabs[cellNum] &= ~0x00FFFFFF;
895 fmt->rgxTabs[cellNum] |= 0x00FFFFFF & info->rtfParam;
897 info->tableDef->numCellsDefined++;
898 break;
900 case rtfRowBordTop:
901 info->borderType = RTFBorderRowTop;
902 break;
903 case rtfRowBordLeft:
904 info->borderType = RTFBorderRowLeft;
905 break;
906 case rtfRowBordBottom:
907 info->borderType = RTFBorderRowBottom;
908 break;
909 case rtfRowBordRight:
910 info->borderType = RTFBorderRowRight;
911 break;
912 case rtfCellBordTop:
913 info->borderType = RTFBorderCellTop;
914 break;
915 case rtfCellBordLeft:
916 info->borderType = RTFBorderCellLeft;
917 break;
918 case rtfCellBordBottom:
919 info->borderType = RTFBorderCellBottom;
920 break;
921 case rtfCellBordRight:
922 info->borderType = RTFBorderCellRight;
923 break;
924 case rtfRowGapH:
925 if (info->tableDef)
926 info->tableDef->gapH = info->rtfParam;
927 break;
928 case rtfRowLeftEdge:
929 if (info->tableDef)
930 info->tableDef->leftEdge = info->rtfParam;
931 break;
935 void ME_RTFSpecialCharHook(RTF_Info *info)
937 RTFTable *tableDef = info->tableDef;
938 switch (info->rtfMinor)
940 case rtfNestCell:
941 if (info->editor->bEmulateVersion10) /* v1.0 - v3.0 */
942 break;
943 /* else fall through since v4.1 treats rtfNestCell and rtfCell the same */
944 case rtfCell:
945 if (!tableDef)
946 break;
947 RTFFlushOutputBuffer(info);
948 if (!info->editor->bEmulateVersion10) /* v4.1 */
950 if (tableDef->row_start)
952 if (!info->nestingLevel && tableDef->row_start->nFlags & MEPF_ROWEND)
954 ME_Paragraph *para = para_next( tableDef->row_start );
955 tableDef->row_start = table_insert_row_start_at_para( info->editor, para );
956 info->nestingLevel = 1;
958 table_insert_cell( info->editor, info->editor->pCursors );
961 else /* v1.0 - v3.0 */
963 ME_Paragraph *para = info->editor->pCursors[0].para;
965 if (para_in_table( para ) && tableDef->numCellsInserted < tableDef->numCellsDefined)
967 WCHAR tab = '\t';
968 ME_InsertTextFromCursor(info->editor, 0, &tab, 1, info->style);
969 tableDef->numCellsInserted++;
972 break;
973 case rtfNestRow:
974 if (info->editor->bEmulateVersion10) /* v1.0 - v3.0 */
975 break;
976 /* else fall through since v4.1 treats rtfNestRow and rtfRow the same */
977 case rtfRow:
979 ME_Run *run;
980 ME_Paragraph *para;
981 ME_Cell *cell;
982 int i;
984 if (!tableDef)
985 break;
986 RTFFlushOutputBuffer(info);
987 if (!info->editor->bEmulateVersion10) /* v4.1 */
989 if (!tableDef->row_start) break;
990 if (!info->nestingLevel && tableDef->row_start->nFlags & MEPF_ROWEND)
992 para = para_next( tableDef->row_start );
993 tableDef->row_start = table_insert_row_start_at_para( info->editor, para );
994 info->nestingLevel++;
996 para = tableDef->row_start;
997 cell = table_row_first_cell( para );
998 assert( cell && !cell_prev( cell ) );
999 if (tableDef->numCellsDefined < 1)
1001 /* 2000 twips appears to be the cell size that native richedit uses
1002 * when no cell sizes are specified. */
1003 const int default_size = 2000;
1004 int right_boundary = default_size;
1005 cell->nRightBoundary = right_boundary;
1006 while (cell_next( cell ))
1008 cell = cell_next( cell );
1009 right_boundary += default_size;
1010 cell->nRightBoundary = right_boundary;
1012 para = table_insert_cell( info->editor, info->editor->pCursors );
1013 cell = para_cell( para );
1014 cell->nRightBoundary = right_boundary;
1016 else
1018 for (i = 0; i < tableDef->numCellsDefined; i++)
1020 RTFCell *cellDef = &tableDef->cells[i];
1021 cell->nRightBoundary = cellDef->rightBoundary;
1022 ME_ApplyBorderProperties( info, &cell->border, cellDef->border );
1023 cell = cell_next( cell );
1024 if (!cell)
1026 para = table_insert_cell( info->editor, info->editor->pCursors );
1027 cell = para_cell( para );
1030 /* Cell for table row delimiter is empty */
1031 cell->nRightBoundary = tableDef->cells[i - 1].rightBoundary;
1034 run = para_first_run( cell_first_para( cell ) );
1035 if (info->editor->pCursors[0].run != run || info->editor->pCursors[0].nOffset)
1037 int nOfs, nChars;
1038 /* Delete inserted cells that aren't defined. */
1039 info->editor->pCursors[1].run = run;
1040 info->editor->pCursors[1].para = run->para;
1041 info->editor->pCursors[1].nOffset = 0;
1042 nOfs = ME_GetCursorOfs(&info->editor->pCursors[1]);
1043 nChars = ME_GetCursorOfs(&info->editor->pCursors[0]) - nOfs;
1044 ME_InternalDeleteText(info->editor, &info->editor->pCursors[1],
1045 nChars, TRUE);
1048 para = table_insert_row_end( info->editor, info->editor->pCursors );
1049 para->fmt.dxOffset = abs(info->tableDef->gapH);
1050 para->fmt.dxStartIndent = info->tableDef->leftEdge;
1051 ME_ApplyBorderProperties( info, &para->border, tableDef->border );
1052 info->nestingLevel--;
1053 if (!info->nestingLevel)
1055 if (info->canInheritInTbl) tableDef->row_start = para;
1056 else
1058 while (info->tableDef)
1060 tableDef = info->tableDef;
1061 info->tableDef = tableDef->parent;
1062 free(tableDef);
1066 else
1068 info->tableDef = tableDef->parent;
1069 free(tableDef);
1072 else /* v1.0 - v3.0 */
1074 para = info->editor->pCursors[0].para;
1075 para->fmt.dxOffset = info->tableDef->gapH;
1076 para->fmt.dxStartIndent = info->tableDef->leftEdge;
1078 ME_ApplyBorderProperties( info, &para->border, tableDef->border );
1079 while (tableDef->numCellsInserted < tableDef->numCellsDefined)
1081 WCHAR tab = '\t';
1082 ME_InsertTextFromCursor(info->editor, 0, &tab, 1, info->style);
1083 tableDef->numCellsInserted++;
1085 para->fmt.cTabCount = min(tableDef->numCellsDefined, MAX_TAB_STOPS);
1086 if (!tableDef->numCellsDefined) para->fmt.wEffects &= ~PFE_TABLE;
1087 ME_InsertTextFromCursor(info->editor, 0, L"\r", 1, info->style);
1088 tableDef->numCellsInserted = 0;
1090 break;
1092 case rtfTab:
1093 case rtfPar:
1094 if (info->editor->bEmulateVersion10) /* v1.0 - 3.0 */
1096 ME_Paragraph *para;
1098 RTFFlushOutputBuffer(info);
1099 para = info->editor->pCursors[0].para;
1100 if (para_in_table( para ))
1102 /* rtfPar is treated like a space within a table. */
1103 info->rtfClass = rtfText;
1104 info->rtfMajor = ' ';
1106 else if (info->rtfMinor == rtfPar && tableDef)
1107 tableDef->numCellsInserted = 0;
1109 break;
1113 static HRESULT insert_static_object(ME_TextEditor *editor, HENHMETAFILE hemf, HBITMAP hbmp,
1114 const SIZEL* sz)
1116 LPOLEOBJECT lpObject = NULL;
1117 LPSTORAGE lpStorage = NULL;
1118 LPOLECLIENTSITE lpClientSite = NULL;
1119 LPDATAOBJECT lpDataObject = NULL;
1120 LPOLECACHE lpOleCache = NULL;
1121 STGMEDIUM stgm;
1122 FORMATETC fm;
1123 CLSID clsid;
1124 HRESULT hr = E_FAIL;
1125 DWORD conn;
1127 if (hemf)
1129 stgm.tymed = TYMED_ENHMF;
1130 stgm.hEnhMetaFile = hemf;
1131 fm.cfFormat = CF_ENHMETAFILE;
1133 else if (hbmp)
1135 stgm.tymed = TYMED_GDI;
1136 stgm.hBitmap = hbmp;
1137 fm.cfFormat = CF_BITMAP;
1139 else return E_FAIL;
1141 stgm.pUnkForRelease = NULL;
1143 fm.ptd = NULL;
1144 fm.dwAspect = DVASPECT_CONTENT;
1145 fm.lindex = -1;
1146 fm.tymed = stgm.tymed;
1148 if (OleCreateDefaultHandler(&CLSID_NULL, NULL, &IID_IOleObject, (void**)&lpObject) == S_OK &&
1149 IRichEditOle_GetClientSite(editor->richole, &lpClientSite) == S_OK &&
1150 IOleObject_SetClientSite(lpObject, lpClientSite) == S_OK &&
1151 IOleObject_GetUserClassID(lpObject, &clsid) == S_OK &&
1152 IOleObject_QueryInterface(lpObject, &IID_IOleCache, (void**)&lpOleCache) == S_OK &&
1153 IOleCache_Cache(lpOleCache, &fm, 0, &conn) == S_OK &&
1154 IOleObject_QueryInterface(lpObject, &IID_IDataObject, (void**)&lpDataObject) == S_OK &&
1155 IDataObject_SetData(lpDataObject, &fm, &stgm, TRUE) == S_OK)
1157 REOBJECT reobject;
1159 reobject.cbStruct = sizeof(reobject);
1160 reobject.cp = REO_CP_SELECTION;
1161 reobject.clsid = clsid;
1162 reobject.poleobj = lpObject;
1163 reobject.pstg = lpStorage;
1164 reobject.polesite = lpClientSite;
1165 /* convert from twips to .01 mm */
1166 reobject.sizel.cx = MulDiv(sz->cx, 254, 144);
1167 reobject.sizel.cy = MulDiv(sz->cy, 254, 144);
1168 reobject.dvaspect = DVASPECT_CONTENT;
1169 reobject.dwFlags = 0; /* FIXME */
1170 reobject.dwUser = 0;
1172 hr = editor_insert_oleobj(editor, &reobject);
1175 if (lpObject) IOleObject_Release(lpObject);
1176 if (lpClientSite) IOleClientSite_Release(lpClientSite);
1177 if (lpStorage) IStorage_Release(lpStorage);
1178 if (lpDataObject) IDataObject_Release(lpDataObject);
1179 if (lpOleCache) IOleCache_Release(lpOleCache);
1181 return hr;
1184 static void ME_RTFReadShpPictGroup( RTF_Info *info )
1186 int level = 1;
1188 for (;;)
1190 RTFGetToken (info);
1192 if (info->rtfClass == rtfEOF) return;
1193 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1195 if (--level == 0) break;
1197 else if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1199 level++;
1201 else
1203 RTFRouteToken( info );
1204 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1205 level--;
1209 RTFRouteToken( info ); /* feed "}" back to router */
1210 return;
1213 static DWORD read_hex_data( RTF_Info *info, BYTE **out )
1215 DWORD read = 0, size = 1024;
1216 BYTE *buf, val;
1217 BOOL flip;
1219 *out = NULL;
1221 if (info->rtfClass != rtfText)
1223 ERR("Called with incorrect token\n");
1224 return 0;
1227 buf = malloc(size);
1228 if (!buf) return 0;
1230 val = info->rtfMajor;
1231 for (flip = TRUE;; flip = !flip)
1233 RTFGetToken( info );
1234 if (info->rtfClass == rtfEOF)
1236 free(buf);
1237 return 0;
1239 if (info->rtfClass != rtfText) break;
1240 if (flip)
1242 if (read >= size)
1244 size *= 2;
1245 buf = realloc(buf, size);
1246 if (!buf) return 0;
1248 buf[read++] = RTFCharToHex(val) * 16 + RTFCharToHex(info->rtfMajor);
1250 else
1251 val = info->rtfMajor;
1253 if (flip) FIXME("wrong hex string\n");
1255 *out = buf;
1256 return read;
1259 static void ME_RTFReadPictGroup(RTF_Info *info)
1261 SIZEL sz;
1262 BYTE *buffer = NULL;
1263 DWORD size = 0;
1264 METAFILEPICT mfp;
1265 HENHMETAFILE hemf;
1266 HBITMAP hbmp;
1267 enum gfxkind {gfx_unknown = 0, gfx_enhmetafile, gfx_metafile, gfx_dib} gfx = gfx_unknown;
1268 int level = 1;
1270 mfp.mm = MM_TEXT;
1271 sz.cx = sz.cy = 0;
1273 for (;;)
1275 RTFGetToken( info );
1277 if (info->rtfClass == rtfText)
1279 if (level == 1)
1281 if (!buffer)
1282 size = read_hex_data( info, &buffer );
1284 else
1286 RTFSkipGroup( info );
1288 } /* We potentially have a new token so fall through. */
1290 if (info->rtfClass == rtfEOF) return;
1292 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1294 if (--level == 0) break;
1295 continue;
1297 if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1299 level++;
1300 continue;
1302 if (!RTFCheckCM( info, rtfControl, rtfPictAttr ))
1304 RTFRouteToken( info );
1305 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1306 level--;
1307 continue;
1310 if (RTFCheckMM( info, rtfPictAttr, rtfWinMetafile ))
1312 mfp.mm = info->rtfParam;
1313 gfx = gfx_metafile;
1315 else if (RTFCheckMM( info, rtfPictAttr, rtfDevIndBitmap ))
1317 if (info->rtfParam != 0) FIXME("dibitmap should be 0 (%d)\n", info->rtfParam);
1318 gfx = gfx_dib;
1320 else if (RTFCheckMM( info, rtfPictAttr, rtfEmfBlip ))
1321 gfx = gfx_enhmetafile;
1322 else if (RTFCheckMM( info, rtfPictAttr, rtfPicWid ))
1323 mfp.xExt = info->rtfParam;
1324 else if (RTFCheckMM( info, rtfPictAttr, rtfPicHt ))
1325 mfp.yExt = info->rtfParam;
1326 else if (RTFCheckMM( info, rtfPictAttr, rtfPicGoalWid ))
1327 sz.cx = info->rtfParam;
1328 else if (RTFCheckMM( info, rtfPictAttr, rtfPicGoalHt ))
1329 sz.cy = info->rtfParam;
1330 else
1331 FIXME("Non supported attribute: %d %d %d\n", info->rtfClass, info->rtfMajor, info->rtfMinor);
1334 if (buffer)
1336 switch (gfx)
1338 case gfx_enhmetafile:
1339 if ((hemf = SetEnhMetaFileBits( size, buffer )))
1340 insert_static_object( info->editor, hemf, NULL, &sz );
1341 break;
1342 case gfx_metafile:
1343 if ((hemf = SetWinMetaFileBits( size, buffer, NULL, &mfp )))
1344 insert_static_object( info->editor, hemf, NULL, &sz );
1345 break;
1346 case gfx_dib:
1348 BITMAPINFO *bi = (BITMAPINFO*)buffer;
1349 HDC hdc = GetDC(0);
1350 unsigned nc = bi->bmiHeader.biClrUsed;
1352 /* not quite right, especially for bitfields type of compression */
1353 if (!nc && bi->bmiHeader.biBitCount <= 8)
1354 nc = 1 << bi->bmiHeader.biBitCount;
1355 if ((hbmp = CreateDIBitmap( hdc, &bi->bmiHeader,
1356 CBM_INIT, (char*)(bi + 1) + nc * sizeof(RGBQUAD),
1357 bi, DIB_RGB_COLORS)) )
1358 insert_static_object( info->editor, NULL, hbmp, &sz );
1359 ReleaseDC( 0, hdc );
1360 break;
1362 default:
1363 break;
1366 free( buffer );
1367 RTFRouteToken( info ); /* feed "}" back to router */
1368 return;
1371 /* for now, lookup the \result part and use it, whatever the object */
1372 static void ME_RTFReadObjectGroup(RTF_Info *info)
1374 for (;;)
1376 RTFGetToken (info);
1377 if (info->rtfClass == rtfEOF)
1378 return;
1379 if (RTFCheckCM(info, rtfGroup, rtfEndGroup))
1380 break;
1381 if (RTFCheckCM(info, rtfGroup, rtfBeginGroup))
1383 RTFGetToken (info);
1384 if (info->rtfClass == rtfEOF)
1385 return;
1386 if (RTFCheckCMM(info, rtfControl, rtfDestination, rtfObjResult))
1388 int level = 1;
1390 while (RTFGetToken (info) != rtfEOF)
1392 if (info->rtfClass == rtfGroup)
1394 if (info->rtfMajor == rtfBeginGroup) level++;
1395 else if (info->rtfMajor == rtfEndGroup && --level < 0) break;
1397 RTFRouteToken(info);
1400 else RTFSkipGroup(info);
1401 continue;
1403 if (!RTFCheckCM (info, rtfControl, rtfObjAttr))
1405 FIXME("Non supported attribute: %d %d %d\n", info->rtfClass, info->rtfMajor, info->rtfMinor);
1406 return;
1409 RTFRouteToken(info); /* feed "}" back to router */
1412 static void ME_RTFReadParnumGroup( RTF_Info *info )
1414 int level = 1, type = -1;
1415 WORD indent = 0, start = 1;
1416 WCHAR txt_before = 0, txt_after = 0;
1418 for (;;)
1420 RTFGetToken( info );
1422 if (RTFCheckCMM( info, rtfControl, rtfDestination, rtfParNumTextBefore ) ||
1423 RTFCheckCMM( info, rtfControl, rtfDestination, rtfParNumTextAfter ))
1425 int loc = info->rtfMinor;
1427 RTFGetToken( info );
1428 if (info->rtfClass == rtfText)
1430 if (loc == rtfParNumTextBefore)
1431 txt_before = info->rtfMajor;
1432 else
1433 txt_after = info->rtfMajor;
1434 continue;
1436 /* falling through to catch EOFs and group level changes */
1439 if (info->rtfClass == rtfEOF)
1440 return;
1442 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1444 if (--level == 0) break;
1445 continue;
1448 if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1450 level++;
1451 continue;
1454 /* Ignore non para-attr */
1455 if (!RTFCheckCM( info, rtfControl, rtfParAttr ))
1456 continue;
1458 switch (info->rtfMinor)
1460 case rtfParLevel: /* Para level is ignored */
1461 case rtfParSimple:
1462 break;
1463 case rtfParBullet:
1464 type = PFN_BULLET;
1465 break;
1467 case rtfParNumDecimal:
1468 type = PFN_ARABIC;
1469 break;
1470 case rtfParNumULetter:
1471 type = PFN_UCLETTER;
1472 break;
1473 case rtfParNumURoman:
1474 type = PFN_UCROMAN;
1475 break;
1476 case rtfParNumLLetter:
1477 type = PFN_LCLETTER;
1478 break;
1479 case rtfParNumLRoman:
1480 type = PFN_LCROMAN;
1481 break;
1483 case rtfParNumIndent:
1484 indent = info->rtfParam;
1485 break;
1486 case rtfParNumStartAt:
1487 start = info->rtfParam;
1488 break;
1492 if (type != -1)
1494 info->fmt.dwMask |= (PFM_NUMBERING | PFM_NUMBERINGSTART | PFM_NUMBERINGSTYLE | PFM_NUMBERINGTAB);
1495 info->fmt.wNumbering = type;
1496 info->fmt.wNumberingStart = start;
1497 info->fmt.wNumberingStyle = PFNS_PAREN;
1498 if (type != PFN_BULLET)
1500 if (txt_before == 0 && txt_after == 0)
1501 info->fmt.wNumberingStyle = PFNS_PLAIN;
1502 else if (txt_after == '.')
1503 info->fmt.wNumberingStyle = PFNS_PERIOD;
1504 else if (txt_before == '(' && txt_after == ')')
1505 info->fmt.wNumberingStyle = PFNS_PARENS;
1507 info->fmt.wNumberingTab = indent;
1510 TRACE("type %d indent %d start %d txt before %04x txt after %04x\n",
1511 type, indent, start, txt_before, txt_after);
1513 RTFRouteToken( info ); /* feed "}" back to router */
1516 static void ME_RTFReadHook(RTF_Info *info)
1518 switch(info->rtfClass)
1520 case rtfGroup:
1521 switch(info->rtfMajor)
1523 case rtfBeginGroup:
1524 if (info->stackTop < maxStack) {
1525 info->stack[info->stackTop].style = info->style;
1526 ME_AddRefStyle(info->style);
1527 info->stack[info->stackTop].codePage = info->codePage;
1528 info->stack[info->stackTop].unicodeLength = info->unicodeLength;
1530 info->stackTop++;
1531 info->styleChanged = FALSE;
1532 break;
1533 case rtfEndGroup:
1535 RTFFlushOutputBuffer(info);
1536 info->stackTop--;
1537 if (info->stackTop <= 0)
1538 info->rtfClass = rtfEOF;
1539 if (info->stackTop < 0)
1540 return;
1542 ME_ReleaseStyle(info->style);
1543 info->style = info->stack[info->stackTop].style;
1544 info->codePage = info->stack[info->stackTop].codePage;
1545 info->unicodeLength = info->stack[info->stackTop].unicodeLength;
1546 break;
1549 break;
1553 void
1554 ME_StreamInFill(ME_InStream *stream)
1556 stream->editstream->dwError = stream->editstream->pfnCallback(stream->editstream->dwCookie,
1557 (BYTE *)stream->buffer,
1558 sizeof(stream->buffer),
1559 (LONG *)&stream->dwSize);
1560 stream->dwUsed = 0;
1563 static LRESULT ME_StreamIn(ME_TextEditor *editor, DWORD format, EDITSTREAM *stream, BOOL stripLastCR)
1565 RTF_Info parser;
1566 ME_Style *style;
1567 LONG from, to;
1568 int nUndoMode;
1569 int nEventMask = editor->nEventMask;
1570 ME_InStream inStream;
1571 BOOL invalidRTF = FALSE;
1572 ME_Cursor *selStart, *selEnd;
1573 LRESULT num_read = 0; /* bytes read for SF_TEXT, non-control chars inserted for SF_RTF */
1575 TRACE("stream==%p editor==%p format==0x%lX\n", stream, editor, format);
1576 editor->nEventMask = 0;
1578 ME_GetSelectionOfs(editor, &from, &to);
1579 if (format & SFF_SELECTION && editor->mode & TM_RICHTEXT)
1581 ME_GetSelection(editor, &selStart, &selEnd);
1582 style = ME_GetSelectionInsertStyle(editor);
1584 ME_InternalDeleteText(editor, selStart, to - from, FALSE);
1586 /* Don't insert text at the end of the table row */
1587 if (!editor->bEmulateVersion10) /* v4.1 */
1589 ME_Paragraph *para = editor->pCursors->para;
1590 if (para->nFlags & (MEPF_ROWSTART | MEPF_ROWEND))
1592 para = para_next( para );
1593 editor->pCursors[0].para = para;
1594 editor->pCursors[0].run = para_first_run( para );
1595 editor->pCursors[0].nOffset = 0;
1597 editor->pCursors[1] = editor->pCursors[0];
1599 else /* v1.0 - 3.0 */
1601 if (editor->pCursors[0].run->nFlags & MERF_ENDPARA &&
1602 para_in_table( editor->pCursors[0].para ))
1603 return 0;
1606 else
1608 style = editor->pBuffer->pDefaultStyle;
1609 ME_AddRefStyle(style);
1610 if (format & SFF_SELECTION)
1612 ME_GetSelection(editor, &selStart, &selEnd);
1613 ME_InternalDeleteText(editor, selStart, to - from, FALSE);
1615 else
1617 set_selection_cursors(editor, 0, 0);
1618 ME_InternalDeleteText(editor, &editor->pCursors[1],
1619 ME_GetTextLength(editor), FALSE);
1621 from = to = 0;
1622 ME_ClearTempStyle(editor);
1623 editor_set_default_para_fmt( editor, &editor->pCursors[0].para->fmt );
1627 /* Back up undo mode to a local variable */
1628 nUndoMode = editor->nUndoMode;
1630 /* Only create an undo if SFF_SELECTION is set */
1631 if (!(format & SFF_SELECTION))
1632 editor->nUndoMode = umIgnore;
1634 inStream.editstream = stream;
1635 inStream.editstream->dwError = 0;
1636 inStream.dwSize = 0;
1637 inStream.dwUsed = 0;
1639 if (format & SF_RTF)
1641 /* Check if it's really RTF, and if it is not, use plain text */
1642 ME_StreamInFill(&inStream);
1643 if (!inStream.editstream->dwError)
1645 if ((!editor->bEmulateVersion10 && strncmp(inStream.buffer, "{\\rtf", 5) && strncmp(inStream.buffer, "{\\urtf", 6))
1646 || (editor->bEmulateVersion10 && *inStream.buffer != '{'))
1648 invalidRTF = TRUE;
1649 inStream.editstream->dwError = -16;
1654 if (!invalidRTF && !inStream.editstream->dwError)
1656 ME_Cursor start;
1657 from = ME_GetCursorOfs(&editor->pCursors[0]);
1658 if (format & SF_RTF) {
1660 /* setup the RTF parser */
1661 memset(&parser, 0, sizeof parser);
1662 RTFSetEditStream(&parser, &inStream);
1663 parser.rtfFormat = format&(SF_TEXT|SF_RTF);
1664 parser.editor = editor;
1665 parser.style = style;
1666 WriterInit(&parser);
1667 RTFInit(&parser);
1668 RTFSetReadHook(&parser, ME_RTFReadHook);
1669 RTFSetDestinationCallback(&parser, rtfShpPict, ME_RTFReadShpPictGroup);
1670 RTFSetDestinationCallback(&parser, rtfPict, ME_RTFReadPictGroup);
1671 RTFSetDestinationCallback(&parser, rtfObject, ME_RTFReadObjectGroup);
1672 RTFSetDestinationCallback(&parser, rtfParNumbering, ME_RTFReadParnumGroup);
1673 if (!parser.editor->bEmulateVersion10) /* v4.1 */
1675 RTFSetDestinationCallback(&parser, rtfNoNestTables, RTFSkipGroup);
1676 RTFSetDestinationCallback(&parser, rtfNestTableProps, RTFReadGroup);
1678 BeginFile(&parser);
1680 /* do the parsing */
1681 RTFRead(&parser);
1682 RTFFlushOutputBuffer(&parser);
1683 if (!editor->bEmulateVersion10) /* v4.1 */
1685 if (parser.tableDef && parser.tableDef->row_start &&
1686 (parser.nestingLevel > 0 || parser.canInheritInTbl))
1688 /* Delete any incomplete table row at the end of the rich text. */
1689 int nOfs, nChars;
1690 ME_Paragraph *para;
1692 parser.rtfMinor = rtfRow;
1693 /* Complete the table row before deleting it.
1694 * By doing it this way we will have the current paragraph format set
1695 * properly to reflect that is not in the complete table, and undo items
1696 * will be added for this change to the current paragraph format. */
1697 if (parser.nestingLevel > 0)
1699 while (parser.nestingLevel > 1)
1700 ME_RTFSpecialCharHook(&parser); /* Decrements nestingLevel */
1701 para = parser.tableDef->row_start;
1702 ME_RTFSpecialCharHook(&parser);
1704 else
1706 para = parser.tableDef->row_start;
1707 ME_RTFSpecialCharHook(&parser);
1708 assert( para->nFlags & MEPF_ROWEND );
1709 para = para_next( para );
1712 editor->pCursors[1].para = para;
1713 editor->pCursors[1].run = para_first_run( para );
1714 editor->pCursors[1].nOffset = 0;
1715 nOfs = ME_GetCursorOfs(&editor->pCursors[1]);
1716 nChars = ME_GetCursorOfs(&editor->pCursors[0]) - nOfs;
1717 ME_InternalDeleteText(editor, &editor->pCursors[1], nChars, TRUE);
1718 if (parser.tableDef) parser.tableDef->row_start = NULL;
1721 RTFDestroy(&parser);
1723 if (parser.stackTop > 0)
1725 while (--parser.stackTop >= 0)
1727 ME_ReleaseStyle(parser.style);
1728 parser.style = parser.stack[parser.stackTop].style;
1730 if (!inStream.editstream->dwError)
1731 inStream.editstream->dwError = HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
1734 /* Remove last line break, as mandated by tests. This is not affected by
1735 CR/LF counters, since RTF streaming presents only \para tokens, which
1736 are converted according to the standard rules: \r for 2.0, \r\n for 1.0
1738 if (stripLastCR && !(format & SFF_SELECTION)) {
1739 int newto;
1740 ME_GetSelection(editor, &selStart, &selEnd);
1741 newto = ME_GetCursorOfs(selEnd);
1742 if (newto > to + (editor->bEmulateVersion10 ? 1 : 0)) {
1743 WCHAR lastchar[3] = {'\0', '\0'};
1744 int linebreakSize = editor->bEmulateVersion10 ? 2 : 1;
1745 ME_Cursor linebreakCursor = *selEnd, lastcharCursor = *selEnd;
1746 CHARFORMAT2W cf;
1748 /* Set the final eop to the char fmt of the last char */
1749 cf.cbSize = sizeof(cf);
1750 cf.dwMask = CFM_ALL2;
1751 ME_MoveCursorChars(editor, &lastcharCursor, -1, FALSE);
1752 ME_GetCharFormat(editor, &lastcharCursor, &linebreakCursor, &cf);
1753 set_selection_cursors(editor, newto, -1);
1754 ME_SetSelectionCharFormat(editor, &cf);
1755 set_selection_cursors(editor, newto, newto);
1757 ME_MoveCursorChars(editor, &linebreakCursor, -linebreakSize, FALSE);
1758 ME_GetTextW(editor, lastchar, 2, &linebreakCursor, linebreakSize, FALSE, FALSE);
1759 if (lastchar[0] == '\r' && (lastchar[1] == '\n' || lastchar[1] == '\0')) {
1760 ME_InternalDeleteText(editor, &linebreakCursor, linebreakSize, FALSE);
1764 to = ME_GetCursorOfs(&editor->pCursors[0]);
1765 num_read = to - from;
1767 style = parser.style;
1769 else if (format & SF_TEXT)
1771 num_read = ME_StreamInText(editor, format, &inStream, style);
1772 to = ME_GetCursorOfs(&editor->pCursors[0]);
1774 else
1775 ERR("EM_STREAMIN without SF_TEXT or SF_RTF\n");
1776 /* put the cursor at the top */
1777 if (!(format & SFF_SELECTION))
1778 set_selection_cursors(editor, 0, 0);
1779 cursor_from_char_ofs( editor, from, &start );
1780 ME_UpdateLinkAttribute(editor, &start, to - from);
1783 /* Restore saved undo mode */
1784 editor->nUndoMode = nUndoMode;
1786 /* even if we didn't add an undo, we need to commit anything on the stack */
1787 ME_CommitUndo(editor);
1789 /* If SFF_SELECTION isn't set, delete any undos from before we started too */
1790 if (!(format & SFF_SELECTION))
1791 ME_EmptyUndoStack(editor);
1793 ME_ReleaseStyle(style);
1794 editor->nEventMask = nEventMask;
1795 ME_UpdateRepaint(editor, FALSE);
1796 if (!(format & SFF_SELECTION)) {
1797 ME_ClearTempStyle(editor);
1799 ME_SendSelChange(editor);
1800 ME_SendRequestResize(editor, FALSE);
1802 return num_read;
1806 typedef struct tagME_RTFStringStreamStruct
1808 char *string;
1809 int pos;
1810 int length;
1811 } ME_RTFStringStreamStruct;
1813 static DWORD CALLBACK ME_ReadFromRTFString(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
1815 ME_RTFStringStreamStruct *pStruct = (ME_RTFStringStreamStruct *)dwCookie;
1816 int count;
1818 count = min(cb, pStruct->length - pStruct->pos);
1819 memmove(lpBuff, pStruct->string + pStruct->pos, count);
1820 pStruct->pos += count;
1821 *pcb = count;
1822 return 0;
1825 static void
1826 ME_StreamInRTFString(ME_TextEditor *editor, BOOL selection, char *string)
1828 EDITSTREAM es;
1829 ME_RTFStringStreamStruct data;
1831 data.string = string;
1832 data.length = strlen(string);
1833 data.pos = 0;
1834 es.dwCookie = (DWORD_PTR)&data;
1835 es.pfnCallback = ME_ReadFromRTFString;
1836 ME_StreamIn(editor, SF_RTF | (selection ? SFF_SELECTION : 0), &es, TRUE);
1840 static int
1841 ME_FindText(ME_TextEditor *editor, DWORD flags, const CHARRANGE *chrg, const WCHAR *text, CHARRANGE *chrgText)
1843 const int nLen = lstrlenW(text);
1844 const int nTextLen = ME_GetTextLength(editor);
1845 int nMin, nMax;
1846 ME_Cursor cursor;
1847 WCHAR wLastChar = ' ';
1849 TRACE("flags==0x%08lx, chrg->cpMin==%ld, chrg->cpMax==%ld text==%s\n",
1850 flags, chrg->cpMin, chrg->cpMax, debugstr_w(text));
1852 if (flags & ~(FR_DOWN | FR_MATCHCASE | FR_WHOLEWORD))
1853 FIXME("Flags 0x%08lx not implemented\n",
1854 flags & ~(FR_DOWN | FR_MATCHCASE | FR_WHOLEWORD));
1856 nMin = chrg->cpMin;
1857 if (chrg->cpMax == -1)
1858 nMax = nTextLen;
1859 else
1860 nMax = chrg->cpMax > nTextLen ? nTextLen : chrg->cpMax;
1862 /* In 1.0 emulation, if cpMax reaches end of text, add the FR_DOWN flag */
1863 if (editor->bEmulateVersion10 && nMax == nTextLen)
1865 flags |= FR_DOWN;
1868 /* In 1.0 emulation, cpMin must always be no greater than cpMax */
1869 if (editor->bEmulateVersion10 && nMax < nMin)
1871 if (chrgText)
1873 chrgText->cpMin = -1;
1874 chrgText->cpMax = -1;
1876 return -1;
1879 /* when searching up, if cpMin < cpMax, then instead of searching
1880 * on [cpMin,cpMax], we search on [0,cpMin], otherwise, search on
1881 * [cpMax, cpMin]. The exception is when cpMax is -1, in which
1882 * case, it is always bigger than cpMin.
1884 if (!editor->bEmulateVersion10 && !(flags & FR_DOWN))
1886 int nSwap = nMax;
1888 nMax = nMin > nTextLen ? nTextLen : nMin;
1889 if (nMin < nSwap || chrg->cpMax == -1)
1890 nMin = 0;
1891 else
1892 nMin = nSwap;
1895 if (!nLen || nMin < 0 || nMax < 0 || nMax < nMin)
1897 if (chrgText)
1898 chrgText->cpMin = chrgText->cpMax = -1;
1899 return -1;
1902 if (flags & FR_DOWN) /* Forward search */
1904 /* If possible, find the character before where the search starts */
1905 if ((flags & FR_WHOLEWORD) && nMin)
1907 cursor_from_char_ofs( editor, nMin - 1, &cursor );
1908 wLastChar = *get_text( cursor.run, cursor.nOffset );
1909 ME_MoveCursorChars(editor, &cursor, 1, FALSE);
1911 else cursor_from_char_ofs( editor, nMin, &cursor );
1913 while (cursor.run && ME_GetCursorOfs(&cursor) + nLen <= nMax)
1915 ME_Run *run = cursor.run;
1916 int nCurStart = cursor.nOffset;
1917 int nMatched = 0;
1919 while (run && ME_CharCompare( *get_text( run, nCurStart + nMatched ), text[nMatched], (flags & FR_MATCHCASE)))
1921 if ((flags & FR_WHOLEWORD) && iswalnum(wLastChar))
1922 break;
1924 nMatched++;
1925 if (nMatched == nLen)
1927 ME_Run *next_run = run;
1928 int nNextStart = nCurStart;
1929 WCHAR wNextChar;
1931 /* Check to see if next character is a whitespace */
1932 if (flags & FR_WHOLEWORD)
1934 if (nCurStart + nMatched == run->len)
1936 next_run = run_next_all_paras( run );
1937 nNextStart = -nMatched;
1940 if (next_run)
1941 wNextChar = *get_text( next_run, nNextStart + nMatched );
1942 else
1943 wNextChar = ' ';
1945 if (iswalnum(wNextChar))
1946 break;
1949 cursor.nOffset += cursor.para->nCharOfs + cursor.run->nCharOfs;
1950 if (chrgText)
1952 chrgText->cpMin = cursor.nOffset;
1953 chrgText->cpMax = cursor.nOffset + nLen;
1955 TRACE("found at %d-%d\n", cursor.nOffset, cursor.nOffset + nLen);
1956 return cursor.nOffset;
1958 if (nCurStart + nMatched == run->len)
1960 run = run_next_all_paras( run );
1961 nCurStart = -nMatched;
1964 if (run)
1965 wLastChar = *get_text( run, nCurStart + nMatched );
1966 else
1967 wLastChar = ' ';
1969 cursor.nOffset++;
1970 if (cursor.nOffset == cursor.run->len)
1972 if (run_next_all_paras( cursor.run ))
1974 cursor.run = run_next_all_paras( cursor.run );
1975 cursor.para = cursor.run->para;
1976 cursor.nOffset = 0;
1978 else
1979 cursor.run = NULL;
1983 else /* Backward search */
1985 /* If possible, find the character after where the search ends */
1986 if ((flags & FR_WHOLEWORD) && nMax < nTextLen - 1)
1988 cursor_from_char_ofs( editor, nMax + 1, &cursor );
1989 wLastChar = *get_text( cursor.run, cursor.nOffset );
1990 ME_MoveCursorChars(editor, &cursor, -1, FALSE);
1992 else cursor_from_char_ofs( editor, nMax, &cursor );
1994 while (cursor.run && ME_GetCursorOfs(&cursor) - nLen >= nMin)
1996 ME_Run *run = cursor.run;
1997 ME_Paragraph *para = cursor.para;
1998 int nCurEnd = cursor.nOffset;
1999 int nMatched = 0;
2001 if (nCurEnd == 0 && run_prev_all_paras( run ))
2003 run = run_prev_all_paras( run );
2004 para = run->para;
2005 nCurEnd = run->len;
2008 while (run && ME_CharCompare( *get_text( run, nCurEnd - nMatched - 1 ),
2009 text[nLen - nMatched - 1], (flags & FR_MATCHCASE) ))
2011 if ((flags & FR_WHOLEWORD) && iswalnum(wLastChar))
2012 break;
2014 nMatched++;
2015 if (nMatched == nLen)
2017 ME_Run *prev_run = run;
2018 int nPrevEnd = nCurEnd;
2019 WCHAR wPrevChar;
2020 int nStart;
2022 /* Check to see if previous character is a whitespace */
2023 if (flags & FR_WHOLEWORD)
2025 if (nPrevEnd - nMatched == 0)
2027 prev_run = run_prev_all_paras( run );
2028 if (prev_run) nPrevEnd = prev_run->len + nMatched;
2031 if (prev_run) wPrevChar = *get_text( prev_run, nPrevEnd - nMatched - 1 );
2032 else wPrevChar = ' ';
2034 if (iswalnum(wPrevChar))
2035 break;
2038 nStart = para->nCharOfs + run->nCharOfs + nCurEnd - nMatched;
2039 if (chrgText)
2041 chrgText->cpMin = nStart;
2042 chrgText->cpMax = nStart + nLen;
2044 TRACE("found at %d-%d\n", nStart, nStart + nLen);
2045 return nStart;
2047 if (nCurEnd - nMatched == 0)
2049 if (run_prev_all_paras( run ))
2051 run = run_prev_all_paras( run );
2052 para = run->para;
2054 /* Don't care about pCurItem becoming NULL here; it's already taken
2055 * care of in the exterior loop condition */
2056 nCurEnd = run->len + nMatched;
2059 if (run)
2060 wLastChar = *get_text( run, nCurEnd - nMatched - 1 );
2061 else
2062 wLastChar = ' ';
2064 cursor.nOffset--;
2065 if (cursor.nOffset < 0)
2067 if (run_prev_all_paras( cursor.run ) )
2069 cursor.run = run_prev_all_paras( cursor.run );
2070 cursor.para = cursor.run->para;
2071 cursor.nOffset = cursor.run->len;
2073 else
2074 cursor.run = NULL;
2078 TRACE("not found\n");
2079 if (chrgText)
2080 chrgText->cpMin = chrgText->cpMax = -1;
2081 return -1;
2084 static int ME_GetTextEx(ME_TextEditor *editor, GETTEXTEX *ex, LPARAM pText)
2086 int nChars;
2087 ME_Cursor start;
2089 if (!ex->cb || !pText) return 0;
2091 if (ex->flags & ~(GT_SELECTION | GT_USECRLF))
2092 FIXME("GETTEXTEX flags 0x%08lx not supported\n", ex->flags & ~(GT_SELECTION | GT_USECRLF));
2094 if (ex->flags & GT_SELECTION)
2096 LONG from, to;
2097 int nStartCur = ME_GetSelectionOfs(editor, &from, &to);
2098 start = editor->pCursors[nStartCur];
2099 nChars = to - from;
2101 else
2103 ME_SetCursorToStart(editor, &start);
2104 nChars = INT_MAX;
2106 if (ex->codepage == CP_UNICODE)
2108 return ME_GetTextW(editor, (LPWSTR)pText, ex->cb / sizeof(WCHAR) - 1,
2109 &start, nChars, ex->flags & GT_USECRLF, FALSE);
2111 else
2113 /* potentially each char may be a CR, why calculate the exact value with O(N) when
2114 we can just take a bigger buffer? :)
2115 The above assumption still holds with CR/LF counters, since CR->CRLF expansion
2116 occurs only in richedit 2.0 mode, in which line breaks have only one CR
2118 int crlfmul = (ex->flags & GT_USECRLF) ? 2 : 1;
2119 DWORD buflen;
2120 LPWSTR buffer;
2121 LRESULT rc;
2123 buflen = min(crlfmul * nChars, ex->cb - 1);
2124 buffer = malloc((buflen + 1) * sizeof(WCHAR));
2126 nChars = ME_GetTextW(editor, buffer, buflen, &start, nChars, ex->flags & GT_USECRLF, FALSE);
2127 rc = WideCharToMultiByte(ex->codepage, 0, buffer, nChars + 1,
2128 (LPSTR)pText, ex->cb, ex->lpDefaultChar, ex->lpUsedDefChar);
2129 if (rc) rc--; /* do not count 0 terminator */
2131 free(buffer);
2132 return rc;
2136 static int get_text_range( ME_TextEditor *editor, WCHAR *buffer,
2137 const ME_Cursor *start, int len )
2139 if (!buffer) return 0;
2140 return ME_GetTextW( editor, buffer, INT_MAX, start, len, FALSE, FALSE );
2143 int set_selection( ME_TextEditor *editor, int to, int from )
2145 int end;
2147 TRACE("%d - %d\n", to, from );
2149 if (!editor->bHideSelection) ME_InvalidateSelection( editor );
2150 end = set_selection_cursors( editor, to, from );
2151 editor_ensure_visible( editor, &editor->pCursors[0] );
2152 if (!editor->bHideSelection) ME_InvalidateSelection( editor );
2153 update_caret( editor );
2154 ME_Repaint( editor );
2155 ME_SendSelChange( editor );
2157 return end;
2160 typedef struct tagME_GlobalDestStruct
2162 HGLOBAL hData;
2163 int nLength;
2164 } ME_GlobalDestStruct;
2166 static DWORD CALLBACK ME_ReadFromHGLOBALUnicode(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
2168 ME_GlobalDestStruct *pData = (ME_GlobalDestStruct *)dwCookie;
2169 int i;
2170 WORD *pSrc, *pDest;
2172 cb = cb >> 1;
2173 pDest = (WORD *)lpBuff;
2174 pSrc = GlobalLock(pData->hData);
2175 for (i = 0; i<cb && pSrc[pData->nLength+i]; i++) {
2176 pDest[i] = pSrc[pData->nLength+i];
2178 pData->nLength += i;
2179 *pcb = 2*i;
2180 GlobalUnlock(pData->hData);
2181 return 0;
2184 static DWORD CALLBACK ME_ReadFromHGLOBALRTF(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
2186 ME_GlobalDestStruct *pData = (ME_GlobalDestStruct *)dwCookie;
2187 int i;
2188 BYTE *pSrc, *pDest;
2190 pDest = lpBuff;
2191 pSrc = GlobalLock(pData->hData);
2192 for (i = 0; i<cb && pSrc[pData->nLength+i]; i++) {
2193 pDest[i] = pSrc[pData->nLength+i];
2195 pData->nLength += i;
2196 *pcb = i;
2197 GlobalUnlock(pData->hData);
2198 return 0;
2201 static HRESULT paste_rtf(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2203 EDITSTREAM es;
2204 ME_GlobalDestStruct gds;
2205 HRESULT hr;
2207 gds.hData = med->hGlobal;
2208 gds.nLength = 0;
2209 es.dwCookie = (DWORD_PTR)&gds;
2210 es.pfnCallback = ME_ReadFromHGLOBALRTF;
2211 hr = ME_StreamIn( editor, SF_RTF | SFF_SELECTION, &es, FALSE ) == 0 ? E_FAIL : S_OK;
2212 ReleaseStgMedium( med );
2213 return hr;
2216 static HRESULT paste_text(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2218 EDITSTREAM es;
2219 ME_GlobalDestStruct gds;
2220 HRESULT hr;
2222 gds.hData = med->hGlobal;
2223 gds.nLength = 0;
2224 es.dwCookie = (DWORD_PTR)&gds;
2225 es.pfnCallback = ME_ReadFromHGLOBALUnicode;
2226 hr = ME_StreamIn( editor, SF_TEXT | SF_UNICODE | SFF_SELECTION, &es, FALSE ) == 0 ? E_FAIL : S_OK;
2227 ReleaseStgMedium( med );
2228 return hr;
2231 static HRESULT paste_emf(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2233 HRESULT hr;
2234 SIZEL sz = {0, 0};
2236 hr = insert_static_object( editor, med->hEnhMetaFile, NULL, &sz );
2237 if (SUCCEEDED(hr))
2239 ME_CommitUndo( editor );
2240 ME_UpdateRepaint( editor, FALSE );
2242 else
2243 ReleaseStgMedium( med );
2245 return hr;
2248 static struct paste_format
2250 FORMATETC fmt;
2251 HRESULT (*paste)(ME_TextEditor *, FORMATETC *, STGMEDIUM *);
2252 const WCHAR *name;
2253 } paste_formats[] =
2255 {{ -1, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, paste_rtf, L"Rich Text Format" },
2256 {{ CF_UNICODETEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, paste_text },
2257 {{ CF_ENHMETAFILE, NULL, DVASPECT_CONTENT, -1, TYMED_ENHMF }, paste_emf },
2258 {{ 0 }}
2261 static void init_paste_formats(void)
2263 struct paste_format *format;
2264 static int done;
2266 if (!done)
2268 for (format = paste_formats; format->fmt.cfFormat; format++)
2270 if (format->name)
2271 format->fmt.cfFormat = RegisterClipboardFormatW( format->name );
2273 done = 1;
2277 static BOOL paste_special(ME_TextEditor *editor, UINT cf, REPASTESPECIAL *ps, BOOL check_only)
2279 HRESULT hr;
2280 STGMEDIUM med;
2281 struct paste_format *format;
2282 IDataObject *data;
2284 /* Protect read-only edit control from modification */
2285 if (editor->props & TXTBIT_READONLY)
2287 if (!check_only) editor_beep( editor, MB_ICONERROR );
2288 return FALSE;
2291 init_paste_formats();
2293 if (ps && ps->dwAspect != DVASPECT_CONTENT)
2294 FIXME("Ignoring aspect %lx\n", ps->dwAspect);
2296 hr = OleGetClipboard( &data );
2297 if (hr != S_OK) return FALSE;
2299 if (cf == CF_TEXT) cf = CF_UNICODETEXT;
2301 hr = S_FALSE;
2302 for (format = paste_formats; format->fmt.cfFormat; format++)
2304 if (cf && cf != format->fmt.cfFormat) continue;
2305 hr = IDataObject_QueryGetData( data, &format->fmt );
2306 if (hr == S_OK)
2308 if (!check_only)
2310 hr = IDataObject_GetData( data, &format->fmt, &med );
2311 if (hr != S_OK) goto done;
2312 hr = format->paste( editor, &format->fmt, &med );
2314 break;
2318 done:
2319 IDataObject_Release( data );
2321 return hr == S_OK;
2324 static HRESULT editor_copy( ME_TextEditor *editor, ME_Cursor *start, int chars, IDataObject **data_out )
2326 IDataObject *data = NULL;
2327 HRESULT hr = S_OK;
2329 if (editor->lpOleCallback)
2331 CHARRANGE range;
2332 range.cpMin = ME_GetCursorOfs( start );
2333 range.cpMax = range.cpMin + chars;
2334 hr = IRichEditOleCallback_GetClipboardData( editor->lpOleCallback, &range, RECO_COPY, &data );
2337 if (FAILED( hr ) || !data)
2338 hr = ME_GetDataObject( editor, start, chars, &data );
2340 if (SUCCEEDED( hr ))
2342 if (data_out)
2343 *data_out = data;
2344 else
2346 hr = OleSetClipboard( data );
2347 IDataObject_Release( data );
2351 return hr;
2354 HRESULT editor_copy_or_cut( ME_TextEditor *editor, BOOL cut, ME_Cursor *start, int count,
2355 IDataObject **data_out )
2357 HRESULT hr;
2359 if (cut && (editor->props & TXTBIT_READONLY))
2361 return E_ACCESSDENIED;
2364 hr = editor_copy( editor, start, count, data_out );
2365 if (SUCCEEDED(hr) && cut)
2367 ME_InternalDeleteText( editor, start, count, FALSE );
2368 ME_CommitUndo( editor );
2369 ME_UpdateRepaint( editor, TRUE );
2371 return hr;
2374 static BOOL copy_or_cut( ME_TextEditor *editor, BOOL cut )
2376 HRESULT hr;
2377 LONG offs, count;
2378 int start_cursor = ME_GetSelectionOfs( editor, &offs, &count );
2379 ME_Cursor *sel_start = &editor->pCursors[start_cursor];
2381 if (editor->password_char) return FALSE;
2383 count -= offs;
2384 hr = editor_copy_or_cut( editor, cut, sel_start, count, NULL );
2385 if (FAILED( hr )) editor_beep( editor, MB_ICONERROR );
2387 return SUCCEEDED( hr );
2390 static void ME_UpdateSelectionLinkAttribute(ME_TextEditor *editor)
2392 ME_Paragraph *start_para, *end_para;
2393 ME_Cursor *from, *to, start;
2394 int num_chars;
2396 if (!editor->AutoURLDetect_bEnable) return;
2398 ME_GetSelection(editor, &from, &to);
2400 /* Find paragraph previous to the one that contains start cursor */
2401 start_para = from->para;
2402 if (para_prev( start_para )) start_para = para_prev( start_para );
2404 /* Find paragraph that contains end cursor */
2405 end_para = para_next( to->para );
2407 start.para = start_para;
2408 start.run = para_first_run( start_para );
2409 start.nOffset = 0;
2410 num_chars = end_para->nCharOfs - start_para->nCharOfs;
2412 ME_UpdateLinkAttribute( editor, &start, num_chars );
2415 static BOOL handle_enter(ME_TextEditor *editor)
2417 BOOL shift_is_down = GetKeyState(VK_SHIFT) & 0x8000;
2419 if (editor->props & TXTBIT_MULTILINE)
2421 ME_Cursor cursor = editor->pCursors[0];
2422 ME_Paragraph *para = cursor.para;
2423 LONG from, to;
2424 ME_Style *style, *eop_style;
2426 if (editor->props & TXTBIT_READONLY)
2428 editor_beep( editor, MB_ICONERROR );
2429 return TRUE;
2432 ME_GetSelectionOfs(editor, &from, &to);
2433 if (editor->nTextLimit > ME_GetTextLength(editor) - (to-from))
2435 if (!editor->bEmulateVersion10) /* v4.1 */
2437 if (para->nFlags & MEPF_ROWEND)
2439 /* Add a new table row after this row. */
2440 para = table_append_row( editor, para );
2441 para = para_next( para );
2442 editor->pCursors[0].para = para;
2443 editor->pCursors[0].run = para_first_run( para );
2444 editor->pCursors[0].nOffset = 0;
2445 editor->pCursors[1] = editor->pCursors[0];
2446 ME_CommitUndo(editor);
2447 ME_UpdateRepaint(editor, FALSE);
2448 return TRUE;
2450 else if (para == editor->pCursors[1].para &&
2451 cursor.nOffset + cursor.run->nCharOfs == 0 &&
2452 para_prev( para ) && para_prev( para )->nFlags & MEPF_ROWSTART &&
2453 !para_prev( para )->nCharOfs)
2455 /* Insert a newline before the table. */
2456 para = para_prev( para );
2457 para->nFlags &= ~MEPF_ROWSTART;
2458 editor->pCursors[0].para = para;
2459 editor->pCursors[0].run = para_first_run( para );
2460 editor->pCursors[1] = editor->pCursors[0];
2461 ME_InsertTextFromCursor( editor, 0, L"\r", 1, editor->pCursors[0].run->style );
2462 para = editor_first_para( editor );
2463 editor_set_default_para_fmt( editor, &para->fmt );
2464 para->nFlags = 0;
2465 para_mark_rewrap( editor, para );
2466 editor->pCursors[0].para = para;
2467 editor->pCursors[0].run = para_first_run( para );
2468 editor->pCursors[1] = editor->pCursors[0];
2469 para_next( para )->nFlags |= MEPF_ROWSTART;
2470 ME_CommitCoalescingUndo(editor);
2471 ME_UpdateRepaint(editor, FALSE);
2472 return TRUE;
2475 else /* v1.0 - 3.0 */
2477 ME_Paragraph *para = cursor.para;
2478 if (para_in_table( para ))
2480 if (cursor.run->nFlags & MERF_ENDPARA)
2482 if (from == to)
2484 ME_ContinueCoalescingTransaction(editor);
2485 para = table_append_row( editor, para );
2486 editor->pCursors[0].para = para;
2487 editor->pCursors[0].run = para_first_run( para );
2488 editor->pCursors[0].nOffset = 0;
2489 editor->pCursors[1] = editor->pCursors[0];
2490 ME_CommitCoalescingUndo(editor);
2491 ME_UpdateRepaint(editor, FALSE);
2492 return TRUE;
2495 else
2497 ME_ContinueCoalescingTransaction(editor);
2498 if (cursor.run->nCharOfs + cursor.nOffset == 0 &&
2499 para_prev( para ) && !para_in_table( para_prev( para ) ))
2501 /* Insert newline before table */
2502 cursor.run = para_end_run( para_prev( para ) );
2503 if (cursor.run)
2505 editor->pCursors[0].run = cursor.run;
2506 editor->pCursors[0].para = para_prev( para );
2508 editor->pCursors[0].nOffset = 0;
2509 editor->pCursors[1] = editor->pCursors[0];
2510 ME_InsertTextFromCursor( editor, 0, L"\r", 1, editor->pCursors[0].run->style );
2512 else
2514 editor->pCursors[1] = editor->pCursors[0];
2515 para = table_append_row( editor, para );
2516 editor->pCursors[0].para = para;
2517 editor->pCursors[0].run = para_first_run( para );
2518 editor->pCursors[0].nOffset = 0;
2519 editor->pCursors[1] = editor->pCursors[0];
2521 ME_CommitCoalescingUndo(editor);
2522 ME_UpdateRepaint(editor, FALSE);
2523 return TRUE;
2528 style = style_get_insert_style( editor, editor->pCursors );
2530 /* Normally the new eop style is the insert style, however in a list it is copied from the existing
2531 eop style (this prevents the list label style changing when the new eop is inserted).
2532 No extra ref is taken here on eop_style. */
2533 if (para->fmt.wNumbering)
2534 eop_style = para->eop_run->style;
2535 else
2536 eop_style = style;
2537 ME_ContinueCoalescingTransaction(editor);
2538 if (shift_is_down)
2539 ME_InsertEndRowFromCursor(editor, 0);
2540 else
2541 if (!editor->bEmulateVersion10)
2542 ME_InsertTextFromCursor(editor, 0, L"\r", 1, eop_style);
2543 else
2544 ME_InsertTextFromCursor(editor, 0, L"\r\n", 2, eop_style);
2545 ME_CommitCoalescingUndo(editor);
2546 SetCursor(NULL);
2548 ME_UpdateSelectionLinkAttribute(editor);
2549 ME_UpdateRepaint(editor, FALSE);
2550 ME_SaveTempStyle(editor, style); /* set the temp insert style for the new para */
2551 ME_ReleaseStyle(style);
2553 return TRUE;
2555 return FALSE;
2558 static BOOL
2559 ME_KeyDown(ME_TextEditor *editor, WORD nKey)
2561 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2562 BOOL shift_is_down = GetKeyState(VK_SHIFT) & 0x8000;
2564 if (editor->bMouseCaptured)
2565 return FALSE;
2566 if (nKey != VK_SHIFT && nKey != VK_CONTROL && nKey != VK_MENU)
2567 editor->nSelectionType = stPosition;
2569 switch (nKey)
2571 case VK_LEFT:
2572 case VK_RIGHT:
2573 case VK_HOME:
2574 case VK_END:
2575 editor->nUDArrowX = -1;
2576 /* fall through */
2577 case VK_UP:
2578 case VK_DOWN:
2579 case VK_PRIOR:
2580 case VK_NEXT:
2581 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
2582 ME_ArrowKey(editor, nKey, shift_is_down, ctrl_is_down);
2583 return TRUE;
2584 case VK_BACK:
2585 case VK_DELETE:
2586 editor->nUDArrowX = -1;
2587 /* FIXME backspace and delete aren't the same, they act different wrt paragraph style of the merged paragraph */
2588 if (editor->props & TXTBIT_READONLY)
2589 return FALSE;
2590 if (ME_IsSelection(editor))
2592 ME_DeleteSelection(editor);
2593 ME_CommitUndo(editor);
2595 else if (nKey == VK_DELETE)
2597 /* Delete stops group typing.
2598 * (See MSDN remarks on EM_STOPGROUPTYPING message) */
2599 ME_DeleteTextAtCursor(editor, 1, 1);
2600 ME_CommitUndo(editor);
2602 else if (ME_ArrowKey(editor, VK_LEFT, FALSE, FALSE))
2604 BOOL bDeletionSucceeded;
2605 /* Backspace can be grouped for a single undo */
2606 ME_ContinueCoalescingTransaction(editor);
2607 bDeletionSucceeded = ME_DeleteTextAtCursor(editor, 1, 1);
2608 if (!bDeletionSucceeded && !editor->bEmulateVersion10) { /* v4.1 */
2609 /* Deletion was prevented so the cursor is moved back to where it was.
2610 * (e.g. this happens when trying to delete cell boundaries)
2612 ME_ArrowKey(editor, VK_RIGHT, FALSE, FALSE);
2614 ME_CommitCoalescingUndo(editor);
2616 else
2617 return TRUE;
2618 table_move_from_row_start( editor );
2619 ME_UpdateSelectionLinkAttribute(editor);
2620 ME_UpdateRepaint(editor, FALSE);
2621 ME_SendRequestResize(editor, FALSE);
2622 return TRUE;
2623 case VK_RETURN:
2624 if (!editor->bEmulateVersion10)
2625 return handle_enter(editor);
2626 break;
2627 case 'A':
2628 if (ctrl_is_down)
2630 set_selection( editor, 0, -1 );
2631 return TRUE;
2633 break;
2634 case 'V':
2635 if (ctrl_is_down)
2636 return paste_special( editor, 0, NULL, FALSE );
2637 break;
2638 case 'C':
2639 case 'X':
2640 if (ctrl_is_down)
2641 return copy_or_cut(editor, nKey == 'X');
2642 break;
2643 case 'Z':
2644 if (ctrl_is_down)
2646 ME_Undo(editor);
2647 return TRUE;
2649 break;
2650 case 'Y':
2651 if (ctrl_is_down)
2653 ME_Redo(editor);
2654 return TRUE;
2656 break;
2658 default:
2659 if (nKey != VK_SHIFT && nKey != VK_CONTROL && nKey && nKey != VK_MENU)
2660 editor->nUDArrowX = -1;
2661 if (ctrl_is_down)
2663 if (nKey == 'W')
2665 CHARFORMAT2W chf;
2666 char buf[2048];
2667 chf.cbSize = sizeof(chf);
2669 ME_GetSelectionCharFormat(editor, &chf);
2670 ME_DumpStyleToBuf(&chf, buf);
2671 MessageBoxA(NULL, buf, "Style dump", MB_OK);
2673 if (nKey == 'Q')
2675 ME_CheckCharOffsets(editor);
2679 return FALSE;
2682 static LRESULT handle_wm_char( ME_TextEditor *editor, WCHAR wstr, LPARAM flags )
2684 if (editor->bMouseCaptured)
2685 return 0;
2687 if (editor->props & TXTBIT_READONLY)
2689 editor_beep( editor, MB_ICONERROR );
2690 return 0; /* FIXME really 0 ? */
2693 if (editor->bEmulateVersion10 && wstr == '\r')
2694 handle_enter(editor);
2696 if ((unsigned)wstr >= ' ' || wstr == '\t')
2698 ME_Cursor cursor = editor->pCursors[0];
2699 ME_Paragraph *para = cursor.para;
2700 LONG from, to;
2701 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2702 ME_GetSelectionOfs(editor, &from, &to);
2703 if (wstr == '\t' &&
2704 /* v4.1 allows tabs to be inserted with ctrl key down */
2705 !(ctrl_is_down && !editor->bEmulateVersion10))
2707 BOOL selected_row = FALSE;
2709 if (ME_IsSelection(editor) &&
2710 cursor.run->nCharOfs + cursor.nOffset == 0 &&
2711 to == ME_GetCursorOfs(&editor->pCursors[0]) && para_prev( para ))
2713 para = para_prev( para );
2714 selected_row = TRUE;
2716 if (para_in_table( para ))
2718 table_handle_tab( editor, selected_row );
2719 ME_CommitUndo(editor);
2720 return 0;
2723 else if (!editor->bEmulateVersion10) /* v4.1 */
2725 if (para->nFlags & MEPF_ROWEND)
2727 if (from == to)
2729 para = para_next( para );
2730 if (para->nFlags & MEPF_ROWSTART) para = para_next( para );
2731 editor->pCursors[0].para = para;
2732 editor->pCursors[0].run = para_first_run( para );
2733 editor->pCursors[0].nOffset = 0;
2734 editor->pCursors[1] = editor->pCursors[0];
2738 else /* v1.0 - 3.0 */
2740 if (para_in_table( para ) && cursor.run->nFlags & MERF_ENDPARA && from == to)
2742 /* Text should not be inserted at the end of the table. */
2743 editor_beep( editor, -1 );
2744 return 0;
2747 /* FIXME maybe it would make sense to call EM_REPLACESEL instead ? */
2748 /* WM_CHAR is restricted to nTextLimit */
2749 if(editor->nTextLimit > ME_GetTextLength(editor) - (to-from))
2751 ME_Style *style = style_get_insert_style( editor, editor->pCursors );
2752 ME_ContinueCoalescingTransaction(editor);
2753 ME_InsertTextFromCursor(editor, 0, &wstr, 1, style);
2754 ME_ReleaseStyle(style);
2755 ME_CommitCoalescingUndo(editor);
2756 ITextHost_TxSetCursor(editor->texthost, NULL, FALSE);
2759 ME_UpdateSelectionLinkAttribute(editor);
2760 ME_UpdateRepaint(editor, FALSE);
2762 return 0;
2765 /* Process the message and calculate the new click count.
2767 * returns: The click count if it is mouse down event, else returns 0. */
2768 static int ME_CalculateClickCount(ME_TextEditor *editor, UINT msg, WPARAM wParam,
2769 LPARAM lParam)
2771 static int clickNum = 0;
2772 if (msg < WM_MOUSEFIRST || msg > WM_MOUSELAST)
2773 return 0;
2775 if ((msg == WM_LBUTTONDBLCLK) ||
2776 (msg == WM_RBUTTONDBLCLK) ||
2777 (msg == WM_MBUTTONDBLCLK) ||
2778 (msg == WM_XBUTTONDBLCLK))
2780 msg -= (WM_LBUTTONDBLCLK - WM_LBUTTONDOWN);
2783 if ((msg == WM_LBUTTONDOWN) ||
2784 (msg == WM_RBUTTONDOWN) ||
2785 (msg == WM_MBUTTONDOWN) ||
2786 (msg == WM_XBUTTONDOWN))
2788 static MSG prevClickMsg;
2789 MSG clickMsg;
2790 /* Compare the editor instead of the hwnd so that the this
2791 * can still be done for windowless richedit controls. */
2792 clickMsg.hwnd = (HWND)editor;
2793 clickMsg.message = msg;
2794 clickMsg.wParam = wParam;
2795 clickMsg.lParam = lParam;
2796 clickMsg.time = GetMessageTime();
2797 clickMsg.pt.x = (short)LOWORD(lParam);
2798 clickMsg.pt.y = (short)HIWORD(lParam);
2799 if ((clickNum != 0) &&
2800 (clickMsg.message == prevClickMsg.message) &&
2801 (clickMsg.hwnd == prevClickMsg.hwnd) &&
2802 (clickMsg.wParam == prevClickMsg.wParam) &&
2803 (clickMsg.time - prevClickMsg.time < GetDoubleClickTime()) &&
2804 (abs(clickMsg.pt.x - prevClickMsg.pt.x) < GetSystemMetrics(SM_CXDOUBLECLK)/2) &&
2805 (abs(clickMsg.pt.y - prevClickMsg.pt.y) < GetSystemMetrics(SM_CYDOUBLECLK)/2))
2807 clickNum++;
2808 } else {
2809 clickNum = 1;
2811 prevClickMsg = clickMsg;
2812 } else {
2813 return 0;
2815 return clickNum;
2818 static BOOL is_link( ME_Run *run )
2820 return (run->style->fmt.dwMask & CFM_LINK) && (run->style->fmt.dwEffects & CFE_LINK);
2823 void editor_set_cursor( ME_TextEditor *editor, int x, int y )
2825 ME_Cursor pos;
2826 static HCURSOR cursor_arrow, cursor_hand, cursor_ibeam, cursor_reverse;
2827 HCURSOR cursor;
2829 if (!cursor_arrow)
2831 cursor_arrow = LoadCursorW( NULL, MAKEINTRESOURCEW( IDC_ARROW ) );
2832 cursor_hand = LoadCursorW( NULL, MAKEINTRESOURCEW( IDC_HAND ) );
2833 cursor_ibeam = LoadCursorW( NULL, MAKEINTRESOURCEW( IDC_IBEAM ) );
2834 cursor_reverse = LoadCursorW( dll_instance, MAKEINTRESOURCEW( OCR_REVERSE ) );
2837 cursor = cursor_ibeam;
2839 if ((editor->nSelectionType == stLine && editor->bMouseCaptured) ||
2840 (!editor->bEmulateVersion10 && y < editor->rcFormat.top && x < editor->rcFormat.left))
2841 cursor = cursor_reverse;
2842 else if (y < editor->rcFormat.top || y > editor->rcFormat.bottom)
2844 if (editor->bEmulateVersion10) cursor = cursor_arrow;
2845 else cursor = cursor_ibeam;
2847 else if (x < editor->rcFormat.left) cursor = cursor_reverse;
2848 else if (cursor_from_coords( editor, x, y, &pos ))
2850 ME_Run *run = pos.run;
2852 if (is_link( run )) cursor = cursor_hand;
2854 else if (ME_IsSelection( editor ))
2856 LONG start, end;
2857 int offset = ME_GetCursorOfs( &pos );
2859 ME_GetSelectionOfs( editor, &start, &end );
2860 if (start <= offset && end >= offset) cursor = cursor_arrow;
2864 ITextHost_TxSetCursor( editor->texthost, cursor, cursor == cursor_ibeam );
2867 static LONG ME_GetSelectionType(ME_TextEditor *editor)
2869 LONG sel_type = SEL_EMPTY;
2870 LONG start, end;
2872 ME_GetSelectionOfs(editor, &start, &end);
2873 if (start == end)
2874 sel_type = SEL_EMPTY;
2875 else
2877 LONG object_count = 0, character_count = 0;
2878 int i;
2880 for (i = 0; i < end - start; i++)
2882 ME_Cursor cursor;
2884 cursor_from_char_ofs( editor, start + i, &cursor );
2885 if (cursor.run->reobj) object_count++;
2886 else character_count++;
2887 if (character_count >= 2 && object_count >= 2)
2888 return (SEL_TEXT | SEL_MULTICHAR | SEL_OBJECT | SEL_MULTIOBJECT);
2890 if (character_count)
2892 sel_type |= SEL_TEXT;
2893 if (character_count >= 2)
2894 sel_type |= SEL_MULTICHAR;
2896 if (object_count)
2898 sel_type |= SEL_OBJECT;
2899 if (object_count >= 2)
2900 sel_type |= SEL_MULTIOBJECT;
2903 return sel_type;
2906 static BOOL ME_ShowContextMenu(ME_TextEditor *editor, int x, int y)
2908 CHARRANGE selrange;
2909 HMENU menu;
2910 int seltype;
2911 HWND hwnd, parent;
2913 if (!editor->lpOleCallback || !editor->have_texthost2) return FALSE;
2914 if (FAILED( ITextHost2_TxGetWindow( editor->texthost, &hwnd ))) return FALSE;
2915 parent = GetParent( hwnd );
2916 if (!parent) parent = hwnd;
2918 ME_GetSelectionOfs( editor, &selrange.cpMin, &selrange.cpMax );
2919 seltype = ME_GetSelectionType( editor );
2920 if (SUCCEEDED( IRichEditOleCallback_GetContextMenu( editor->lpOleCallback, seltype, NULL, &selrange, &menu ) ))
2922 TrackPopupMenu( menu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, x, y, 0, parent, NULL );
2923 DestroyMenu( menu );
2925 return TRUE;
2928 ME_TextEditor *ME_MakeEditor(ITextHost *texthost, BOOL bEmulateVersion10)
2930 ME_TextEditor *ed = malloc( sizeof(*ed) );
2931 int i;
2932 LONG selbarwidth;
2933 HRESULT hr;
2934 HDC hdc;
2936 ed->sizeWindow.cx = ed->sizeWindow.cy = 0;
2937 if (ITextHost_QueryInterface( texthost, &IID_ITextHost2, (void **)&ed->texthost ) == S_OK)
2939 ITextHost_Release( texthost );
2940 ed->have_texthost2 = TRUE;
2942 else
2944 ed->texthost = (ITextHost2 *)texthost;
2945 ed->have_texthost2 = FALSE;
2948 ed->bEmulateVersion10 = bEmulateVersion10;
2949 ed->in_place_active = FALSE;
2950 ed->total_rows = 0;
2951 ITextHost_TxGetPropertyBits( ed->texthost, TXTBIT_RICHTEXT | TXTBIT_MULTILINE | TXTBIT_READONLY |
2952 TXTBIT_USEPASSWORD | TXTBIT_HIDESELECTION | TXTBIT_SAVESELECTION |
2953 TXTBIT_AUTOWORDSEL | TXTBIT_VERTICAL | TXTBIT_WORDWRAP | TXTBIT_ALLOWBEEP |
2954 TXTBIT_DISABLEDRAG,
2955 &ed->props );
2956 ITextHost_TxGetScrollBars( ed->texthost, &ed->scrollbars );
2957 ed->pBuffer = ME_MakeText();
2958 ed->nZoomNumerator = ed->nZoomDenominator = 0;
2959 ed->nAvailWidth = 0; /* wrap to client area */
2960 list_init( &ed->style_list );
2962 hdc = ITextHost_TxGetDC( ed->texthost );
2963 ME_MakeFirstParagraph( ed, hdc );
2964 /* The four cursors are for:
2965 * 0 - The position where the caret is shown
2966 * 1 - The anchored end of the selection (for normal selection)
2967 * 2 & 3 - The anchored start and end respectively for word, line,
2968 * or paragraph selection.
2970 ed->nCursors = 4;
2971 ed->pCursors = malloc( ed->nCursors * sizeof(*ed->pCursors) );
2972 ME_SetCursorToStart(ed, &ed->pCursors[0]);
2973 ed->pCursors[1] = ed->pCursors[0];
2974 ed->pCursors[2] = ed->pCursors[0];
2975 ed->pCursors[3] = ed->pCursors[1];
2976 ed->nLastTotalLength = ed->nTotalLength = 0;
2977 ed->nLastTotalWidth = ed->nTotalWidth = 0;
2978 ed->nUDArrowX = -1;
2979 ed->nEventMask = 0;
2980 ed->nModifyStep = 0;
2981 ed->nTextLimit = TEXT_LIMIT_DEFAULT;
2982 list_init( &ed->undo_stack );
2983 list_init( &ed->redo_stack );
2984 ed->nUndoStackSize = 0;
2985 ed->nUndoLimit = STACK_SIZE_DEFAULT;
2986 ed->nUndoMode = umAddToUndo;
2987 ed->undo_ctl_state = undoActive;
2988 ed->nParagraphs = 1;
2989 ed->nLastSelStart = ed->nLastSelEnd = 0;
2990 ed->last_sel_start_para = ed->last_sel_end_para = ed->pCursors[0].para;
2991 ed->bHideSelection = FALSE;
2992 ed->pfnWordBreak = NULL;
2993 ed->richole = NULL;
2994 ed->lpOleCallback = NULL;
2995 ed->mode = TM_MULTILEVELUNDO | TM_MULTICODEPAGE;
2996 ed->mode |= (ed->props & TXTBIT_RICHTEXT) ? TM_RICHTEXT : TM_PLAINTEXT;
2997 ed->AutoURLDetect_bEnable = FALSE;
2998 ed->bHaveFocus = FALSE;
2999 ed->freeze_count = 0;
3000 ed->bMouseCaptured = FALSE;
3001 ed->caret_hidden = FALSE;
3002 ed->caret_height = 0;
3003 for (i=0; i<HFONT_CACHE_SIZE; i++)
3005 ed->pFontCache[i].nRefs = 0;
3006 ed->pFontCache[i].nAge = 0;
3007 ed->pFontCache[i].hFont = NULL;
3010 ME_CheckCharOffsets(ed);
3011 SetRectEmpty(&ed->rcFormat);
3012 hr = ITextHost_TxGetSelectionBarWidth( ed->texthost, &selbarwidth );
3013 /* FIXME: Convert selbarwidth from HIMETRIC to pixels */
3014 if (hr == S_OK && selbarwidth) ed->selofs = SELECTIONBAR_WIDTH;
3015 else ed->selofs = 0;
3016 ed->nSelectionType = stPosition;
3018 ed->password_char = 0;
3019 if (ed->props & TXTBIT_USEPASSWORD)
3020 ITextHost_TxGetPasswordChar( ed->texthost, &ed->password_char );
3022 ed->bWordWrap = (ed->props & TXTBIT_WORDWRAP) && (ed->props & TXTBIT_MULTILINE);
3024 ed->notified_cr.cpMin = ed->notified_cr.cpMax = 0;
3026 /* Default scrollbar information */
3027 ed->vert_si.cbSize = sizeof(SCROLLINFO);
3028 ed->vert_si.nMin = 0;
3029 ed->vert_si.nMax = 0;
3030 ed->vert_si.nPage = 0;
3031 ed->vert_si.nPos = 0;
3032 ed->vert_sb_enabled = 0;
3034 ed->horz_si.cbSize = sizeof(SCROLLINFO);
3035 ed->horz_si.nMin = 0;
3036 ed->horz_si.nMax = 0;
3037 ed->horz_si.nPage = 0;
3038 ed->horz_si.nPos = 0;
3039 ed->horz_sb_enabled = 0;
3041 if (ed->scrollbars & ES_DISABLENOSCROLL)
3043 if (ed->scrollbars & WS_VSCROLL)
3045 ITextHost_TxSetScrollRange( ed->texthost, SB_VERT, 0, 1, TRUE );
3046 ITextHost_TxEnableScrollBar( ed->texthost, SB_VERT, ESB_DISABLE_BOTH );
3048 if (ed->scrollbars & WS_HSCROLL)
3050 ITextHost_TxSetScrollRange( ed->texthost, SB_HORZ, 0, 1, TRUE );
3051 ITextHost_TxEnableScrollBar( ed->texthost, SB_HORZ, ESB_DISABLE_BOTH );
3055 ed->wheel_remain = 0;
3057 ed->back_style = TXTBACK_OPAQUE;
3058 ITextHost_TxGetBackStyle( ed->texthost, &ed->back_style );
3060 list_init( &ed->reobj_list );
3061 OleInitialize(NULL);
3063 wrap_marked_paras_dc( ed, hdc, FALSE );
3064 ITextHost_TxReleaseDC( ed->texthost, hdc );
3066 return ed;
3069 void ME_DestroyEditor(ME_TextEditor *editor)
3071 ME_DisplayItem *p = editor->pBuffer->pFirst, *pNext = NULL;
3072 ME_Style *s, *cursor2;
3073 int i;
3075 ME_ClearTempStyle(editor);
3076 ME_EmptyUndoStack(editor);
3077 editor->pBuffer->pFirst = NULL;
3078 while(p)
3080 pNext = p->next;
3081 if (p->type == diParagraph)
3082 para_destroy( editor, &p->member.para );
3083 else
3084 ME_DestroyDisplayItem(p);
3085 p = pNext;
3088 LIST_FOR_EACH_ENTRY_SAFE( s, cursor2, &editor->style_list, ME_Style, entry )
3089 ME_DestroyStyle( s );
3091 ME_ReleaseStyle(editor->pBuffer->pDefaultStyle);
3092 for (i=0; i<HFONT_CACHE_SIZE; i++)
3094 if (editor->pFontCache[i].hFont)
3095 DeleteObject(editor->pFontCache[i].hFont);
3097 if(editor->lpOleCallback)
3098 IRichEditOleCallback_Release(editor->lpOleCallback);
3100 OleUninitialize();
3102 free(editor->pBuffer);
3103 free(editor->pCursors);
3104 free(editor);
3107 static inline int get_default_line_height( ME_TextEditor *editor )
3109 int height = 0;
3111 if (editor->pBuffer && editor->pBuffer->pDefaultStyle)
3112 height = editor->pBuffer->pDefaultStyle->tm.tmHeight;
3113 if (height <= 0) height = 24;
3115 return height;
3118 static inline int calc_wheel_change( int *remain, int amount_per_click )
3120 int change = amount_per_click * (float)*remain / WHEEL_DELTA;
3121 *remain -= WHEEL_DELTA * change / amount_per_click;
3122 return change;
3125 void link_notify(ME_TextEditor *editor, UINT msg, WPARAM wParam, LPARAM lParam)
3127 int x,y;
3128 ME_Cursor cursor; /* The start of the clicked text. */
3129 ME_Run *run;
3130 ENLINK info;
3132 x = (short)LOWORD(lParam);
3133 y = (short)HIWORD(lParam);
3134 if (!cursor_from_coords( editor, x, y, &cursor )) return;
3136 if (is_link( cursor.run ))
3137 { /* The clicked run has CFE_LINK set */
3138 info.nmhdr.hwndFrom = NULL;
3139 info.nmhdr.idFrom = 0;
3140 info.nmhdr.code = EN_LINK;
3141 info.msg = msg;
3142 info.wParam = wParam;
3143 info.lParam = lParam;
3144 cursor.nOffset = 0;
3146 /* find the first contiguous run with CFE_LINK set */
3147 info.chrg.cpMin = ME_GetCursorOfs(&cursor);
3148 run = cursor.run;
3149 while ((run = run_prev( run )) && is_link( run ))
3150 info.chrg.cpMin -= run->len;
3152 /* find the last contiguous run with CFE_LINK set */
3153 info.chrg.cpMax = ME_GetCursorOfs(&cursor) + cursor.run->len;
3154 run = cursor.run;
3155 while ((run = run_next( run )) && is_link( run ))
3156 info.chrg.cpMax += run->len;
3158 ITextHost_TxNotify(editor->texthost, info.nmhdr.code, &info);
3162 void ME_ReplaceSel(ME_TextEditor *editor, BOOL can_undo, const WCHAR *str, int len)
3164 LONG from, to;
3165 int nStartCursor;
3166 ME_Style *style;
3168 nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3169 style = ME_GetSelectionInsertStyle(editor);
3170 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to-from, FALSE);
3171 ME_InsertTextFromCursor(editor, 0, str, len, style);
3172 ME_ReleaseStyle(style);
3173 /* drop temporary style if line end */
3175 * FIXME question: does abc\n mean: put abc,
3176 * clear temp style, put \n? (would require a change)
3178 if (len>0 && str[len-1] == '\n')
3179 ME_ClearTempStyle(editor);
3180 ME_CommitUndo(editor);
3181 ME_UpdateSelectionLinkAttribute(editor);
3182 if (!can_undo)
3183 ME_EmptyUndoStack(editor);
3184 ME_UpdateRepaint(editor, FALSE);
3187 static void ME_SetText(ME_TextEditor *editor, void *text, BOOL unicode)
3189 LONG codepage = unicode ? CP_UNICODE : CP_ACP;
3190 int textLen;
3192 LPWSTR wszText = ME_ToUnicode(codepage, text, &textLen);
3193 ME_InsertTextFromCursor(editor, 0, wszText, textLen, editor->pBuffer->pDefaultStyle);
3194 ME_EndToUnicode(codepage, wszText);
3197 static LRESULT handle_EM_SETCHARFORMAT( ME_TextEditor *editor, WPARAM flags, const CHARFORMAT2W *fmt_in )
3199 CHARFORMAT2W fmt;
3200 BOOL changed = TRUE;
3201 ME_Cursor start, end;
3203 if (!cfany_to_cf2w( &fmt, fmt_in )) return 0;
3205 if (flags & SCF_ALL)
3207 if (editor->mode & TM_PLAINTEXT)
3209 ME_SetDefaultCharFormat( editor, &fmt );
3211 else
3213 ME_SetCursorToStart( editor, &start );
3214 ME_SetCharFormat( editor, &start, NULL, &fmt );
3215 editor->nModifyStep = 1;
3218 else if (flags & SCF_SELECTION)
3220 if (editor->mode & TM_PLAINTEXT) return 0;
3221 if (flags & SCF_WORD)
3223 end = editor->pCursors[0];
3224 ME_MoveCursorWords( editor, &end, +1 );
3225 start = end;
3226 ME_MoveCursorWords( editor, &start, -1 );
3227 ME_SetCharFormat( editor, &start, &end, &fmt );
3229 changed = ME_IsSelection( editor );
3230 ME_SetSelectionCharFormat( editor, &fmt );
3231 if (changed) editor->nModifyStep = 1;
3233 else /* SCF_DEFAULT */
3235 ME_SetDefaultCharFormat( editor, &fmt );
3238 ME_CommitUndo( editor );
3239 if (changed)
3241 ME_WrapMarkedParagraphs( editor );
3242 ME_UpdateScrollBar( editor );
3244 return 1;
3247 #define UNSUPPORTED_MSG(e) \
3248 case e: \
3249 FIXME(#e ": stub\n"); \
3250 *phresult = S_FALSE; \
3251 return 0;
3253 /* Handle messages for windowless and windowed richedit controls.
3255 * The LRESULT that is returned is a return value for window procs,
3256 * and the phresult parameter is the COM return code needed by the
3257 * text services interface. */
3258 LRESULT editor_handle_message( ME_TextEditor *editor, UINT msg, WPARAM wParam,
3259 LPARAM lParam, HRESULT* phresult )
3261 *phresult = S_OK;
3263 switch(msg) {
3265 UNSUPPORTED_MSG(EM_DISPLAYBAND)
3266 UNSUPPORTED_MSG(EM_FINDWORDBREAK)
3267 UNSUPPORTED_MSG(EM_FMTLINES)
3268 UNSUPPORTED_MSG(EM_FORMATRANGE)
3269 UNSUPPORTED_MSG(EM_GETBIDIOPTIONS)
3270 UNSUPPORTED_MSG(EM_GETEDITSTYLE)
3271 UNSUPPORTED_MSG(EM_GETIMECOMPMODE)
3272 UNSUPPORTED_MSG(EM_GETIMESTATUS)
3273 UNSUPPORTED_MSG(EM_SETIMESTATUS)
3274 UNSUPPORTED_MSG(EM_GETLANGOPTIONS)
3275 UNSUPPORTED_MSG(EM_GETREDONAME)
3276 UNSUPPORTED_MSG(EM_GETTYPOGRAPHYOPTIONS)
3277 UNSUPPORTED_MSG(EM_GETUNDONAME)
3278 UNSUPPORTED_MSG(EM_GETWORDBREAKPROCEX)
3279 UNSUPPORTED_MSG(EM_SETBIDIOPTIONS)
3280 UNSUPPORTED_MSG(EM_SETEDITSTYLE)
3281 UNSUPPORTED_MSG(EM_SETLANGOPTIONS)
3282 UNSUPPORTED_MSG(EM_SETMARGINS)
3283 UNSUPPORTED_MSG(EM_SETPALETTE)
3284 UNSUPPORTED_MSG(EM_SETTABSTOPS)
3285 UNSUPPORTED_MSG(EM_SETTYPOGRAPHYOPTIONS)
3286 UNSUPPORTED_MSG(EM_SETWORDBREAKPROCEX)
3288 /* Messages specific to Richedit controls */
3290 case EM_STREAMIN:
3291 return ME_StreamIn(editor, wParam, (EDITSTREAM*)lParam, TRUE);
3292 case EM_STREAMOUT:
3293 return ME_StreamOut(editor, wParam, (EDITSTREAM *)lParam);
3294 case EM_EMPTYUNDOBUFFER:
3295 ME_EmptyUndoStack(editor);
3296 return 0;
3297 case EM_GETSEL:
3299 /* Note: wParam/lParam can be NULL */
3300 LONG from, to;
3301 LONG *pfrom = wParam ? (LONG *)wParam : &from;
3302 LONG *pto = lParam ? (LONG *)lParam : &to;
3303 ME_GetSelectionOfs(editor, pfrom, pto);
3304 if ((*pfrom|*pto) & 0xFFFF0000)
3305 return -1;
3306 return MAKELONG(*pfrom,*pto);
3308 case EM_EXGETSEL:
3310 CHARRANGE *pRange = (CHARRANGE *)lParam;
3311 ME_GetSelectionOfs(editor, &pRange->cpMin, &pRange->cpMax);
3312 TRACE("EM_EXGETSEL = (%ld,%ld)\n", pRange->cpMin, pRange->cpMax);
3313 return 0;
3315 case EM_SETUNDOLIMIT:
3317 editor_enable_undo(editor);
3318 if ((int)wParam < 0)
3319 editor->nUndoLimit = STACK_SIZE_DEFAULT;
3320 else
3321 editor->nUndoLimit = min(wParam, STACK_SIZE_MAX);
3322 /* Setting a max stack size keeps wine from getting killed
3323 for hogging memory. Windows allocates all this memory at once, so
3324 no program would realistically set a value above our maximum. */
3325 return editor->nUndoLimit;
3327 case EM_CANUNDO:
3328 return !list_empty( &editor->undo_stack );
3329 case EM_CANREDO:
3330 return !list_empty( &editor->redo_stack );
3331 case WM_UNDO: /* FIXME: actually not the same */
3332 case EM_UNDO:
3333 return ME_Undo(editor);
3334 case EM_REDO:
3335 return ME_Redo(editor);
3336 case EM_SETFONTSIZE:
3338 CHARFORMAT2W cf;
3339 LONG tmp_size, size;
3340 BOOL is_increase = ((LONG)wParam > 0);
3342 if (editor->mode & TM_PLAINTEXT)
3343 return FALSE;
3345 cf.cbSize = sizeof(cf);
3346 cf.dwMask = CFM_SIZE;
3347 ME_GetSelectionCharFormat(editor, &cf);
3348 tmp_size = (cf.yHeight / 20) + wParam;
3350 if (tmp_size <= 1)
3351 size = 1;
3352 else if (tmp_size > 12 && tmp_size < 28 && tmp_size % 2)
3353 size = tmp_size + (is_increase ? 1 : -1);
3354 else if (tmp_size > 28 && tmp_size < 36)
3355 size = is_increase ? 36 : 28;
3356 else if (tmp_size > 36 && tmp_size < 48)
3357 size = is_increase ? 48 : 36;
3358 else if (tmp_size > 48 && tmp_size < 72)
3359 size = is_increase ? 72 : 48;
3360 else if (tmp_size > 72 && tmp_size < 80)
3361 size = is_increase ? 80 : 72;
3362 else if (tmp_size > 80 && tmp_size < 1638)
3363 size = 10 * (is_increase ? (tmp_size / 10 + 1) : (tmp_size / 10));
3364 else if (tmp_size >= 1638)
3365 size = 1638;
3366 else
3367 size = tmp_size;
3369 cf.yHeight = size * 20; /* convert twips to points */
3370 ME_SetSelectionCharFormat(editor, &cf);
3371 ME_CommitUndo(editor);
3372 ME_WrapMarkedParagraphs(editor);
3373 ME_UpdateScrollBar(editor);
3375 return TRUE;
3377 case EM_SETSEL:
3379 return set_selection( editor, wParam, lParam );
3381 case EM_SETSCROLLPOS:
3383 POINT *point = (POINT *)lParam;
3384 scroll_abs( editor, point->x, point->y, TRUE );
3385 return 0;
3387 case EM_AUTOURLDETECT:
3389 if (wParam==1 || wParam ==0)
3391 editor->AutoURLDetect_bEnable = (BOOL)wParam;
3392 return 0;
3394 return E_INVALIDARG;
3396 case EM_GETAUTOURLDETECT:
3398 return editor->AutoURLDetect_bEnable;
3400 case EM_EXSETSEL:
3402 CHARRANGE range = *(CHARRANGE *)lParam;
3404 return set_selection( editor, range.cpMin, range.cpMax );
3406 case EM_SETTEXTEX:
3408 LPWSTR wszText;
3409 SETTEXTEX *pStruct = (SETTEXTEX *)wParam;
3410 LONG from, to;
3411 int len;
3412 ME_Style *style;
3413 BOOL bRtf, bUnicode, bSelection, bUTF8;
3414 int oldModify = editor->nModifyStep;
3415 static const char utf8_bom[] = {0xef, 0xbb, 0xbf};
3417 if (!pStruct) return 0;
3419 /* If we detect ascii rtf at the start of the string,
3420 * we know it isn't unicode. */
3421 bRtf = (lParam && (!strncmp((char *)lParam, "{\\rtf", 5) ||
3422 !strncmp((char *)lParam, "{\\urtf", 6)));
3423 bUnicode = !bRtf && pStruct->codepage == CP_UNICODE;
3424 bUTF8 = (lParam && (!strncmp((char *)lParam, utf8_bom, 3)));
3426 TRACE("EM_SETTEXTEX - %s, flags %ld, cp %d\n",
3427 bUnicode ? debugstr_w((LPCWSTR)lParam) : debugstr_a((LPCSTR)lParam),
3428 pStruct->flags, pStruct->codepage);
3430 bSelection = (pStruct->flags & ST_SELECTION) != 0;
3431 if (bSelection) {
3432 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3433 style = ME_GetSelectionInsertStyle(editor);
3434 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to - from, FALSE);
3435 } else {
3436 ME_Cursor start;
3437 ME_SetCursorToStart(editor, &start);
3438 ME_InternalDeleteText(editor, &start, ME_GetTextLength(editor), FALSE);
3439 style = editor->pBuffer->pDefaultStyle;
3442 if (bRtf) {
3443 ME_StreamInRTFString(editor, bSelection, (char *)lParam);
3444 if (bSelection) {
3445 /* FIXME: The length returned doesn't include the rtf control
3446 * characters, only the actual text. */
3447 len = lParam ? strlen((char *)lParam) : 0;
3449 } else {
3450 if (bUTF8 && !bUnicode) {
3451 wszText = ME_ToUnicode(CP_UTF8, (void *)(lParam+3), &len);
3452 ME_InsertTextFromCursor(editor, 0, wszText, len, style);
3453 ME_EndToUnicode(CP_UTF8, wszText);
3454 } else {
3455 wszText = ME_ToUnicode(pStruct->codepage, (void *)lParam, &len);
3456 ME_InsertTextFromCursor(editor, 0, wszText, len, style);
3457 ME_EndToUnicode(pStruct->codepage, wszText);
3461 if (bSelection) {
3462 ME_ReleaseStyle(style);
3463 ME_UpdateSelectionLinkAttribute(editor);
3464 } else {
3465 ME_Cursor cursor;
3466 len = 1;
3467 ME_SetCursorToStart(editor, &cursor);
3468 ME_UpdateLinkAttribute(editor, &cursor, INT_MAX);
3470 ME_CommitUndo(editor);
3471 if (!(pStruct->flags & ST_KEEPUNDO))
3473 editor->nModifyStep = oldModify;
3474 ME_EmptyUndoStack(editor);
3476 ME_UpdateRepaint(editor, FALSE);
3477 return len;
3479 case EM_SELECTIONTYPE:
3480 return ME_GetSelectionType(editor);
3481 case EM_GETMODIFY:
3482 return editor->nModifyStep == 0 ? 0 : -1;
3483 case EM_SETMODIFY:
3485 if (wParam)
3486 editor->nModifyStep = 1;
3487 else
3488 editor->nModifyStep = 0;
3490 return 0;
3492 case EM_SETEVENTMASK:
3494 DWORD nOldMask = editor->nEventMask;
3496 editor->nEventMask = lParam;
3497 return nOldMask;
3499 case EM_GETEVENTMASK:
3500 return editor->nEventMask;
3501 case EM_SETCHARFORMAT:
3502 return handle_EM_SETCHARFORMAT( editor, wParam, (CHARFORMAT2W *)lParam );
3503 case EM_GETCHARFORMAT:
3505 CHARFORMAT2W tmp, *dst = (CHARFORMAT2W *)lParam;
3506 if (dst->cbSize != sizeof(CHARFORMATA) &&
3507 dst->cbSize != sizeof(CHARFORMATW) &&
3508 dst->cbSize != sizeof(CHARFORMAT2A) &&
3509 dst->cbSize != sizeof(CHARFORMAT2W))
3510 return 0;
3511 tmp.cbSize = sizeof(tmp);
3512 if (!wParam)
3513 ME_GetDefaultCharFormat(editor, &tmp);
3514 else
3515 ME_GetSelectionCharFormat(editor, &tmp);
3516 cf2w_to_cfany(dst, &tmp);
3517 return tmp.dwMask;
3519 case EM_SETPARAFORMAT:
3521 BOOL result = editor_set_selection_para_fmt( editor, (PARAFORMAT2 *)lParam );
3522 ME_WrapMarkedParagraphs(editor);
3523 ME_UpdateScrollBar(editor);
3524 ME_CommitUndo(editor);
3525 return result;
3527 case EM_GETPARAFORMAT:
3528 editor_get_selection_para_fmt( editor, (PARAFORMAT2 *)lParam );
3529 return ((PARAFORMAT2 *)lParam)->dwMask;
3530 case EM_GETFIRSTVISIBLELINE:
3532 ME_Paragraph *para = editor_first_para( editor );
3533 ME_Row *row;
3534 int y = editor->vert_si.nPos;
3535 int count = 0;
3537 while (para_next( para ))
3539 if (y < para->pt.y + para->nHeight) break;
3540 count += para->nRows;
3541 para = para_next( para );
3544 row = para_first_row( para );
3545 while (row)
3547 if (y < para->pt.y + row->pt.y + row->nHeight) break;
3548 count++;
3549 row = row_next( row );
3551 return count;
3553 case EM_HIDESELECTION:
3555 editor->bHideSelection = (wParam != 0);
3556 ME_InvalidateSelection(editor);
3557 return 0;
3559 case EM_LINESCROLL:
3561 if (!(editor->props & TXTBIT_MULTILINE))
3562 return FALSE;
3563 ME_ScrollDown( editor, lParam * get_default_line_height( editor ) );
3564 return TRUE;
3566 case WM_CLEAR:
3568 LONG from, to;
3569 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3570 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to-from, FALSE);
3571 ME_CommitUndo(editor);
3572 ME_UpdateRepaint(editor, TRUE);
3573 return 0;
3575 case EM_REPLACESEL:
3577 WCHAR *text = (WCHAR *)lParam;
3578 int len = text ? lstrlenW( text ) : 0;
3580 TRACE( "EM_REPLACESEL - %s\n", debugstr_w( text ) );
3581 ME_ReplaceSel( editor, !!wParam, text, len );
3582 return len;
3584 case EM_SCROLLCARET:
3585 editor_ensure_visible( editor, &editor->pCursors[0] );
3586 return 0;
3587 case WM_SETFONT:
3589 LOGFONTW lf;
3590 CHARFORMAT2W fmt;
3591 HDC hDC;
3592 BOOL bRepaint = LOWORD(lParam);
3594 if (!wParam)
3595 wParam = (WPARAM)GetStockObject(SYSTEM_FONT);
3597 if (!GetObjectW((HGDIOBJ)wParam, sizeof(LOGFONTW), &lf))
3598 return 0;
3600 hDC = ITextHost_TxGetDC(editor->texthost);
3601 ME_CharFormatFromLogFont(hDC, &lf, &fmt);
3602 ITextHost_TxReleaseDC(editor->texthost, hDC);
3603 if (editor->mode & TM_RICHTEXT) {
3604 ME_Cursor start;
3605 ME_SetCursorToStart(editor, &start);
3606 ME_SetCharFormat(editor, &start, NULL, &fmt);
3608 ME_SetDefaultCharFormat(editor, &fmt);
3610 ME_CommitUndo(editor);
3611 editor_mark_rewrap_all( editor );
3612 ME_WrapMarkedParagraphs(editor);
3613 ME_UpdateScrollBar(editor);
3614 if (bRepaint)
3615 ME_Repaint(editor);
3616 return 0;
3618 case WM_SETTEXT:
3620 ME_Cursor cursor;
3621 ME_SetCursorToStart(editor, &cursor);
3622 ME_InternalDeleteText(editor, &cursor, ME_GetTextLength(editor), FALSE);
3623 if (lParam)
3625 TRACE("WM_SETTEXT lParam==%Ix\n",lParam);
3626 if (!strncmp((char *)lParam, "{\\rtf", 5) ||
3627 !strncmp((char *)lParam, "{\\urtf", 6))
3629 /* Undocumented: WM_SETTEXT supports RTF text */
3630 ME_StreamInRTFString(editor, 0, (char *)lParam);
3632 else
3633 ME_SetText( editor, (void*)lParam, TRUE );
3635 else
3636 TRACE("WM_SETTEXT - NULL\n");
3637 ME_SetCursorToStart(editor, &cursor);
3638 ME_UpdateLinkAttribute(editor, &cursor, INT_MAX);
3639 set_selection_cursors(editor, 0, 0);
3640 editor->nModifyStep = 0;
3641 ME_CommitUndo(editor);
3642 ME_EmptyUndoStack(editor);
3643 ME_UpdateRepaint(editor, FALSE);
3644 return 1;
3646 case EM_CANPASTE:
3647 return paste_special( editor, 0, NULL, TRUE );
3648 case WM_PASTE:
3649 case WM_MBUTTONDOWN:
3650 wParam = 0;
3651 lParam = 0;
3652 /* fall through */
3653 case EM_PASTESPECIAL:
3654 paste_special( editor, wParam, (REPASTESPECIAL *)lParam, FALSE );
3655 return 0;
3656 case WM_CUT:
3657 case WM_COPY:
3658 copy_or_cut(editor, msg == WM_CUT);
3659 return 0;
3660 case WM_GETTEXTLENGTH:
3662 GETTEXTLENGTHEX how;
3663 how.flags = GTL_CLOSE | (editor->bEmulateVersion10 ? 0 : GTL_USECRLF) | GTL_NUMCHARS;
3664 how.codepage = CP_UNICODE;
3665 return ME_GetTextLengthEx(editor, &how);
3667 case EM_GETTEXTLENGTHEX:
3668 return ME_GetTextLengthEx(editor, (GETTEXTLENGTHEX *)wParam);
3669 case WM_GETTEXT:
3671 GETTEXTEX ex;
3672 ex.cb = wParam * sizeof(WCHAR);
3673 ex.flags = GT_USECRLF;
3674 ex.codepage = CP_UNICODE;
3675 ex.lpDefaultChar = NULL;
3676 ex.lpUsedDefChar = NULL;
3677 return ME_GetTextEx(editor, &ex, lParam);
3679 case EM_GETTEXTEX:
3680 return ME_GetTextEx(editor, (GETTEXTEX*)wParam, lParam);
3681 case EM_GETSELTEXT:
3683 LONG nFrom, nTo;
3684 int nStartCur = ME_GetSelectionOfs(editor, &nFrom, &nTo);
3685 ME_Cursor *from = &editor->pCursors[nStartCur];
3686 return get_text_range( editor, (WCHAR *)lParam, from, nTo - nFrom );
3688 case EM_GETSCROLLPOS:
3690 POINT *point = (POINT *)lParam;
3691 point->x = editor->horz_si.nPos;
3692 point->y = editor->vert_si.nPos;
3693 /* 16-bit scaled value is returned as stored in scrollinfo */
3694 if (editor->horz_si.nMax > 0xffff)
3695 point->x = MulDiv(point->x, 0xffff, editor->horz_si.nMax);
3696 if (editor->vert_si.nMax > 0xffff)
3697 point->y = MulDiv(point->y, 0xffff, editor->vert_si.nMax);
3698 return 1;
3700 case EM_GETTEXTRANGE:
3702 TEXTRANGEW *rng = (TEXTRANGEW *)lParam;
3703 ME_Cursor start;
3704 int nStart = rng->chrg.cpMin;
3705 int nEnd = rng->chrg.cpMax;
3706 int textlength = ME_GetTextLength(editor);
3708 TRACE( "EM_GETTEXTRANGE min = %ld max = %ld textlength = %d\n", rng->chrg.cpMin, rng->chrg.cpMax, textlength );
3709 if (nStart < 0) return 0;
3710 if ((nStart == 0 && nEnd == -1) || nEnd > textlength)
3711 nEnd = textlength;
3712 if (nStart >= nEnd) return 0;
3714 cursor_from_char_ofs( editor, nStart, &start );
3715 return get_text_range( editor, rng->lpstrText, &start, nEnd - nStart );
3717 case EM_GETLINE:
3719 ME_Row *row;
3720 ME_Run *run;
3721 const unsigned int nMaxChars = *(WORD *) lParam;
3722 unsigned int nCharsLeft = nMaxChars;
3723 char *dest = (char *) lParam;
3724 ME_Cursor start, end;
3726 TRACE( "EM_GETLINE: row=%d, nMaxChars=%d\n", (int)wParam, nMaxChars );
3728 row = row_from_row_number( editor, wParam );
3729 if (row == NULL) return 0;
3731 row_first_cursor( row, &start );
3732 row_end_cursor( row, &end, TRUE );
3733 run = start.run;
3734 while (nCharsLeft)
3736 WCHAR *str;
3737 unsigned int nCopy;
3738 int ofs = (run == start.run) ? start.nOffset : 0;
3739 int len = (run == end.run) ? end.nOffset : run->len;
3741 str = get_text( run, ofs );
3742 nCopy = min( nCharsLeft, len );
3744 memcpy(dest, str, nCopy * sizeof(WCHAR));
3745 dest += nCopy * sizeof(WCHAR);
3746 nCharsLeft -= nCopy;
3747 if (run == end.run) break;
3748 run = row_next_run( row, run );
3751 /* append line termination, space allowing */
3752 if (nCharsLeft > 0) *((WCHAR *)dest) = '\0';
3754 TRACE("EM_GETLINE: got %u characters\n", nMaxChars - nCharsLeft);
3755 return nMaxChars - nCharsLeft;
3757 case EM_GETLINECOUNT:
3759 int count = editor->total_rows;
3760 ME_Run *prev_run, *last_run;
3762 last_run = para_end_run( para_prev( editor_end_para( editor ) ) );
3763 prev_run = run_prev_all_paras( last_run );
3765 if (editor->bEmulateVersion10 && prev_run && last_run->nCharOfs == 0 &&
3766 prev_run->len == 1 && *get_text( prev_run, 0 ) == '\r')
3768 /* In 1.0 emulation, the last solitary \r at the very end of the text
3769 (if one exists) is NOT a line break.
3770 FIXME: this is an ugly hack. This should have a more regular model. */
3771 count--;
3774 count = max(1, count);
3775 TRACE("EM_GETLINECOUNT: count==%d\n", count);
3776 return count;
3778 case EM_LINEFROMCHAR:
3780 if (wParam == -1) wParam = ME_GetCursorOfs( editor->pCursors + 1 );
3781 return row_number_from_char_ofs( editor, wParam );
3783 case EM_EXLINEFROMCHAR:
3785 if (lParam == -1) lParam = ME_GetCursorOfs( editor->pCursors + 1 );
3786 return row_number_from_char_ofs( editor, lParam );
3788 case EM_LINEINDEX:
3790 ME_Row *row;
3791 ME_Cursor cursor;
3792 int ofs;
3794 if (wParam == -1) row = row_from_cursor( editor->pCursors );
3795 else row = row_from_row_number( editor, wParam );
3796 if (!row) return -1;
3798 row_first_cursor( row, &cursor );
3799 ofs = ME_GetCursorOfs( &cursor );
3800 TRACE( "EM_LINEINDEX: nCharOfs==%d\n", ofs );
3801 return ofs;
3803 case EM_LINELENGTH:
3805 ME_Row *row;
3806 int start_ofs, end_ofs;
3807 ME_Cursor cursor;
3809 if (wParam > ME_GetTextLength(editor))
3810 return 0;
3811 if (wParam == -1)
3813 FIXME("EM_LINELENGTH: returning number of unselected characters on lines with selection unsupported.\n");
3814 return 0;
3816 cursor_from_char_ofs( editor, wParam, &cursor );
3817 row = row_from_cursor( &cursor );
3818 row_first_cursor( row, &cursor );
3819 start_ofs = ME_GetCursorOfs( &cursor );
3820 row_end_cursor( row, &cursor, FALSE );
3821 end_ofs = ME_GetCursorOfs( &cursor );
3822 TRACE( "EM_LINELENGTH(%Id)==%d\n", wParam, end_ofs - start_ofs );
3823 return end_ofs - start_ofs;
3825 case EM_EXLIMITTEXT:
3827 if ((int)lParam < 0)
3828 return 0;
3829 if (lParam == 0)
3830 editor->nTextLimit = 65536;
3831 else
3832 editor->nTextLimit = (int) lParam;
3833 return 0;
3835 case EM_LIMITTEXT:
3837 if (wParam == 0)
3838 editor->nTextLimit = 65536;
3839 else
3840 editor->nTextLimit = (int) wParam;
3841 return 0;
3843 case EM_GETLIMITTEXT:
3845 return editor->nTextLimit;
3847 case EM_FINDTEXT:
3848 case EM_FINDTEXTW:
3850 FINDTEXTW *ft = (FINDTEXTW *)lParam;
3851 return ME_FindText(editor, wParam, &ft->chrg, ft->lpstrText, NULL);
3853 case EM_FINDTEXTEX:
3854 case EM_FINDTEXTEXW:
3856 FINDTEXTEXW *ex = (FINDTEXTEXW *)lParam;
3857 return ME_FindText(editor, wParam, &ex->chrg, ex->lpstrText, &ex->chrgText);
3859 case EM_GETZOOM:
3860 if (!wParam || !lParam)
3861 return FALSE;
3862 *(int *)wParam = editor->nZoomNumerator;
3863 *(int *)lParam = editor->nZoomDenominator;
3864 return TRUE;
3865 case EM_SETZOOM:
3866 return ME_SetZoom(editor, wParam, lParam);
3867 case EM_CHARFROMPOS:
3869 ME_Cursor cursor;
3870 POINTL *pt = (POINTL *)lParam;
3872 cursor_from_coords(editor, pt->x, pt->y, &cursor);
3873 return ME_GetCursorOfs(&cursor);
3875 case EM_POSFROMCHAR:
3877 ME_Cursor cursor;
3878 int nCharOfs, nLength;
3879 POINTL pt = {0,0};
3881 nCharOfs = wParam;
3882 /* detect which API version we're dealing with */
3883 if (wParam >= 0x40000)
3884 nCharOfs = lParam;
3885 nLength = ME_GetTextLength(editor);
3886 nCharOfs = min(nCharOfs, nLength);
3887 nCharOfs = max(nCharOfs, 0);
3889 cursor_from_char_ofs( editor, nCharOfs, &cursor );
3890 pt.y = cursor.run->pt.y;
3891 pt.x = cursor.run->pt.x +
3892 ME_PointFromChar( editor, cursor.run, cursor.nOffset, TRUE );
3893 pt.y += cursor.para->pt.y + editor->rcFormat.top;
3894 pt.x += editor->rcFormat.left;
3896 pt.x -= editor->horz_si.nPos;
3897 pt.y -= editor->vert_si.nPos;
3899 if (wParam >= 0x40000) *(POINTL *)wParam = pt;
3901 return (wParam >= 0x40000) ? 0 : MAKELONG( pt.x, pt.y );
3903 case WM_LBUTTONDBLCLK:
3904 case WM_LBUTTONDOWN:
3906 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
3907 ITextHost_TxSetFocus(editor->texthost);
3908 ME_LButtonDown(editor, (short)LOWORD(lParam), (short)HIWORD(lParam),
3909 ME_CalculateClickCount(editor, msg, wParam, lParam));
3910 ITextHost_TxSetCapture(editor->texthost, TRUE);
3911 editor->bMouseCaptured = TRUE;
3912 link_notify( editor, msg, wParam, lParam );
3913 break;
3915 case WM_MOUSEMOVE:
3916 if (editor->bMouseCaptured)
3917 ME_MouseMove(editor, (short)LOWORD(lParam), (short)HIWORD(lParam));
3918 else
3919 link_notify( editor, msg, wParam, lParam );
3920 break;
3921 case WM_LBUTTONUP:
3922 if (editor->bMouseCaptured) {
3923 ITextHost_TxSetCapture(editor->texthost, FALSE);
3924 editor->bMouseCaptured = FALSE;
3926 if (editor->nSelectionType == stDocument)
3927 editor->nSelectionType = stPosition;
3928 else
3930 link_notify( editor, msg, wParam, lParam );
3932 break;
3933 case WM_RBUTTONUP:
3934 case WM_RBUTTONDOWN:
3935 case WM_RBUTTONDBLCLK:
3936 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
3937 link_notify( editor, msg, wParam, lParam );
3938 goto do_default;
3939 case WM_CONTEXTMENU:
3940 if (!ME_ShowContextMenu(editor, (short)LOWORD(lParam), (short)HIWORD(lParam)))
3941 goto do_default;
3942 break;
3943 case WM_SETFOCUS:
3944 editor->bHaveFocus = TRUE;
3945 create_caret(editor);
3946 update_caret(editor);
3947 ITextHost_TxNotify( editor->texthost, EN_SETFOCUS, NULL );
3948 if (!editor->bHideSelection && (editor->props & TXTBIT_HIDESELECTION))
3949 ME_InvalidateSelection( editor );
3950 return 0;
3951 case WM_KILLFOCUS:
3952 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
3953 editor->bHaveFocus = FALSE;
3954 editor->wheel_remain = 0;
3955 hide_caret(editor);
3956 DestroyCaret();
3957 ITextHost_TxNotify( editor->texthost, EN_KILLFOCUS, NULL );
3958 if (!editor->bHideSelection && (editor->props & TXTBIT_HIDESELECTION))
3959 ME_InvalidateSelection( editor );
3960 return 0;
3961 case WM_COMMAND:
3962 TRACE("editor wnd command = %d\n", LOWORD(wParam));
3963 return 0;
3964 case WM_KEYDOWN:
3965 if (ME_KeyDown(editor, LOWORD(wParam)))
3966 return 0;
3967 goto do_default;
3968 case WM_CHAR:
3969 return handle_wm_char( editor, wParam, lParam );
3970 case WM_UNICHAR:
3971 if (wParam == UNICODE_NOCHAR) return TRUE;
3972 if (wParam <= 0x000fffff)
3974 if (wParam > 0xffff) /* convert to surrogates */
3976 wParam -= 0x10000;
3977 handle_wm_char( editor, (wParam >> 10) + 0xd800, 0 );
3978 handle_wm_char( editor, (wParam & 0x03ff) + 0xdc00, 0 );
3980 else
3981 handle_wm_char( editor, wParam, 0 );
3983 return 0;
3984 case EM_STOPGROUPTYPING:
3985 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
3986 return 0;
3987 case WM_HSCROLL:
3989 const int scrollUnit = 7;
3991 switch(LOWORD(wParam))
3993 case SB_LEFT:
3994 scroll_abs( editor, 0, 0, TRUE );
3995 break;
3996 case SB_RIGHT:
3997 scroll_abs( editor, editor->horz_si.nMax - (int)editor->horz_si.nPage,
3998 editor->vert_si.nMax - (int)editor->vert_si.nPage, TRUE );
3999 break;
4000 case SB_LINELEFT:
4001 ME_ScrollLeft(editor, scrollUnit);
4002 break;
4003 case SB_LINERIGHT:
4004 ME_ScrollRight(editor, scrollUnit);
4005 break;
4006 case SB_PAGELEFT:
4007 ME_ScrollLeft(editor, editor->sizeWindow.cx);
4008 break;
4009 case SB_PAGERIGHT:
4010 ME_ScrollRight(editor, editor->sizeWindow.cx);
4011 break;
4012 case SB_THUMBTRACK:
4013 case SB_THUMBPOSITION:
4015 int pos = HIWORD(wParam);
4016 if (editor->horz_si.nMax > 0xffff)
4017 pos = MulDiv(pos, editor->horz_si.nMax, 0xffff);
4018 scroll_h_abs( editor, pos, FALSE );
4019 break;
4022 break;
4024 case EM_SCROLL: /* fall through */
4025 case WM_VSCROLL:
4027 int origNPos;
4028 int lineHeight = get_default_line_height( editor );
4030 origNPos = editor->vert_si.nPos;
4032 switch(LOWORD(wParam))
4034 case SB_TOP:
4035 scroll_abs( editor, 0, 0, TRUE );
4036 break;
4037 case SB_BOTTOM:
4038 scroll_abs( editor, editor->horz_si.nMax - (int)editor->horz_si.nPage,
4039 editor->vert_si.nMax - (int)editor->vert_si.nPage, TRUE );
4040 break;
4041 case SB_LINEUP:
4042 ME_ScrollUp(editor,lineHeight);
4043 break;
4044 case SB_LINEDOWN:
4045 ME_ScrollDown(editor,lineHeight);
4046 break;
4047 case SB_PAGEUP:
4048 ME_ScrollUp(editor,editor->sizeWindow.cy);
4049 break;
4050 case SB_PAGEDOWN:
4051 ME_ScrollDown(editor,editor->sizeWindow.cy);
4052 break;
4053 case SB_THUMBTRACK:
4054 case SB_THUMBPOSITION:
4056 int pos = HIWORD(wParam);
4057 if (editor->vert_si.nMax > 0xffff)
4058 pos = MulDiv(pos, editor->vert_si.nMax, 0xffff);
4059 scroll_v_abs( editor, pos, FALSE );
4060 break;
4063 if (msg == EM_SCROLL)
4064 return 0x00010000 | (((editor->vert_si.nPos - origNPos)/lineHeight) & 0xffff);
4065 break;
4067 case WM_MOUSEWHEEL:
4069 int delta = GET_WHEEL_DELTA_WPARAM( wParam );
4070 BOOL ctrl_is_down = GetKeyState( VK_CONTROL ) & 0x8000;
4072 /* if scrolling changes direction, ignore left overs */
4073 if ((delta < 0 && editor->wheel_remain < 0) ||
4074 (delta > 0 && editor->wheel_remain > 0))
4075 editor->wheel_remain += delta;
4076 else
4077 editor->wheel_remain = delta;
4079 if (editor->wheel_remain)
4081 if (ctrl_is_down) {
4082 int numerator;
4083 if (!editor->nZoomNumerator || !editor->nZoomDenominator)
4085 numerator = 100;
4086 } else {
4087 numerator = editor->nZoomNumerator * 100 / editor->nZoomDenominator;
4089 numerator += calc_wheel_change( &editor->wheel_remain, 10 );
4090 if (numerator >= 10 && numerator <= 500)
4091 ME_SetZoom(editor, numerator, 100);
4092 } else {
4093 UINT max_lines = 3;
4094 int lines = 0;
4096 SystemParametersInfoW( SPI_GETWHEELSCROLLLINES, 0, &max_lines, 0 );
4097 if (max_lines)
4098 lines = calc_wheel_change( &editor->wheel_remain, (int)max_lines );
4099 if (lines)
4100 ME_ScrollDown( editor, -lines * get_default_line_height( editor ) );
4103 break;
4105 case EM_REQUESTRESIZE:
4106 ME_SendRequestResize(editor, TRUE);
4107 return 0;
4108 /* IME messages to make richedit controls IME aware */
4109 case WM_IME_SETCONTEXT:
4110 case WM_IME_CONTROL:
4111 case WM_IME_SELECT:
4112 case WM_IME_COMPOSITIONFULL:
4113 return 0;
4114 case WM_IME_STARTCOMPOSITION:
4116 ME_DeleteSelection(editor);
4117 editor->imeStartIndex = ME_GetCursorOfs(&editor->pCursors[0]);
4118 ME_CommitUndo(editor);
4119 ME_UpdateRepaint(editor, FALSE);
4120 return 0;
4122 case WM_IME_COMPOSITION:
4124 HIMC hIMC;
4126 ME_Style *style = style_get_insert_style( editor, editor->pCursors );
4127 hIMC = ITextHost_TxImmGetContext(editor->texthost);
4128 ME_DeleteSelection(editor);
4129 ME_SaveTempStyle(editor, style);
4130 if (lParam & (GCS_RESULTSTR|GCS_COMPSTR))
4132 LPWSTR lpCompStr = NULL;
4133 DWORD dwBufLen;
4134 DWORD dwIndex = lParam & GCS_RESULTSTR;
4135 if (!dwIndex)
4136 dwIndex = GCS_COMPSTR;
4138 dwBufLen = ImmGetCompositionStringW(hIMC, dwIndex, NULL, 0);
4139 lpCompStr = malloc(dwBufLen + sizeof(WCHAR));
4140 ImmGetCompositionStringW(hIMC, dwIndex, lpCompStr, dwBufLen);
4141 lpCompStr[dwBufLen/sizeof(WCHAR)] = 0;
4142 ME_InsertTextFromCursor(editor,0,lpCompStr,dwBufLen/sizeof(WCHAR),style);
4143 free(lpCompStr);
4145 if (dwIndex == GCS_COMPSTR)
4146 set_selection_cursors(editor,editor->imeStartIndex,
4147 editor->imeStartIndex + dwBufLen/sizeof(WCHAR));
4148 else
4149 editor->imeStartIndex = ME_GetCursorOfs(&editor->pCursors[0]);
4151 ME_ReleaseStyle(style);
4152 ME_CommitUndo(editor);
4153 ME_UpdateRepaint(editor, FALSE);
4154 return 0;
4156 case WM_IME_ENDCOMPOSITION:
4158 ME_DeleteSelection(editor);
4159 editor->imeStartIndex=-1;
4160 return 0;
4162 case EM_GETOLEINTERFACE:
4163 IRichEditOle_AddRef( editor->richole );
4164 *(IRichEditOle **)lParam = editor->richole;
4165 return 1;
4167 case EM_SETOLECALLBACK:
4168 if(editor->lpOleCallback)
4169 IRichEditOleCallback_Release(editor->lpOleCallback);
4170 editor->lpOleCallback = (IRichEditOleCallback*)lParam;
4171 if(editor->lpOleCallback)
4172 IRichEditOleCallback_AddRef(editor->lpOleCallback);
4173 return TRUE;
4174 case EM_GETWORDBREAKPROC:
4175 return (LRESULT)editor->pfnWordBreak;
4176 case EM_SETWORDBREAKPROC:
4178 EDITWORDBREAKPROCW pfnOld = editor->pfnWordBreak;
4180 editor->pfnWordBreak = (EDITWORDBREAKPROCW)lParam;
4181 return (LRESULT)pfnOld;
4183 case EM_GETTEXTMODE:
4184 return editor->mode;
4185 case EM_SETTEXTMODE:
4187 int mask = 0;
4188 int changes = 0;
4190 if (ME_GetTextLength(editor) ||
4191 !list_empty( &editor->undo_stack ) || !list_empty( &editor->redo_stack ))
4192 return E_UNEXPECTED;
4194 /* Check for mutually exclusive flags in adjacent bits of wParam */
4195 if ((wParam & (TM_RICHTEXT | TM_MULTILEVELUNDO | TM_MULTICODEPAGE)) &
4196 (wParam & (TM_PLAINTEXT | TM_SINGLELEVELUNDO | TM_SINGLECODEPAGE)) << 1)
4197 return E_INVALIDARG;
4199 if (wParam & (TM_RICHTEXT | TM_PLAINTEXT))
4201 mask |= TM_RICHTEXT | TM_PLAINTEXT;
4202 changes |= wParam & (TM_RICHTEXT | TM_PLAINTEXT);
4203 if (wParam & TM_PLAINTEXT) {
4204 /* Clear selection since it should be possible to select the
4205 * end of text run for rich text */
4206 ME_InvalidateSelection(editor);
4207 ME_SetCursorToStart(editor, &editor->pCursors[0]);
4208 editor->pCursors[1] = editor->pCursors[0];
4209 /* plain text can only have the default style. */
4210 ME_ClearTempStyle(editor);
4211 ME_AddRefStyle(editor->pBuffer->pDefaultStyle);
4212 ME_ReleaseStyle( editor->pCursors[0].run->style );
4213 editor->pCursors[0].run->style = editor->pBuffer->pDefaultStyle;
4216 /* FIXME: Currently no support for undo level and code page options */
4217 editor->mode = (editor->mode & ~mask) | changes;
4218 return 0;
4220 case EM_SETTARGETDEVICE:
4221 if (wParam == 0)
4223 BOOL new = (lParam == 0 && (editor->props & TXTBIT_MULTILINE));
4224 if (editor->nAvailWidth || editor->bWordWrap != new)
4226 editor->bWordWrap = new;
4227 editor->nAvailWidth = 0; /* wrap to client area */
4228 ME_RewrapRepaint(editor);
4230 } else {
4231 int width = max(0, lParam);
4232 if ((editor->props & TXTBIT_MULTILINE) &&
4233 (!editor->bWordWrap || editor->nAvailWidth != width))
4235 editor->nAvailWidth = width;
4236 editor->bWordWrap = TRUE;
4237 ME_RewrapRepaint(editor);
4239 FIXME("EM_SETTARGETDEVICE doesn't use non-NULL target devices\n");
4241 return TRUE;
4242 default:
4243 do_default:
4244 *phresult = S_FALSE;
4245 break;
4247 return 0L;
4250 /* Fill buffer with srcChars unicode characters from the start cursor.
4252 * buffer: destination buffer
4253 * buflen: length of buffer in characters excluding the NULL terminator.
4254 * start: start of editor text to copy into buffer.
4255 * srcChars: Number of characters to use from the editor text.
4256 * bCRLF: if true, replaces all end of lines with \r\n pairs.
4258 * returns the number of characters written excluding the NULL terminator.
4260 * The written text is always NULL terminated.
4262 int ME_GetTextW(ME_TextEditor *editor, WCHAR *buffer, int buflen,
4263 const ME_Cursor *start, int srcChars, BOOL bCRLF,
4264 BOOL bEOP)
4266 ME_Run *run, *next_run;
4267 const WCHAR *pStart = buffer;
4268 const WCHAR *str;
4269 int nLen;
4271 /* bCRLF flag is only honored in 2.0 and up. 1.0 must always return text verbatim */
4272 if (editor->bEmulateVersion10) bCRLF = FALSE;
4274 run = start->run;
4275 next_run = run_next_all_paras( run );
4277 nLen = run->len - start->nOffset;
4278 str = get_text( run, start->nOffset );
4280 while (srcChars && buflen && next_run)
4282 if (bCRLF && run->nFlags & MERF_ENDPARA && ~run->nFlags & MERF_ENDCELL)
4284 if (buflen == 1) break;
4285 /* FIXME: native fails to reduce srcChars here for WM_GETTEXT or
4286 * EM_GETTEXTEX, however, this is done for copying text which
4287 * also uses this function. */
4288 srcChars -= min(nLen, srcChars);
4289 nLen = 2;
4290 str = L"\r\n";
4292 else
4294 nLen = min(nLen, srcChars);
4295 srcChars -= nLen;
4298 nLen = min(nLen, buflen);
4299 buflen -= nLen;
4301 CopyMemory(buffer, str, sizeof(WCHAR) * nLen);
4303 buffer += nLen;
4305 run = next_run;
4306 next_run = run_next_all_paras( run );
4308 nLen = run->len;
4309 str = get_text( run, 0 );
4311 /* append '\r' to the last paragraph. */
4312 if (run == para_end_run( para_prev( editor_end_para( editor ) ) ) && bEOP)
4314 *buffer = '\r';
4315 buffer ++;
4317 *buffer = 0;
4318 return buffer - pStart;
4321 static int __cdecl wchar_comp( const void *key, const void *elem )
4323 return *(const WCHAR *)key - *(const WCHAR *)elem;
4326 /* neutral characters end the url if the next non-neutral character is a space character,
4327 otherwise they are included in the url. */
4328 static BOOL isurlneutral( WCHAR c )
4330 /* NB this list is sorted */
4331 static const WCHAR neutral_chars[] = L"!\"'(),-.:;<>?[]{}";
4333 /* Some shortcuts */
4334 if (isalnum( c )) return FALSE;
4335 if (c > L'}') return FALSE;
4337 return !!bsearch( &c, neutral_chars, ARRAY_SIZE( neutral_chars ) - 1, sizeof(c), wchar_comp );
4341 * This proc takes a selection, and scans it forward in order to select the span
4342 * of a possible URL candidate. A possible URL candidate must start with isalnum
4343 * or one of the following special characters: *|/\+%#@ and must consist entirely
4344 * of the characters allowed to start the URL, plus : (colon) which may occur
4345 * at most once, and not at either end.
4347 static BOOL ME_FindNextURLCandidate(ME_TextEditor *editor,
4348 const ME_Cursor *start,
4349 int nChars,
4350 ME_Cursor *candidate_min,
4351 ME_Cursor *candidate_max)
4353 ME_Cursor cursor = *start, neutral_end, space_end;
4354 BOOL candidateStarted = FALSE, quoted = FALSE;
4355 WCHAR c;
4357 while (nChars > 0)
4359 WCHAR *str = get_text( cursor.run, 0 );
4360 int run_len = cursor.run->len;
4362 nChars -= run_len - cursor.nOffset;
4364 /* Find start of candidate */
4365 if (!candidateStarted)
4367 while (cursor.nOffset < run_len)
4369 c = str[cursor.nOffset];
4370 if (!iswspace( c ) && !isurlneutral( c ))
4372 *candidate_min = cursor;
4373 candidateStarted = TRUE;
4374 neutral_end.para = NULL;
4375 space_end.para = NULL;
4376 cursor.nOffset++;
4377 break;
4379 quoted = (c == '<');
4380 cursor.nOffset++;
4384 /* Find end of candidate */
4385 if (candidateStarted)
4387 while (cursor.nOffset < run_len)
4389 c = str[cursor.nOffset];
4390 if (iswspace( c ))
4392 if (quoted && c != '\r')
4394 if (!space_end.para)
4396 if (neutral_end.para)
4397 space_end = neutral_end;
4398 else
4399 space_end = cursor;
4402 else
4403 goto done;
4405 else if (isurlneutral( c ))
4407 if (quoted && c == '>')
4409 neutral_end.para = NULL;
4410 space_end.para = NULL;
4411 goto done;
4413 if (!neutral_end.para)
4414 neutral_end = cursor;
4416 else
4417 neutral_end.para = NULL;
4419 cursor.nOffset++;
4423 cursor.nOffset = 0;
4424 if (!cursor_next_run( &cursor, TRUE ))
4425 goto done;
4428 done:
4429 if (candidateStarted)
4431 if (space_end.para)
4432 *candidate_max = space_end;
4433 else if (neutral_end.para)
4434 *candidate_max = neutral_end;
4435 else
4436 *candidate_max = cursor;
4437 return TRUE;
4439 *candidate_max = *candidate_min = cursor;
4440 return FALSE;
4444 * This proc evaluates the selection and returns TRUE if it can be considered an URL
4446 static BOOL ME_IsCandidateAnURL(ME_TextEditor *editor, const ME_Cursor *start, int nChars)
4448 #define MAX_PREFIX_LEN 9
4449 #define X(str) str, ARRAY_SIZE(str) - 1
4450 struct prefix_s {
4451 const WCHAR text[MAX_PREFIX_LEN];
4452 int length;
4453 }prefixes[] = {
4454 {X(L"prospero:")},
4455 {X(L"telnet:")},
4456 {X(L"gopher:")},
4457 {X(L"mailto:")},
4458 {X(L"https:")},
4459 {X(L"file:")},
4460 {X(L"news:")},
4461 {X(L"wais:")},
4462 {X(L"nntp:")},
4463 {X(L"http:")},
4464 {X(L"www.")},
4465 {X(L"ftp:")},
4467 #undef X
4468 WCHAR bufferW[MAX_PREFIX_LEN + 1];
4469 unsigned int i;
4471 ME_GetTextW(editor, bufferW, MAX_PREFIX_LEN, start, nChars, FALSE, FALSE);
4472 for (i = 0; i < ARRAY_SIZE(prefixes); i++)
4474 if (nChars < prefixes[i].length) continue;
4475 if (!memcmp(prefixes[i].text, bufferW, prefixes[i].length * sizeof(WCHAR)))
4476 return TRUE;
4478 return FALSE;
4479 #undef MAX_PREFIX_LEN
4483 * This proc walks through the indicated selection and evaluates whether each
4484 * section identified by ME_FindNextURLCandidate and in-between sections have
4485 * their proper CFE_LINK attributes set or unset. If the CFE_LINK attribute is
4486 * not what it is supposed to be, this proc sets or unsets it as appropriate.
4488 * Since this function can cause runs to be split, do not depend on the value
4489 * of the start cursor at the end of the function.
4491 * nChars may be set to INT_MAX to update to the end of the text.
4493 * Returns TRUE if at least one section was modified.
4495 static BOOL ME_UpdateLinkAttribute(ME_TextEditor *editor, ME_Cursor *start, int nChars)
4497 BOOL modified = FALSE;
4498 ME_Cursor startCur = *start;
4500 if (!editor->AutoURLDetect_bEnable) return FALSE;
4504 CHARFORMAT2W link;
4505 ME_Cursor candidateStart, candidateEnd;
4507 if (ME_FindNextURLCandidate(editor, &startCur, nChars,
4508 &candidateStart, &candidateEnd))
4510 /* Section before candidate is not an URL */
4511 int cMin = ME_GetCursorOfs(&candidateStart);
4512 int cMax = ME_GetCursorOfs(&candidateEnd);
4514 if (!ME_IsCandidateAnURL(editor, &candidateStart, cMax - cMin))
4515 candidateStart = candidateEnd;
4516 nChars -= cMax - ME_GetCursorOfs(&startCur);
4518 else
4520 /* No more candidates until end of selection */
4521 nChars = 0;
4524 if (startCur.run != candidateStart.run ||
4525 startCur.nOffset != candidateStart.nOffset)
4527 /* CFE_LINK effect should be consistently unset */
4528 link.cbSize = sizeof(link);
4529 ME_GetCharFormat(editor, &startCur, &candidateStart, &link);
4530 if (!(link.dwMask & CFM_LINK) || (link.dwEffects & CFE_LINK))
4532 /* CFE_LINK must be unset from this range */
4533 memset(&link, 0, sizeof(CHARFORMAT2W));
4534 link.cbSize = sizeof(link);
4535 link.dwMask = CFM_LINK;
4536 link.dwEffects = 0;
4537 ME_SetCharFormat(editor, &startCur, &candidateStart, &link);
4538 /* Update candidateEnd since setting character formats may split
4539 * runs, which can cause a cursor to be at an invalid offset within
4540 * a split run. */
4541 while (candidateEnd.nOffset >= candidateEnd.run->len)
4543 candidateEnd.nOffset -= candidateEnd.run->len;
4544 candidateEnd.run = run_next_all_paras( candidateEnd.run );
4546 modified = TRUE;
4549 if (candidateStart.run != candidateEnd.run ||
4550 candidateStart.nOffset != candidateEnd.nOffset)
4552 /* CFE_LINK effect should be consistently set */
4553 link.cbSize = sizeof(link);
4554 ME_GetCharFormat(editor, &candidateStart, &candidateEnd, &link);
4555 if (!(link.dwMask & CFM_LINK) || !(link.dwEffects & CFE_LINK))
4557 /* CFE_LINK must be set on this range */
4558 memset(&link, 0, sizeof(CHARFORMAT2W));
4559 link.cbSize = sizeof(link);
4560 link.dwMask = CFM_LINK;
4561 link.dwEffects = CFE_LINK;
4562 ME_SetCharFormat(editor, &candidateStart, &candidateEnd, &link);
4563 modified = TRUE;
4566 startCur = candidateEnd;
4567 } while (nChars > 0);
4568 return modified;