Reactos
1/*
2 * RichEdit - functions dealing with editor object
3 *
4 * Copyright 2004 by Krzysztof Foltman
5 * Copyright 2005 by Cihan Altinay
6 * Copyright 2005 by Phil Krylov
7 * Copyright 2008 Eric Pouech
8 *
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.
13 *
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.
18 *
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
22 */
23
24/*
25 API implementation status:
26
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
148
149 Notifications
150
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
170
171 Styles
172
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
191*/
192
193/*
194 * RICHED20 TODO (incomplete):
195 *
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
215 *
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?
224 *
225 */
226
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#ifdef __REACTOS__
235 #include <immdev.h>
236 #include <imm32_undoc.h>
237#endif
238#include "res.h"
239
240#ifdef __REACTOS__
241#include <reactos/undocuser.h>
242#endif
243
244#define STACK_SIZE_DEFAULT 100
245#define STACK_SIZE_MAX 1000
246
247#define TEXT_LIMIT_DEFAULT 32767
248
249WINE_DEFAULT_DEBUG_CHANNEL(richedit);
250
251static BOOL ME_UpdateLinkAttribute(ME_TextEditor *editor, ME_Cursor *start, int nChars);
252
253HINSTANCE dll_instance = NULL;
254BOOL me_debug = FALSE;
255
256static ME_TextBuffer *ME_MakeText(void) {
257 ME_TextBuffer *buf = malloc(sizeof(*buf));
258 ME_DisplayItem *p1 = ME_MakeDI(diTextStart);
259 ME_DisplayItem *p2 = ME_MakeDI(diTextEnd);
260
261 p1->prev = NULL;
262 p1->next = p2;
263 p2->prev = p1;
264 p2->next = NULL;
265 p1->member.para.next_para = p2;
266 p2->member.para.prev_para = p1;
267 p2->member.para.nCharOfs = 0;
268
269 buf->pFirst = p1;
270 buf->pLast = p2;
271 buf->pCharStyle = NULL;
272
273 return buf;
274}
275
276ME_Paragraph *editor_first_para( ME_TextEditor *editor )
277{
278 return para_next( &editor->pBuffer->pFirst->member.para );
279}
280
281/* Note, returns the diTextEnd sentinel paragraph */
282ME_Paragraph *editor_end_para( ME_TextEditor *editor )
283{
284 return &editor->pBuffer->pLast->member.para;
285}
286
287static BOOL editor_beep( ME_TextEditor *editor, UINT type )
288{
289 return editor->props & TXTBIT_ALLOWBEEP && MessageBeep( type );
290}
291
292static LRESULT ME_StreamInText(ME_TextEditor *editor, DWORD dwFormat, ME_InStream *stream, ME_Style *style)
293{
294 WCHAR *pText;
295 LRESULT total_bytes_read = 0;
296 BOOL is_read = FALSE;
297 DWORD cp = CP_ACP, copy = 0;
298 char conv_buf[4 + STREAMIN_BUFFER_SIZE]; /* up to 4 additional UTF-8 bytes */
299
300 static const char bom_utf8[] = {0xEF, 0xBB, 0xBF};
301
302 TRACE("%08lx %p\n", dwFormat, stream);
303
304 do {
305 LONG nWideChars = 0;
306 WCHAR wszText[STREAMIN_BUFFER_SIZE+1];
307
308 if (!stream->dwSize)
309 {
310 ME_StreamInFill(stream);
311 if (stream->editstream->dwError)
312 break;
313 if (!stream->dwSize)
314 break;
315 total_bytes_read += stream->dwSize;
316 }
317
318 if (!(dwFormat & SF_UNICODE))
319 {
320 char * buf = stream->buffer;
321 DWORD size = stream->dwSize, end;
322
323 if (!is_read)
324 {
325 is_read = TRUE;
326 if (stream->dwSize >= 3 && !memcmp(stream->buffer, bom_utf8, 3))
327 {
328 cp = CP_UTF8;
329 buf += 3;
330 size -= 3;
331 }
332 }
333
334 if (cp == CP_UTF8)
335 {
336 if (copy)
337 {
338 memcpy(conv_buf + copy, buf, size);
339 buf = conv_buf;
340 size += copy;
341 }
342 end = size;
343 while ((buf[end-1] & 0xC0) == 0x80)
344 {
345 --end;
346 --total_bytes_read; /* strange, but seems to match windows */
347 }
348 if (buf[end-1] & 0x80)
349 {
350 DWORD need = 0;
351 if ((buf[end-1] & 0xE0) == 0xC0)
352 need = 1;
353 if ((buf[end-1] & 0xF0) == 0xE0)
354 need = 2;
355 if ((buf[end-1] & 0xF8) == 0xF0)
356 need = 3;
357
358 if (size - end >= need)
359 {
360 /* we have enough bytes for this sequence */
361 end = size;
362 }
363 else
364 {
365 /* need more bytes, so don't transcode this sequence */
366 --end;
367 }
368 }
369 }
370 else
371 end = size;
372
373 nWideChars = MultiByteToWideChar(cp, 0, buf, end, wszText, STREAMIN_BUFFER_SIZE);
374 pText = wszText;
375
376 if (cp == CP_UTF8)
377 {
378 if (end != size)
379 {
380 memcpy(conv_buf, buf + end, size - end);
381 copy = size - end;
382 }
383 }
384 }
385 else
386 {
387 nWideChars = stream->dwSize >> 1;
388 pText = (WCHAR *)stream->buffer;
389 }
390
391 ME_InsertTextFromCursor(editor, 0, pText, nWideChars, style);
392 if (stream->dwSize == 0)
393 break;
394 stream->dwSize = 0;
395 } while(1);
396 return total_bytes_read;
397}
398
399static void ME_ApplyBorderProperties(RTF_Info *info,
400 ME_BorderRect *borderRect,
401 RTFBorder *borderDef)
402{
403 int i, colorNum;
404 ME_Border *pBorders[] = {&borderRect->top,
405 &borderRect->left,
406 &borderRect->bottom,
407 &borderRect->right};
408 for (i = 0; i < 4; i++)
409 {
410 RTFColor *colorDef = info->colorList;
411 pBorders[i]->width = borderDef[i].width;
412 colorNum = borderDef[i].color;
413 while (colorDef && colorDef->rtfCNum != colorNum)
414 colorDef = colorDef->rtfNextColor;
415 if (colorDef)
416 pBorders[i]->colorRef = RGB(
417 colorDef->rtfCRed >= 0 ? colorDef->rtfCRed : 0,
418 colorDef->rtfCGreen >= 0 ? colorDef->rtfCGreen : 0,
419 colorDef->rtfCBlue >= 0 ? colorDef->rtfCBlue : 0);
420 else
421 pBorders[i]->colorRef = RGB(0, 0, 0);
422 }
423}
424
425void ME_RTFCharAttrHook(RTF_Info *info)
426{
427 CHARFORMAT2W fmt;
428 fmt.cbSize = sizeof(fmt);
429 fmt.dwMask = 0;
430 fmt.dwEffects = 0;
431
432 switch(info->rtfMinor)
433 {
434 case rtfPlain:
435 /* FIXME add more flags once they're implemented */
436 fmt.dwMask = CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE | CFM_UNDERLINETYPE | CFM_STRIKEOUT |
437 CFM_COLOR | CFM_BACKCOLOR | CFM_SIZE | CFM_WEIGHT;
438 fmt.dwEffects = CFE_AUTOCOLOR | CFE_AUTOBACKCOLOR;
439 fmt.yHeight = 12*20; /* 12pt */
440 fmt.wWeight = FW_NORMAL;
441 fmt.bUnderlineType = CFU_UNDERLINE;
442 break;
443 case rtfBold:
444 fmt.dwMask = CFM_BOLD | CFM_WEIGHT;
445 fmt.dwEffects = info->rtfParam ? CFE_BOLD : 0;
446 fmt.wWeight = info->rtfParam ? FW_BOLD : FW_NORMAL;
447 break;
448 case rtfItalic:
449 fmt.dwMask = CFM_ITALIC;
450 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
451 break;
452 case rtfUnderline:
453 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
454 fmt.bUnderlineType = CFU_UNDERLINE;
455 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
456 break;
457 case rtfDotUnderline:
458 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
459 fmt.bUnderlineType = CFU_UNDERLINEDOTTED;
460 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
461 break;
462 case rtfDbUnderline:
463 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
464 fmt.bUnderlineType = CFU_UNDERLINEDOUBLE;
465 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
466 break;
467 case rtfWordUnderline:
468 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
469 fmt.bUnderlineType = CFU_UNDERLINEWORD;
470 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
471 break;
472 case rtfNoUnderline:
473 fmt.dwMask = CFM_UNDERLINE;
474 fmt.dwEffects = 0;
475 break;
476 case rtfStrikeThru:
477 fmt.dwMask = CFM_STRIKEOUT;
478 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
479 break;
480 case rtfSubScript:
481 case rtfSuperScript:
482 case rtfSubScrShrink:
483 case rtfSuperScrShrink:
484 case rtfNoSuperSub:
485 fmt.dwMask = CFM_SUBSCRIPT|CFM_SUPERSCRIPT;
486 if (info->rtfMinor == rtfSubScrShrink) fmt.dwEffects = CFE_SUBSCRIPT;
487 if (info->rtfMinor == rtfSuperScrShrink) fmt.dwEffects = CFE_SUPERSCRIPT;
488 if (info->rtfMinor == rtfNoSuperSub) fmt.dwEffects = 0;
489 break;
490 case rtfInvisible:
491 fmt.dwMask = CFM_HIDDEN;
492 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
493 break;
494 case rtfBackColor:
495 fmt.dwMask = CFM_BACKCOLOR;
496 fmt.dwEffects = 0;
497 if (info->rtfParam == 0)
498 fmt.dwEffects = CFE_AUTOBACKCOLOR;
499 else if (info->rtfParam != rtfNoParam)
500 {
501 RTFColor *c = RTFGetColor(info, info->rtfParam);
502 if (c && c->rtfCBlue >= 0)
503 fmt.crBackColor = (c->rtfCBlue<<16)|(c->rtfCGreen<<8)|(c->rtfCRed);
504 else
505 fmt.dwEffects = CFE_AUTOBACKCOLOR;
506 }
507 break;
508 case rtfForeColor:
509 fmt.dwMask = CFM_COLOR;
510 fmt.dwEffects = 0;
511 if (info->rtfParam == 0)
512 fmt.dwEffects = CFE_AUTOCOLOR;
513 else if (info->rtfParam != rtfNoParam)
514 {
515 RTFColor *c = RTFGetColor(info, info->rtfParam);
516 if (c && c->rtfCBlue >= 0)
517 fmt.crTextColor = (c->rtfCBlue<<16)|(c->rtfCGreen<<8)|(c->rtfCRed);
518 else {
519 fmt.dwEffects = CFE_AUTOCOLOR;
520 }
521 }
522 break;
523 case rtfFontNum:
524 if (info->rtfParam != rtfNoParam)
525 {
526 RTFFont *f = RTFGetFont(info, info->rtfParam);
527 if (f)
528 {
529 MultiByteToWideChar(CP_ACP, 0, f->rtfFName, -1, fmt.szFaceName, ARRAY_SIZE(fmt.szFaceName));
530 fmt.szFaceName[ARRAY_SIZE(fmt.szFaceName)-1] = '\0';
531 fmt.bCharSet = f->rtfFCharSet;
532 fmt.dwMask = CFM_FACE | CFM_CHARSET;
533 fmt.bPitchAndFamily = f->rtfFPitch | (f->rtfFFamily << 4);
534 }
535 }
536 break;
537 case rtfFontSize:
538 fmt.dwMask = CFM_SIZE;
539 if (info->rtfParam != rtfNoParam)
540 fmt.yHeight = info->rtfParam*10;
541 break;
542 }
543 if (fmt.dwMask) {
544 ME_Style *style2;
545 RTFFlushOutputBuffer(info);
546 /* FIXME too slow ? how come ? */
547 style2 = ME_ApplyStyle(info->editor, info->style, &fmt);
548 ME_ReleaseStyle(info->style);
549 info->style = style2;
550 info->styleChanged = TRUE;
551 }
552}
553
554/* FIXME this function doesn't get any information about context of the RTF tag, which is very bad,
555 the same tags mean different things in different contexts */
556void ME_RTFParAttrHook(RTF_Info *info)
557{
558 switch(info->rtfMinor)
559 {
560 case rtfParDef: /* restores default paragraph attributes */
561 if (!info->editor->bEmulateVersion10) /* v4.1 */
562 info->borderType = RTFBorderParaLeft;
563 else /* v1.0 - 3.0 */
564 info->borderType = RTFBorderParaTop;
565 info->fmt.dwMask = PFM_ALIGNMENT | PFM_BORDER | PFM_LINESPACING | PFM_TABSTOPS |
566 PFM_OFFSET | PFM_RIGHTINDENT | PFM_SPACEAFTER | PFM_SPACEBEFORE |
567 PFM_STARTINDENT | PFM_RTLPARA | PFM_NUMBERING | PFM_NUMBERINGSTART |
568 PFM_NUMBERINGSTYLE | PFM_NUMBERINGTAB;
569 /* TODO: shading */
570 info->fmt.wAlignment = PFA_LEFT;
571 info->fmt.cTabCount = 0;
572 info->fmt.dxOffset = info->fmt.dxStartIndent = info->fmt.dxRightIndent = 0;
573 info->fmt.wBorderWidth = info->fmt.wBorders = 0;
574 info->fmt.wBorderSpace = 0;
575 info->fmt.bLineSpacingRule = 0;
576 info->fmt.dySpaceBefore = info->fmt.dySpaceAfter = 0;
577 info->fmt.dyLineSpacing = 0;
578 info->fmt.wEffects &= ~PFE_RTLPARA;
579 info->fmt.wNumbering = 0;
580 info->fmt.wNumberingStart = 0;
581 info->fmt.wNumberingStyle = 0;
582 info->fmt.wNumberingTab = 0;
583
584 if (!info->editor->bEmulateVersion10) /* v4.1 */
585 {
586 if (info->tableDef && info->tableDef->row_start &&
587 info->tableDef->row_start->nFlags & MEPF_ROWEND)
588 {
589 ME_Cursor cursor;
590 ME_Paragraph *para;
591 /* We are just after a table row. */
592 RTFFlushOutputBuffer(info);
593 cursor = info->editor->pCursors[0];
594 para = cursor.para;
595 if (para == para_next( info->tableDef->row_start )
596 && !cursor.nOffset && !cursor.run->nCharOfs)
597 {
598 /* Since the table row end, no text has been inserted, and the \intbl
599 * control word has not be used. We can confirm that we are not in a
600 * table anymore.
601 */
602 info->tableDef->row_start = NULL;
603 info->canInheritInTbl = FALSE;
604 }
605 }
606 }
607 else /* v1.0 - v3.0 */
608 {
609 info->fmt.dwMask |= PFM_TABLE;
610 info->fmt.wEffects &= ~PFE_TABLE;
611 }
612 break;
613 case rtfNestLevel:
614 if (!info->editor->bEmulateVersion10) /* v4.1 */
615 {
616 while (info->rtfParam > info->nestingLevel)
617 {
618 RTFTable *tableDef = calloc(1, sizeof(*tableDef));
619 tableDef->parent = info->tableDef;
620 info->tableDef = tableDef;
621
622 RTFFlushOutputBuffer(info);
623 if (tableDef->row_start && tableDef->row_start->nFlags & MEPF_ROWEND)
624 {
625 ME_Paragraph *para = para_next( tableDef->row_start );
626 tableDef->row_start = table_insert_row_start_at_para( info->editor, para );
627 }
628 else
629 {
630 ME_Cursor cursor;
631 cursor = info->editor->pCursors[0];
632 if (cursor.nOffset || cursor.run->nCharOfs)
633 ME_InsertTextFromCursor(info->editor, 0, L"\r", 1, info->style);
634 tableDef->row_start = table_insert_row_start( info->editor, info->editor->pCursors );
635 }
636
637 info->nestingLevel++;
638 }
639 info->canInheritInTbl = FALSE;
640 }
641 break;
642 case rtfInTable:
643 {
644 if (!info->editor->bEmulateVersion10) /* v4.1 */
645 {
646 if (info->nestingLevel < 1)
647 {
648 RTFTable *tableDef;
649 ME_Paragraph *para;
650
651 if (!info->tableDef)
652 info->tableDef = calloc(1, sizeof(*info->tableDef));
653 tableDef = info->tableDef;
654 RTFFlushOutputBuffer(info);
655 if (tableDef->row_start && tableDef->row_start->nFlags & MEPF_ROWEND)
656 para = para_next( tableDef->row_start );
657 else
658 para = info->editor->pCursors[0].para;
659
660 tableDef->row_start = table_insert_row_start_at_para( info->editor, para );
661
662 info->nestingLevel = 1;
663 info->canInheritInTbl = TRUE;
664 }
665 return;
666 } else { /* v1.0 - v3.0 */
667 info->fmt.dwMask |= PFM_TABLE;
668 info->fmt.wEffects |= PFE_TABLE;
669 }
670 break;
671 }
672 case rtfFirstIndent:
673 case rtfLeftIndent:
674 if ((info->fmt.dwMask & (PFM_STARTINDENT | PFM_OFFSET)) != (PFM_STARTINDENT | PFM_OFFSET))
675 {
676 PARAFORMAT2 fmt;
677 fmt.cbSize = sizeof(fmt);
678 editor_get_selection_para_fmt( info->editor, &fmt );
679 info->fmt.dwMask |= PFM_STARTINDENT | PFM_OFFSET;
680 info->fmt.dxStartIndent = fmt.dxStartIndent;
681 info->fmt.dxOffset = fmt.dxOffset;
682 }
683 if (info->rtfMinor == rtfFirstIndent)
684 {
685 info->fmt.dxStartIndent += info->fmt.dxOffset + info->rtfParam;
686 info->fmt.dxOffset = -info->rtfParam;
687 }
688 else
689 info->fmt.dxStartIndent = info->rtfParam - info->fmt.dxOffset;
690 break;
691 case rtfRightIndent:
692 info->fmt.dwMask |= PFM_RIGHTINDENT;
693 info->fmt.dxRightIndent = info->rtfParam;
694 break;
695 case rtfQuadLeft:
696 case rtfQuadJust:
697 info->fmt.dwMask |= PFM_ALIGNMENT;
698 info->fmt.wAlignment = PFA_LEFT;
699 break;
700 case rtfQuadRight:
701 info->fmt.dwMask |= PFM_ALIGNMENT;
702 info->fmt.wAlignment = PFA_RIGHT;
703 break;
704 case rtfQuadCenter:
705 info->fmt.dwMask |= PFM_ALIGNMENT;
706 info->fmt.wAlignment = PFA_CENTER;
707 break;
708 case rtfTabPos:
709 if (!(info->fmt.dwMask & PFM_TABSTOPS))
710 {
711 PARAFORMAT2 fmt;
712 fmt.cbSize = sizeof(fmt);
713 editor_get_selection_para_fmt( info->editor, &fmt );
714 memcpy(info->fmt.rgxTabs, fmt.rgxTabs,
715 fmt.cTabCount * sizeof(fmt.rgxTabs[0]));
716 info->fmt.cTabCount = fmt.cTabCount;
717 info->fmt.dwMask |= PFM_TABSTOPS;
718 }
719 if (info->fmt.cTabCount < MAX_TAB_STOPS && info->rtfParam < 0x1000000)
720 info->fmt.rgxTabs[info->fmt.cTabCount++] = info->rtfParam;
721 break;
722 case rtfKeep:
723 info->fmt.dwMask |= PFM_KEEP;
724 info->fmt.wEffects |= PFE_KEEP;
725 break;
726 case rtfNoWidowControl:
727 info->fmt.dwMask |= PFM_NOWIDOWCONTROL;
728 info->fmt.wEffects |= PFE_NOWIDOWCONTROL;
729 break;
730 case rtfKeepNext:
731 info->fmt.dwMask |= PFM_KEEPNEXT;
732 info->fmt.wEffects |= PFE_KEEPNEXT;
733 break;
734 case rtfSpaceAfter:
735 info->fmt.dwMask |= PFM_SPACEAFTER;
736 info->fmt.dySpaceAfter = info->rtfParam;
737 break;
738 case rtfSpaceBefore:
739 info->fmt.dwMask |= PFM_SPACEBEFORE;
740 info->fmt.dySpaceBefore = info->rtfParam;
741 break;
742 case rtfSpaceBetween:
743 info->fmt.dwMask |= PFM_LINESPACING;
744 if ((int)info->rtfParam > 0)
745 {
746 info->fmt.dyLineSpacing = info->rtfParam;
747 info->fmt.bLineSpacingRule = 3;
748 }
749 else
750 {
751 info->fmt.dyLineSpacing = info->rtfParam;
752 info->fmt.bLineSpacingRule = 4;
753 }
754 break;
755 case rtfSpaceMultiply:
756 info->fmt.dwMask |= PFM_LINESPACING;
757 info->fmt.dyLineSpacing = info->rtfParam * 20;
758 info->fmt.bLineSpacingRule = 5;
759 break;
760 case rtfParBullet:
761 info->fmt.dwMask |= PFM_NUMBERING;
762 info->fmt.wNumbering = PFN_BULLET;
763 break;
764 case rtfParSimple:
765 info->fmt.dwMask |= PFM_NUMBERING;
766 info->fmt.wNumbering = 2; /* FIXME: MSDN says it's not used ?? */
767 break;
768 case rtfBorderLeft:
769 info->borderType = RTFBorderParaLeft;
770 info->fmt.wBorders |= 1;
771 info->fmt.dwMask |= PFM_BORDER;
772 break;
773 case rtfBorderRight:
774 info->borderType = RTFBorderParaRight;
775 info->fmt.wBorders |= 2;
776 info->fmt.dwMask |= PFM_BORDER;
777 break;
778 case rtfBorderTop:
779 info->borderType = RTFBorderParaTop;
780 info->fmt.wBorders |= 4;
781 info->fmt.dwMask |= PFM_BORDER;
782 break;
783 case rtfBorderBottom:
784 info->borderType = RTFBorderParaBottom;
785 info->fmt.wBorders |= 8;
786 info->fmt.dwMask |= PFM_BORDER;
787 break;
788 case rtfBorderSingle:
789 info->fmt.wBorders &= ~0x700;
790 info->fmt.wBorders |= 1 << 8;
791 info->fmt.dwMask |= PFM_BORDER;
792 break;
793 case rtfBorderThick:
794 info->fmt.wBorders &= ~0x700;
795 info->fmt.wBorders |= 2 << 8;
796 info->fmt.dwMask |= PFM_BORDER;
797 break;
798 case rtfBorderShadow:
799 info->fmt.wBorders &= ~0x700;
800 info->fmt.wBorders |= 10 << 8;
801 info->fmt.dwMask |= PFM_BORDER;
802 break;
803 case rtfBorderDouble:
804 info->fmt.wBorders &= ~0x700;
805 info->fmt.wBorders |= 7 << 8;
806 info->fmt.dwMask |= PFM_BORDER;
807 break;
808 case rtfBorderDot:
809 info->fmt.wBorders &= ~0x700;
810 info->fmt.wBorders |= 11 << 8;
811 info->fmt.dwMask |= PFM_BORDER;
812 break;
813 case rtfBorderWidth:
814 {
815 int borderSide = info->borderType & RTFBorderSideMask;
816 RTFTable *tableDef = info->tableDef;
817 if ((info->borderType & RTFBorderTypeMask) == RTFBorderTypeCell)
818 {
819 RTFBorder *border;
820 if (!tableDef || tableDef->numCellsDefined >= MAX_TABLE_CELLS)
821 break;
822 border = &tableDef->cells[tableDef->numCellsDefined].border[borderSide];
823 border->width = info->rtfParam;
824 break;
825 }
826 info->fmt.wBorderWidth = info->rtfParam;
827 info->fmt.dwMask |= PFM_BORDER;
828 break;
829 }
830 case rtfBorderSpace:
831 info->fmt.wBorderSpace = info->rtfParam;
832 info->fmt.dwMask |= PFM_BORDER;
833 break;
834 case rtfBorderColor:
835 {
836 RTFTable *tableDef = info->tableDef;
837 int borderSide = info->borderType & RTFBorderSideMask;
838 int borderType = info->borderType & RTFBorderTypeMask;
839 switch(borderType)
840 {
841 case RTFBorderTypePara:
842 if (!info->editor->bEmulateVersion10) /* v4.1 */
843 break;
844 /* v1.0 - 3.0 treat paragraph and row borders the same. */
845 case RTFBorderTypeRow:
846 if (tableDef) {
847 tableDef->border[borderSide].color = info->rtfParam;
848 }
849 break;
850 case RTFBorderTypeCell:
851 if (tableDef && tableDef->numCellsDefined < MAX_TABLE_CELLS) {
852 tableDef->cells[tableDef->numCellsDefined].border[borderSide].color = info->rtfParam;
853 }
854 break;
855 }
856 break;
857 }
858 case rtfRTLPar:
859 info->fmt.dwMask |= PFM_RTLPARA;
860 info->fmt.wEffects |= PFE_RTLPARA;
861 break;
862 case rtfLTRPar:
863 info->fmt.dwMask |= PFM_RTLPARA;
864 info->fmt.wEffects &= ~PFE_RTLPARA;
865 break;
866 }
867}
868
869void ME_RTFTblAttrHook(RTF_Info *info)
870{
871 switch (info->rtfMinor)
872 {
873 case rtfRowDef:
874 {
875 if (!info->editor->bEmulateVersion10) /* v4.1 */
876 info->borderType = 0; /* Not sure */
877 else /* v1.0 - 3.0 */
878 info->borderType = RTFBorderRowTop;
879 if (!info->tableDef) {
880 info->tableDef = ME_MakeTableDef(info->editor);
881 } else {
882 ME_InitTableDef(info->editor, info->tableDef);
883 }
884 break;
885 }
886 case rtfCellPos:
887 {
888 int cellNum;
889 if (!info->tableDef)
890 {
891 info->tableDef = ME_MakeTableDef(info->editor);
892 }
893 cellNum = info->tableDef->numCellsDefined;
894 if (cellNum >= MAX_TABLE_CELLS)
895 break;
896 info->tableDef->cells[cellNum].rightBoundary = info->rtfParam;
897 if (cellNum < MAX_TAB_STOPS)
898 {
899 /* Tab stops were used to store cell positions before v4.1 but v4.1
900 * still seems to set the tabstops without using them. */
901 PARAFORMAT2 *fmt = &info->editor->pCursors[0].para->fmt;
902 fmt->rgxTabs[cellNum] &= ~0x00FFFFFF;
903 fmt->rgxTabs[cellNum] |= 0x00FFFFFF & info->rtfParam;
904 }
905 info->tableDef->numCellsDefined++;
906 break;
907 }
908 case rtfRowBordTop:
909 info->borderType = RTFBorderRowTop;
910 break;
911 case rtfRowBordLeft:
912 info->borderType = RTFBorderRowLeft;
913 break;
914 case rtfRowBordBottom:
915 info->borderType = RTFBorderRowBottom;
916 break;
917 case rtfRowBordRight:
918 info->borderType = RTFBorderRowRight;
919 break;
920 case rtfCellBordTop:
921 info->borderType = RTFBorderCellTop;
922 break;
923 case rtfCellBordLeft:
924 info->borderType = RTFBorderCellLeft;
925 break;
926 case rtfCellBordBottom:
927 info->borderType = RTFBorderCellBottom;
928 break;
929 case rtfCellBordRight:
930 info->borderType = RTFBorderCellRight;
931 break;
932 case rtfRowGapH:
933 if (info->tableDef)
934 info->tableDef->gapH = info->rtfParam;
935 break;
936 case rtfRowLeftEdge:
937 if (info->tableDef)
938 info->tableDef->leftEdge = info->rtfParam;
939 break;
940 }
941}
942
943void ME_RTFSpecialCharHook(RTF_Info *info)
944{
945 RTFTable *tableDef = info->tableDef;
946 switch (info->rtfMinor)
947 {
948 case rtfNestCell:
949 if (info->editor->bEmulateVersion10) /* v1.0 - v3.0 */
950 break;
951 /* else fall through since v4.1 treats rtfNestCell and rtfCell the same */
952 case rtfCell:
953 if (!tableDef)
954 break;
955 RTFFlushOutputBuffer(info);
956 if (!info->editor->bEmulateVersion10) /* v4.1 */
957 {
958 if (tableDef->row_start)
959 {
960 if (!info->nestingLevel && tableDef->row_start->nFlags & MEPF_ROWEND)
961 {
962 ME_Paragraph *para = para_next( tableDef->row_start );
963 tableDef->row_start = table_insert_row_start_at_para( info->editor, para );
964 info->nestingLevel = 1;
965 }
966 table_insert_cell( info->editor, info->editor->pCursors );
967 }
968 }
969 else /* v1.0 - v3.0 */
970 {
971 ME_Paragraph *para = info->editor->pCursors[0].para;
972
973 if (para_in_table( para ) && tableDef->numCellsInserted < tableDef->numCellsDefined)
974 {
975 WCHAR tab = '\t';
976 ME_InsertTextFromCursor(info->editor, 0, &tab, 1, info->style);
977 tableDef->numCellsInserted++;
978 }
979 }
980 break;
981 case rtfNestRow:
982 if (info->editor->bEmulateVersion10) /* v1.0 - v3.0 */
983 break;
984 /* else fall through since v4.1 treats rtfNestRow and rtfRow the same */
985 case rtfRow:
986 {
987 ME_Run *run;
988 ME_Paragraph *para;
989 ME_Cell *cell;
990 int i;
991
992 if (!tableDef)
993 break;
994 RTFFlushOutputBuffer(info);
995 if (!info->editor->bEmulateVersion10) /* v4.1 */
996 {
997 if (!tableDef->row_start) break;
998 if (!info->nestingLevel && tableDef->row_start->nFlags & MEPF_ROWEND)
999 {
1000 para = para_next( tableDef->row_start );
1001 tableDef->row_start = table_insert_row_start_at_para( info->editor, para );
1002 info->nestingLevel++;
1003 }
1004 para = tableDef->row_start;
1005 cell = table_row_first_cell( para );
1006 assert( cell && !cell_prev( cell ) );
1007 if (tableDef->numCellsDefined < 1)
1008 {
1009 /* 2000 twips appears to be the cell size that native richedit uses
1010 * when no cell sizes are specified. */
1011 const int default_size = 2000;
1012 int right_boundary = default_size;
1013 cell->nRightBoundary = right_boundary;
1014 while (cell_next( cell ))
1015 {
1016 cell = cell_next( cell );
1017 right_boundary += default_size;
1018 cell->nRightBoundary = right_boundary;
1019 }
1020 para = table_insert_cell( info->editor, info->editor->pCursors );
1021 cell = para_cell( para );
1022 cell->nRightBoundary = right_boundary;
1023 }
1024 else
1025 {
1026 for (i = 0; i < tableDef->numCellsDefined; i++)
1027 {
1028 RTFCell *cellDef = &tableDef->cells[i];
1029 cell->nRightBoundary = cellDef->rightBoundary;
1030 ME_ApplyBorderProperties( info, &cell->border, cellDef->border );
1031 cell = cell_next( cell );
1032 if (!cell)
1033 {
1034 para = table_insert_cell( info->editor, info->editor->pCursors );
1035 cell = para_cell( para );
1036 }
1037 }
1038 /* Cell for table row delimiter is empty */
1039 cell->nRightBoundary = tableDef->cells[i - 1].rightBoundary;
1040 }
1041
1042 run = para_first_run( cell_first_para( cell ) );
1043 if (info->editor->pCursors[0].run != run || info->editor->pCursors[0].nOffset)
1044 {
1045 int nOfs, nChars;
1046 /* Delete inserted cells that aren't defined. */
1047 info->editor->pCursors[1].run = run;
1048 info->editor->pCursors[1].para = run->para;
1049 info->editor->pCursors[1].nOffset = 0;
1050 nOfs = ME_GetCursorOfs(&info->editor->pCursors[1]);
1051 nChars = ME_GetCursorOfs(&info->editor->pCursors[0]) - nOfs;
1052 ME_InternalDeleteText(info->editor, &info->editor->pCursors[1],
1053 nChars, TRUE);
1054 }
1055
1056 para = table_insert_row_end( info->editor, info->editor->pCursors );
1057 para->fmt.dxOffset = abs(info->tableDef->gapH);
1058 para->fmt.dxStartIndent = info->tableDef->leftEdge;
1059 ME_ApplyBorderProperties( info, ¶->border, tableDef->border );
1060 info->nestingLevel--;
1061 if (!info->nestingLevel)
1062 {
1063 if (info->canInheritInTbl) tableDef->row_start = para;
1064 else
1065 {
1066 while (info->tableDef)
1067 {
1068 tableDef = info->tableDef;
1069 info->tableDef = tableDef->parent;
1070 free(tableDef);
1071 }
1072 }
1073 }
1074 else
1075 {
1076 info->tableDef = tableDef->parent;
1077 free(tableDef);
1078 }
1079 }
1080 else /* v1.0 - v3.0 */
1081 {
1082 para = info->editor->pCursors[0].para;
1083 para->fmt.dxOffset = info->tableDef->gapH;
1084 para->fmt.dxStartIndent = info->tableDef->leftEdge;
1085
1086 ME_ApplyBorderProperties( info, ¶->border, tableDef->border );
1087 while (tableDef->numCellsInserted < tableDef->numCellsDefined)
1088 {
1089 WCHAR tab = '\t';
1090 ME_InsertTextFromCursor(info->editor, 0, &tab, 1, info->style);
1091 tableDef->numCellsInserted++;
1092 }
1093 para->fmt.cTabCount = min(tableDef->numCellsDefined, MAX_TAB_STOPS);
1094 if (!tableDef->numCellsDefined) para->fmt.wEffects &= ~PFE_TABLE;
1095 ME_InsertTextFromCursor(info->editor, 0, L"\r", 1, info->style);
1096 tableDef->numCellsInserted = 0;
1097 }
1098 break;
1099 }
1100 case rtfTab:
1101 case rtfPar:
1102 if (info->editor->bEmulateVersion10) /* v1.0 - 3.0 */
1103 {
1104 ME_Paragraph *para;
1105
1106 RTFFlushOutputBuffer(info);
1107 para = info->editor->pCursors[0].para;
1108 if (para_in_table( para ))
1109 {
1110 /* rtfPar is treated like a space within a table. */
1111 info->rtfClass = rtfText;
1112 info->rtfMajor = ' ';
1113 }
1114 else if (info->rtfMinor == rtfPar && tableDef)
1115 tableDef->numCellsInserted = 0;
1116 }
1117 break;
1118 }
1119}
1120
1121static HRESULT insert_static_object(ME_TextEditor *editor, HENHMETAFILE hemf, HBITMAP hbmp,
1122 const SIZEL* sz)
1123{
1124 LPOLEOBJECT lpObject = NULL;
1125 LPSTORAGE lpStorage = NULL;
1126 LPOLECLIENTSITE lpClientSite = NULL;
1127 LPDATAOBJECT lpDataObject = NULL;
1128 LPOLECACHE lpOleCache = NULL;
1129 STGMEDIUM stgm;
1130 FORMATETC fm;
1131 CLSID clsid;
1132 HRESULT hr = E_FAIL;
1133 DWORD conn;
1134
1135 if (hemf)
1136 {
1137 stgm.tymed = TYMED_ENHMF;
1138 stgm.hEnhMetaFile = hemf;
1139 fm.cfFormat = CF_ENHMETAFILE;
1140 }
1141 else if (hbmp)
1142 {
1143 stgm.tymed = TYMED_GDI;
1144 stgm.hBitmap = hbmp;
1145 fm.cfFormat = CF_BITMAP;
1146 }
1147 else return E_FAIL;
1148
1149 stgm.pUnkForRelease = NULL;
1150
1151 fm.ptd = NULL;
1152 fm.dwAspect = DVASPECT_CONTENT;
1153 fm.lindex = -1;
1154 fm.tymed = stgm.tymed;
1155
1156 if (OleCreateDefaultHandler(&CLSID_NULL, NULL, &IID_IOleObject, (void**)&lpObject) == S_OK &&
1157 IRichEditOle_GetClientSite(editor->richole, &lpClientSite) == S_OK &&
1158 IOleObject_SetClientSite(lpObject, lpClientSite) == S_OK &&
1159 IOleObject_GetUserClassID(lpObject, &clsid) == S_OK &&
1160 IOleObject_QueryInterface(lpObject, &IID_IOleCache, (void**)&lpOleCache) == S_OK &&
1161 IOleCache_Cache(lpOleCache, &fm, 0, &conn) == S_OK &&
1162 IOleObject_QueryInterface(lpObject, &IID_IDataObject, (void**)&lpDataObject) == S_OK &&
1163 IDataObject_SetData(lpDataObject, &fm, &stgm, TRUE) == S_OK)
1164 {
1165 REOBJECT reobject;
1166
1167 reobject.cbStruct = sizeof(reobject);
1168 reobject.cp = REO_CP_SELECTION;
1169 reobject.clsid = clsid;
1170 reobject.poleobj = lpObject;
1171 reobject.pstg = lpStorage;
1172 reobject.polesite = lpClientSite;
1173 /* convert from twips to .01 mm */
1174 reobject.sizel.cx = MulDiv(sz->cx, 254, 144);
1175 reobject.sizel.cy = MulDiv(sz->cy, 254, 144);
1176 reobject.dvaspect = DVASPECT_CONTENT;
1177 reobject.dwFlags = 0; /* FIXME */
1178 reobject.dwUser = 0;
1179
1180 hr = editor_insert_oleobj(editor, &reobject);
1181 }
1182
1183 if (lpObject) IOleObject_Release(lpObject);
1184 if (lpClientSite) IOleClientSite_Release(lpClientSite);
1185 if (lpStorage) IStorage_Release(lpStorage);
1186 if (lpDataObject) IDataObject_Release(lpDataObject);
1187 if (lpOleCache) IOleCache_Release(lpOleCache);
1188
1189 return hr;
1190}
1191
1192static void ME_RTFReadShpPictGroup( RTF_Info *info )
1193{
1194 int level = 1;
1195
1196 for (;;)
1197 {
1198 RTFGetToken (info);
1199
1200 if (info->rtfClass == rtfEOF) return;
1201 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1202 {
1203 if (--level == 0) break;
1204 }
1205 else if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1206 {
1207 level++;
1208 }
1209 else
1210 {
1211 RTFRouteToken( info );
1212 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1213 level--;
1214 }
1215 }
1216
1217 RTFRouteToken( info ); /* feed "}" back to router */
1218 return;
1219}
1220
1221static DWORD read_hex_data( RTF_Info *info, BYTE **out )
1222{
1223 DWORD read = 0, size = 1024;
1224 BYTE *buf, val;
1225 BOOL flip;
1226
1227 *out = NULL;
1228
1229 if (info->rtfClass != rtfText)
1230 {
1231 ERR("Called with incorrect token\n");
1232 return 0;
1233 }
1234
1235 buf = malloc(size);
1236 if (!buf) return 0;
1237
1238 val = info->rtfMajor;
1239 for (flip = TRUE;; flip = !flip)
1240 {
1241 RTFGetToken( info );
1242 if (info->rtfClass == rtfEOF)
1243 {
1244 free(buf);
1245 return 0;
1246 }
1247 if (info->rtfClass != rtfText) break;
1248 if (flip)
1249 {
1250 if (read >= size)
1251 {
1252 size *= 2;
1253 buf = realloc(buf, size);
1254 if (!buf) return 0;
1255 }
1256 buf[read++] = RTFCharToHex(val) * 16 + RTFCharToHex(info->rtfMajor);
1257 }
1258 else
1259 val = info->rtfMajor;
1260 }
1261 if (flip) FIXME("wrong hex string\n");
1262
1263 *out = buf;
1264 return read;
1265}
1266
1267static void ME_RTFReadPictGroup(RTF_Info *info)
1268{
1269 SIZEL sz;
1270 BYTE *buffer = NULL;
1271 DWORD size = 0;
1272 METAFILEPICT mfp;
1273 HENHMETAFILE hemf;
1274 HBITMAP hbmp;
1275 enum gfxkind {gfx_unknown = 0, gfx_enhmetafile, gfx_metafile, gfx_dib} gfx = gfx_unknown;
1276 int level = 1;
1277
1278 mfp.mm = MM_TEXT;
1279 sz.cx = sz.cy = 0;
1280
1281 for (;;)
1282 {
1283 RTFGetToken( info );
1284
1285 if (info->rtfClass == rtfText)
1286 {
1287 if (level == 1)
1288 {
1289 if (!buffer)
1290 size = read_hex_data( info, &buffer );
1291 }
1292 else
1293 {
1294 RTFSkipGroup( info );
1295 }
1296 } /* We potentially have a new token so fall through. */
1297
1298 if (info->rtfClass == rtfEOF) return;
1299
1300 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1301 {
1302 if (--level == 0) break;
1303 continue;
1304 }
1305 if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1306 {
1307 level++;
1308 continue;
1309 }
1310 if (!RTFCheckCM( info, rtfControl, rtfPictAttr ))
1311 {
1312 RTFRouteToken( info );
1313 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1314 level--;
1315 continue;
1316 }
1317
1318 if (RTFCheckMM( info, rtfPictAttr, rtfWinMetafile ))
1319 {
1320 mfp.mm = info->rtfParam;
1321 gfx = gfx_metafile;
1322 }
1323 else if (RTFCheckMM( info, rtfPictAttr, rtfDevIndBitmap ))
1324 {
1325 if (info->rtfParam != 0) FIXME("dibitmap should be 0 (%d)\n", info->rtfParam);
1326 gfx = gfx_dib;
1327 }
1328 else if (RTFCheckMM( info, rtfPictAttr, rtfEmfBlip ))
1329 gfx = gfx_enhmetafile;
1330 else if (RTFCheckMM( info, rtfPictAttr, rtfPicWid ))
1331 mfp.xExt = info->rtfParam;
1332 else if (RTFCheckMM( info, rtfPictAttr, rtfPicHt ))
1333 mfp.yExt = info->rtfParam;
1334 else if (RTFCheckMM( info, rtfPictAttr, rtfPicGoalWid ))
1335 sz.cx = info->rtfParam;
1336 else if (RTFCheckMM( info, rtfPictAttr, rtfPicGoalHt ))
1337 sz.cy = info->rtfParam;
1338 else
1339 FIXME("Non supported attribute: %d %d %d\n", info->rtfClass, info->rtfMajor, info->rtfMinor);
1340 }
1341
1342 if (buffer)
1343 {
1344 switch (gfx)
1345 {
1346 case gfx_enhmetafile:
1347 if ((hemf = SetEnhMetaFileBits( size, buffer )))
1348 insert_static_object( info->editor, hemf, NULL, &sz );
1349 break;
1350 case gfx_metafile:
1351 if ((hemf = SetWinMetaFileBits( size, buffer, NULL, &mfp )))
1352 insert_static_object( info->editor, hemf, NULL, &sz );
1353 break;
1354 case gfx_dib:
1355 {
1356 BITMAPINFO *bi = (BITMAPINFO*)buffer;
1357 HDC hdc = GetDC(0);
1358 unsigned nc = bi->bmiHeader.biClrUsed;
1359
1360 /* not quite right, especially for bitfields type of compression */
1361 if (!nc && bi->bmiHeader.biBitCount <= 8)
1362 nc = 1 << bi->bmiHeader.biBitCount;
1363 if ((hbmp = CreateDIBitmap( hdc, &bi->bmiHeader,
1364 CBM_INIT, (char*)(bi + 1) + nc * sizeof(RGBQUAD),
1365 bi, DIB_RGB_COLORS)) )
1366 insert_static_object( info->editor, NULL, hbmp, &sz );
1367 ReleaseDC( 0, hdc );
1368 break;
1369 }
1370 default:
1371 break;
1372 }
1373 }
1374 free( buffer );
1375 RTFRouteToken( info ); /* feed "}" back to router */
1376 return;
1377}
1378
1379/* for now, lookup the \result part and use it, whatever the object */
1380static void ME_RTFReadObjectGroup(RTF_Info *info)
1381{
1382 for (;;)
1383 {
1384 RTFGetToken (info);
1385 if (info->rtfClass == rtfEOF)
1386 return;
1387 if (RTFCheckCM(info, rtfGroup, rtfEndGroup))
1388 break;
1389 if (RTFCheckCM(info, rtfGroup, rtfBeginGroup))
1390 {
1391 RTFGetToken (info);
1392 if (info->rtfClass == rtfEOF)
1393 return;
1394 if (RTFCheckCMM(info, rtfControl, rtfDestination, rtfObjResult))
1395 {
1396 int level = 1;
1397
1398 while (RTFGetToken (info) != rtfEOF)
1399 {
1400 if (info->rtfClass == rtfGroup)
1401 {
1402 if (info->rtfMajor == rtfBeginGroup) level++;
1403 else if (info->rtfMajor == rtfEndGroup && --level < 0) break;
1404 }
1405 RTFRouteToken(info);
1406 }
1407 }
1408 else RTFSkipGroup(info);
1409 continue;
1410 }
1411 if (!RTFCheckCM (info, rtfControl, rtfObjAttr))
1412 {
1413 FIXME("Non supported attribute: %d %d %d\n", info->rtfClass, info->rtfMajor, info->rtfMinor);
1414 return;
1415 }
1416 }
1417 RTFRouteToken(info); /* feed "}" back to router */
1418}
1419
1420static void ME_RTFReadParnumGroup( RTF_Info *info )
1421{
1422 int level = 1, type = -1;
1423 WORD indent = 0, start = 1;
1424 WCHAR txt_before = 0, txt_after = 0;
1425
1426 for (;;)
1427 {
1428 RTFGetToken( info );
1429
1430 if (RTFCheckCMM( info, rtfControl, rtfDestination, rtfParNumTextBefore ) ||
1431 RTFCheckCMM( info, rtfControl, rtfDestination, rtfParNumTextAfter ))
1432 {
1433 int loc = info->rtfMinor;
1434
1435 RTFGetToken( info );
1436 if (info->rtfClass == rtfText)
1437 {
1438 if (loc == rtfParNumTextBefore)
1439 txt_before = info->rtfMajor;
1440 else
1441 txt_after = info->rtfMajor;
1442 continue;
1443 }
1444 /* falling through to catch EOFs and group level changes */
1445 }
1446
1447 if (info->rtfClass == rtfEOF)
1448 return;
1449
1450 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1451 {
1452 if (--level == 0) break;
1453 continue;
1454 }
1455
1456 if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1457 {
1458 level++;
1459 continue;
1460 }
1461
1462 /* Ignore non para-attr */
1463 if (!RTFCheckCM( info, rtfControl, rtfParAttr ))
1464 continue;
1465
1466 switch (info->rtfMinor)
1467 {
1468 case rtfParLevel: /* Para level is ignored */
1469 case rtfParSimple:
1470 break;
1471 case rtfParBullet:
1472 type = PFN_BULLET;
1473 break;
1474
1475 case rtfParNumDecimal:
1476 type = PFN_ARABIC;
1477 break;
1478 case rtfParNumULetter:
1479 type = PFN_UCLETTER;
1480 break;
1481 case rtfParNumURoman:
1482 type = PFN_UCROMAN;
1483 break;
1484 case rtfParNumLLetter:
1485 type = PFN_LCLETTER;
1486 break;
1487 case rtfParNumLRoman:
1488 type = PFN_LCROMAN;
1489 break;
1490
1491 case rtfParNumIndent:
1492 indent = info->rtfParam;
1493 break;
1494 case rtfParNumStartAt:
1495 start = info->rtfParam;
1496 break;
1497 }
1498 }
1499
1500 if (type != -1)
1501 {
1502 info->fmt.dwMask |= (PFM_NUMBERING | PFM_NUMBERINGSTART | PFM_NUMBERINGSTYLE | PFM_NUMBERINGTAB);
1503 info->fmt.wNumbering = type;
1504 info->fmt.wNumberingStart = start;
1505 info->fmt.wNumberingStyle = PFNS_PAREN;
1506 if (type != PFN_BULLET)
1507 {
1508 if (txt_before == 0 && txt_after == 0)
1509 info->fmt.wNumberingStyle = PFNS_PLAIN;
1510 else if (txt_after == '.')
1511 info->fmt.wNumberingStyle = PFNS_PERIOD;
1512 else if (txt_before == '(' && txt_after == ')')
1513 info->fmt.wNumberingStyle = PFNS_PARENS;
1514 }
1515 info->fmt.wNumberingTab = indent;
1516 }
1517
1518 TRACE("type %d indent %d start %d txt before %04x txt after %04x\n",
1519 type, indent, start, txt_before, txt_after);
1520
1521 RTFRouteToken( info ); /* feed "}" back to router */
1522}
1523
1524static void ME_RTFReadHook(RTF_Info *info)
1525{
1526 switch(info->rtfClass)
1527 {
1528 case rtfGroup:
1529 switch(info->rtfMajor)
1530 {
1531 case rtfBeginGroup:
1532 if (info->stackTop < maxStack) {
1533 info->stack[info->stackTop].style = info->style;
1534 ME_AddRefStyle(info->style);
1535 info->stack[info->stackTop].codePage = info->codePage;
1536 info->stack[info->stackTop].unicodeLength = info->unicodeLength;
1537 }
1538 info->stackTop++;
1539 info->styleChanged = FALSE;
1540 break;
1541 case rtfEndGroup:
1542 {
1543 RTFFlushOutputBuffer(info);
1544 info->stackTop--;
1545 if (info->stackTop <= 0)
1546 info->rtfClass = rtfEOF;
1547 if (info->stackTop < 0)
1548 return;
1549
1550 ME_ReleaseStyle(info->style);
1551 info->style = info->stack[info->stackTop].style;
1552 info->codePage = info->stack[info->stackTop].codePage;
1553 info->unicodeLength = info->stack[info->stackTop].unicodeLength;
1554 break;
1555 }
1556 }
1557 break;
1558 }
1559}
1560
1561void
1562ME_StreamInFill(ME_InStream *stream)
1563{
1564 stream->editstream->dwError = stream->editstream->pfnCallback(stream->editstream->dwCookie,
1565 (BYTE *)stream->buffer,
1566 sizeof(stream->buffer),
1567 (LONG *)&stream->dwSize);
1568 stream->dwUsed = 0;
1569}
1570
1571static LRESULT ME_StreamIn(ME_TextEditor *editor, DWORD format, EDITSTREAM *stream, BOOL stripLastCR)
1572{
1573 RTF_Info parser;
1574 ME_Style *style;
1575 LONG from, to;
1576 int nUndoMode;
1577 int nEventMask = editor->nEventMask;
1578 ME_InStream inStream;
1579 BOOL invalidRTF = FALSE;
1580 ME_Cursor *selStart, *selEnd;
1581 LRESULT num_read = 0; /* bytes read for SF_TEXT, non-control chars inserted for SF_RTF */
1582
1583 TRACE("stream==%p editor==%p format==0x%lX\n", stream, editor, format);
1584 editor->nEventMask = 0;
1585
1586 ME_GetSelectionOfs(editor, &from, &to);
1587 if (format & SFF_SELECTION && editor->mode & TM_RICHTEXT)
1588 {
1589 ME_GetSelection(editor, &selStart, &selEnd);
1590 style = ME_GetSelectionInsertStyle(editor);
1591
1592 ME_InternalDeleteText(editor, selStart, to - from, FALSE);
1593
1594 /* Don't insert text at the end of the table row */
1595 if (!editor->bEmulateVersion10) /* v4.1 */
1596 {
1597 ME_Paragraph *para = editor->pCursors->para;
1598 if (para->nFlags & (MEPF_ROWSTART | MEPF_ROWEND))
1599 {
1600 para = para_next( para );
1601 editor->pCursors[0].para = para;
1602 editor->pCursors[0].run = para_first_run( para );
1603 editor->pCursors[0].nOffset = 0;
1604 }
1605 editor->pCursors[1] = editor->pCursors[0];
1606 }
1607 else /* v1.0 - 3.0 */
1608 {
1609 if (editor->pCursors[0].run->nFlags & MERF_ENDPARA &&
1610 para_in_table( editor->pCursors[0].para ))
1611 return 0;
1612 }
1613 }
1614 else
1615 {
1616 style = editor->pBuffer->pDefaultStyle;
1617 ME_AddRefStyle(style);
1618 if (format & SFF_SELECTION)
1619 {
1620 ME_GetSelection(editor, &selStart, &selEnd);
1621 ME_InternalDeleteText(editor, selStart, to - from, FALSE);
1622 }
1623 else
1624 {
1625 set_selection_cursors(editor, 0, 0);
1626 ME_InternalDeleteText(editor, &editor->pCursors[1],
1627 ME_GetTextLength(editor), FALSE);
1628 }
1629 from = to = 0;
1630 ME_ClearTempStyle(editor);
1631 editor_set_default_para_fmt( editor, &editor->pCursors[0].para->fmt );
1632 }
1633
1634
1635 /* Back up undo mode to a local variable */
1636 nUndoMode = editor->nUndoMode;
1637
1638 /* Only create an undo if SFF_SELECTION is set */
1639 if (!(format & SFF_SELECTION))
1640 editor->nUndoMode = umIgnore;
1641
1642 inStream.editstream = stream;
1643 inStream.editstream->dwError = 0;
1644 inStream.dwSize = 0;
1645 inStream.dwUsed = 0;
1646
1647 if (format & SF_RTF)
1648 {
1649 /* Check if it's really RTF, and if it is not, use plain text */
1650 ME_StreamInFill(&inStream);
1651 if (!inStream.editstream->dwError)
1652 {
1653 if ((!editor->bEmulateVersion10 && strncmp(inStream.buffer, "{\\rtf", 5) && strncmp(inStream.buffer, "{\\urtf", 6))
1654 || (editor->bEmulateVersion10 && *inStream.buffer != '{'))
1655 {
1656 invalidRTF = TRUE;
1657 inStream.editstream->dwError = -16;
1658 }
1659 }
1660 }
1661
1662 if (!invalidRTF && !inStream.editstream->dwError)
1663 {
1664 ME_Cursor start;
1665 from = ME_GetCursorOfs(&editor->pCursors[0]);
1666 if (format & SF_RTF) {
1667
1668 /* setup the RTF parser */
1669 memset(&parser, 0, sizeof parser);
1670 RTFSetEditStream(&parser, &inStream);
1671 parser.rtfFormat = format&(SF_TEXT|SF_RTF);
1672 parser.editor = editor;
1673 parser.style = style;
1674 WriterInit(&parser);
1675 RTFInit(&parser);
1676 RTFSetReadHook(&parser, ME_RTFReadHook);
1677 RTFSetDestinationCallback(&parser, rtfShpPict, ME_RTFReadShpPictGroup);
1678 RTFSetDestinationCallback(&parser, rtfPict, ME_RTFReadPictGroup);
1679 RTFSetDestinationCallback(&parser, rtfObject, ME_RTFReadObjectGroup);
1680 RTFSetDestinationCallback(&parser, rtfParNumbering, ME_RTFReadParnumGroup);
1681 if (!parser.editor->bEmulateVersion10) /* v4.1 */
1682 {
1683 RTFSetDestinationCallback(&parser, rtfNoNestTables, RTFSkipGroup);
1684 RTFSetDestinationCallback(&parser, rtfNestTableProps, RTFReadGroup);
1685 }
1686 BeginFile(&parser);
1687
1688 /* do the parsing */
1689 RTFRead(&parser);
1690 RTFFlushOutputBuffer(&parser);
1691 if (!editor->bEmulateVersion10) /* v4.1 */
1692 {
1693 if (parser.tableDef && parser.tableDef->row_start &&
1694 (parser.nestingLevel > 0 || parser.canInheritInTbl))
1695 {
1696 /* Delete any incomplete table row at the end of the rich text. */
1697 int nOfs, nChars;
1698 ME_Paragraph *para;
1699
1700 parser.rtfMinor = rtfRow;
1701 /* Complete the table row before deleting it.
1702 * By doing it this way we will have the current paragraph format set
1703 * properly to reflect that is not in the complete table, and undo items
1704 * will be added for this change to the current paragraph format. */
1705 if (parser.nestingLevel > 0)
1706 {
1707 while (parser.nestingLevel > 1)
1708 ME_RTFSpecialCharHook(&parser); /* Decrements nestingLevel */
1709 para = parser.tableDef->row_start;
1710 ME_RTFSpecialCharHook(&parser);
1711 }
1712 else
1713 {
1714 para = parser.tableDef->row_start;
1715 ME_RTFSpecialCharHook(&parser);
1716 assert( para->nFlags & MEPF_ROWEND );
1717 para = para_next( para );
1718 }
1719
1720 editor->pCursors[1].para = para;
1721 editor->pCursors[1].run = para_first_run( para );
1722 editor->pCursors[1].nOffset = 0;
1723 nOfs = ME_GetCursorOfs(&editor->pCursors[1]);
1724 nChars = ME_GetCursorOfs(&editor->pCursors[0]) - nOfs;
1725 ME_InternalDeleteText(editor, &editor->pCursors[1], nChars, TRUE);
1726 if (parser.tableDef) parser.tableDef->row_start = NULL;
1727 }
1728 }
1729 RTFDestroy(&parser);
1730
1731 if (parser.stackTop > 0)
1732 {
1733 while (--parser.stackTop >= 0)
1734 {
1735 ME_ReleaseStyle(parser.style);
1736 parser.style = parser.stack[parser.stackTop].style;
1737 }
1738 if (!inStream.editstream->dwError)
1739 inStream.editstream->dwError = HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
1740 }
1741
1742 /* Remove last line break, as mandated by tests. This is not affected by
1743 CR/LF counters, since RTF streaming presents only \para tokens, which
1744 are converted according to the standard rules: \r for 2.0, \r\n for 1.0
1745 */
1746 if (stripLastCR && !(format & SFF_SELECTION)) {
1747 int newto;
1748 ME_GetSelection(editor, &selStart, &selEnd);
1749 newto = ME_GetCursorOfs(selEnd);
1750 if (newto > to + (editor->bEmulateVersion10 ? 1 : 0)) {
1751 WCHAR lastchar[3] = {'\0', '\0'};
1752 int linebreakSize = editor->bEmulateVersion10 ? 2 : 1;
1753 ME_Cursor linebreakCursor = *selEnd, lastcharCursor = *selEnd;
1754 CHARFORMAT2W cf;
1755
1756 /* Set the final eop to the char fmt of the last char */
1757 cf.cbSize = sizeof(cf);
1758 cf.dwMask = CFM_ALL2;
1759 ME_MoveCursorChars(editor, &lastcharCursor, -1, FALSE);
1760 ME_GetCharFormat(editor, &lastcharCursor, &linebreakCursor, &cf);
1761 set_selection_cursors(editor, newto, -1);
1762 ME_SetSelectionCharFormat(editor, &cf);
1763 set_selection_cursors(editor, newto, newto);
1764
1765 ME_MoveCursorChars(editor, &linebreakCursor, -linebreakSize, FALSE);
1766 ME_GetTextW(editor, lastchar, 2, &linebreakCursor, linebreakSize, FALSE, FALSE);
1767 if (lastchar[0] == '\r' && (lastchar[1] == '\n' || lastchar[1] == '\0')) {
1768 ME_InternalDeleteText(editor, &linebreakCursor, linebreakSize, FALSE);
1769 }
1770 }
1771 }
1772 to = ME_GetCursorOfs(&editor->pCursors[0]);
1773 num_read = to - from;
1774
1775 style = parser.style;
1776 }
1777 else if (format & SF_TEXT)
1778 {
1779 num_read = ME_StreamInText(editor, format, &inStream, style);
1780 to = ME_GetCursorOfs(&editor->pCursors[0]);
1781 }
1782 else
1783 ERR("EM_STREAMIN without SF_TEXT or SF_RTF\n");
1784 /* put the cursor at the top */
1785 if (!(format & SFF_SELECTION))
1786 set_selection_cursors(editor, 0, 0);
1787 cursor_from_char_ofs( editor, from, &start );
1788 ME_UpdateLinkAttribute(editor, &start, to - from);
1789 }
1790
1791 /* Restore saved undo mode */
1792 editor->nUndoMode = nUndoMode;
1793
1794 /* even if we didn't add an undo, we need to commit anything on the stack */
1795 ME_CommitUndo(editor);
1796
1797 /* If SFF_SELECTION isn't set, delete any undos from before we started too */
1798 if (!(format & SFF_SELECTION))
1799 ME_EmptyUndoStack(editor);
1800
1801 ME_ReleaseStyle(style);
1802 editor->nEventMask = nEventMask;
1803 ME_UpdateRepaint(editor, FALSE);
1804 if (!(format & SFF_SELECTION)) {
1805 ME_ClearTempStyle(editor);
1806 }
1807 ME_SendSelChange(editor);
1808 ME_SendRequestResize(editor, FALSE);
1809
1810 return num_read;
1811}
1812
1813
1814typedef struct tagME_RTFStringStreamStruct
1815{
1816 char *string;
1817 int pos;
1818 int length;
1819} ME_RTFStringStreamStruct;
1820
1821static DWORD CALLBACK ME_ReadFromRTFString(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
1822{
1823 ME_RTFStringStreamStruct *pStruct = (ME_RTFStringStreamStruct *)dwCookie;
1824 int count;
1825
1826 count = min(cb, pStruct->length - pStruct->pos);
1827 memmove(lpBuff, pStruct->string + pStruct->pos, count);
1828 pStruct->pos += count;
1829 *pcb = count;
1830 return 0;
1831}
1832
1833static void
1834ME_StreamInRTFString(ME_TextEditor *editor, BOOL selection, char *string)
1835{
1836 EDITSTREAM es;
1837 ME_RTFStringStreamStruct data;
1838
1839 data.string = string;
1840 data.length = strlen(string);
1841 data.pos = 0;
1842 es.dwCookie = (DWORD_PTR)&data;
1843 es.pfnCallback = ME_ReadFromRTFString;
1844 ME_StreamIn(editor, SF_RTF | (selection ? SFF_SELECTION : 0), &es, TRUE);
1845}
1846
1847
1848static int
1849ME_FindText(ME_TextEditor *editor, DWORD flags, const CHARRANGE *chrg, const WCHAR *text, CHARRANGE *chrgText)
1850{
1851 const int nLen = lstrlenW(text);
1852 const int nTextLen = ME_GetTextLength(editor);
1853 int nMin, nMax;
1854 ME_Cursor cursor;
1855 WCHAR wLastChar = ' ';
1856
1857 TRACE("flags==0x%08lx, chrg->cpMin==%ld, chrg->cpMax==%ld text==%s\n",
1858 flags, chrg->cpMin, chrg->cpMax, debugstr_w(text));
1859
1860 if (flags & ~(FR_DOWN | FR_MATCHCASE | FR_WHOLEWORD))
1861 FIXME("Flags 0x%08lx not implemented\n",
1862 flags & ~(FR_DOWN | FR_MATCHCASE | FR_WHOLEWORD));
1863
1864 nMin = chrg->cpMin;
1865 if (chrg->cpMax == -1)
1866 nMax = nTextLen;
1867 else
1868 nMax = chrg->cpMax > nTextLen ? nTextLen : chrg->cpMax;
1869
1870 /* In 1.0 emulation, if cpMax reaches end of text, add the FR_DOWN flag */
1871 if (editor->bEmulateVersion10 && nMax == nTextLen)
1872 {
1873 flags |= FR_DOWN;
1874 }
1875
1876 /* In 1.0 emulation, cpMin must always be no greater than cpMax */
1877 if (editor->bEmulateVersion10 && nMax < nMin)
1878 {
1879 if (chrgText)
1880 {
1881 chrgText->cpMin = -1;
1882 chrgText->cpMax = -1;
1883 }
1884 return -1;
1885 }
1886
1887 /* when searching up, if cpMin < cpMax, then instead of searching
1888 * on [cpMin,cpMax], we search on [0,cpMin], otherwise, search on
1889 * [cpMax, cpMin]. The exception is when cpMax is -1, in which
1890 * case, it is always bigger than cpMin.
1891 */
1892 if (!editor->bEmulateVersion10 && !(flags & FR_DOWN))
1893 {
1894 int nSwap = nMax;
1895
1896 nMax = nMin > nTextLen ? nTextLen : nMin;
1897 if (nMin < nSwap || chrg->cpMax == -1)
1898 nMin = 0;
1899 else
1900 nMin = nSwap;
1901 }
1902
1903 if (!nLen || nMin < 0 || nMax < 0 || nMax < nMin)
1904 {
1905 if (chrgText)
1906 chrgText->cpMin = chrgText->cpMax = -1;
1907 return -1;
1908 }
1909
1910 if (flags & FR_DOWN) /* Forward search */
1911 {
1912 /* If possible, find the character before where the search starts */
1913 if ((flags & FR_WHOLEWORD) && nMin)
1914 {
1915 cursor_from_char_ofs( editor, nMin - 1, &cursor );
1916 wLastChar = *get_text( cursor.run, cursor.nOffset );
1917 ME_MoveCursorChars(editor, &cursor, 1, FALSE);
1918 }
1919 else cursor_from_char_ofs( editor, nMin, &cursor );
1920
1921 while (cursor.run && ME_GetCursorOfs(&cursor) + nLen <= nMax)
1922 {
1923 ME_Run *run = cursor.run;
1924 int nCurStart = cursor.nOffset;
1925 int nMatched = 0;
1926
1927 while (run && ME_CharCompare( *get_text( run, nCurStart + nMatched ), text[nMatched], (flags & FR_MATCHCASE)))
1928 {
1929 if ((flags & FR_WHOLEWORD) && iswalnum(wLastChar))
1930 break;
1931
1932 nMatched++;
1933 if (nMatched == nLen)
1934 {
1935 ME_Run *next_run = run;
1936 int nNextStart = nCurStart;
1937 WCHAR wNextChar;
1938
1939 /* Check to see if next character is a whitespace */
1940 if (flags & FR_WHOLEWORD)
1941 {
1942 if (nCurStart + nMatched == run->len)
1943 {
1944 next_run = run_next_all_paras( run );
1945 nNextStart = -nMatched;
1946 }
1947
1948 if (next_run)
1949 wNextChar = *get_text( next_run, nNextStart + nMatched );
1950 else
1951 wNextChar = ' ';
1952
1953 if (iswalnum(wNextChar))
1954 break;
1955 }
1956
1957 cursor.nOffset += cursor.para->nCharOfs + cursor.run->nCharOfs;
1958 if (chrgText)
1959 {
1960 chrgText->cpMin = cursor.nOffset;
1961 chrgText->cpMax = cursor.nOffset + nLen;
1962 }
1963 TRACE("found at %d-%d\n", cursor.nOffset, cursor.nOffset + nLen);
1964 return cursor.nOffset;
1965 }
1966 if (nCurStart + nMatched == run->len)
1967 {
1968 run = run_next_all_paras( run );
1969 nCurStart = -nMatched;
1970 }
1971 }
1972 if (run)
1973 wLastChar = *get_text( run, nCurStart + nMatched );
1974 else
1975 wLastChar = ' ';
1976
1977 cursor.nOffset++;
1978 if (cursor.nOffset == cursor.run->len)
1979 {
1980 if (run_next_all_paras( cursor.run ))
1981 {
1982 cursor.run = run_next_all_paras( cursor.run );
1983 cursor.para = cursor.run->para;
1984 cursor.nOffset = 0;
1985 }
1986 else
1987 cursor.run = NULL;
1988 }
1989 }
1990 }
1991 else /* Backward search */
1992 {
1993 /* If possible, find the character after where the search ends */
1994 if ((flags & FR_WHOLEWORD) && nMax < nTextLen - 1)
1995 {
1996 cursor_from_char_ofs( editor, nMax + 1, &cursor );
1997 wLastChar = *get_text( cursor.run, cursor.nOffset );
1998 ME_MoveCursorChars(editor, &cursor, -1, FALSE);
1999 }
2000 else cursor_from_char_ofs( editor, nMax, &cursor );
2001
2002 while (cursor.run && ME_GetCursorOfs(&cursor) - nLen >= nMin)
2003 {
2004 ME_Run *run = cursor.run;
2005 ME_Paragraph *para = cursor.para;
2006 int nCurEnd = cursor.nOffset;
2007 int nMatched = 0;
2008
2009 if (nCurEnd == 0 && run_prev_all_paras( run ))
2010 {
2011 run = run_prev_all_paras( run );
2012 para = run->para;
2013 nCurEnd = run->len;
2014 }
2015
2016 while (run && ME_CharCompare( *get_text( run, nCurEnd - nMatched - 1 ),
2017 text[nLen - nMatched - 1], (flags & FR_MATCHCASE) ))
2018 {
2019 if ((flags & FR_WHOLEWORD) && iswalnum(wLastChar))
2020 break;
2021
2022 nMatched++;
2023 if (nMatched == nLen)
2024 {
2025 ME_Run *prev_run = run;
2026 int nPrevEnd = nCurEnd;
2027 WCHAR wPrevChar;
2028 int nStart;
2029
2030 /* Check to see if previous character is a whitespace */
2031 if (flags & FR_WHOLEWORD)
2032 {
2033 if (nPrevEnd - nMatched == 0)
2034 {
2035 prev_run = run_prev_all_paras( run );
2036 if (prev_run) nPrevEnd = prev_run->len + nMatched;
2037 }
2038
2039 if (prev_run) wPrevChar = *get_text( prev_run, nPrevEnd - nMatched - 1 );
2040 else wPrevChar = ' ';
2041
2042 if (iswalnum(wPrevChar))
2043 break;
2044 }
2045
2046 nStart = para->nCharOfs + run->nCharOfs + nCurEnd - nMatched;
2047 if (chrgText)
2048 {
2049 chrgText->cpMin = nStart;
2050 chrgText->cpMax = nStart + nLen;
2051 }
2052 TRACE("found at %d-%d\n", nStart, nStart + nLen);
2053 return nStart;
2054 }
2055 if (nCurEnd - nMatched == 0)
2056 {
2057 if (run_prev_all_paras( run ))
2058 {
2059 run = run_prev_all_paras( run );
2060 para = run->para;
2061 }
2062 /* Don't care about pCurItem becoming NULL here; it's already taken
2063 * care of in the exterior loop condition */
2064 nCurEnd = run->len + nMatched;
2065 }
2066 }
2067 if (run)
2068 wLastChar = *get_text( run, nCurEnd - nMatched - 1 );
2069 else
2070 wLastChar = ' ';
2071
2072 cursor.nOffset--;
2073 if (cursor.nOffset < 0)
2074 {
2075 if (run_prev_all_paras( cursor.run ) )
2076 {
2077 cursor.run = run_prev_all_paras( cursor.run );
2078 cursor.para = cursor.run->para;
2079 cursor.nOffset = cursor.run->len;
2080 }
2081 else
2082 cursor.run = NULL;
2083 }
2084 }
2085 }
2086 TRACE("not found\n");
2087 if (chrgText)
2088 chrgText->cpMin = chrgText->cpMax = -1;
2089 return -1;
2090}
2091
2092static int ME_GetTextEx(ME_TextEditor *editor, GETTEXTEX *ex, LPARAM pText)
2093{
2094 int nChars;
2095 ME_Cursor start;
2096
2097 if (!ex->cb || !pText) return 0;
2098
2099 if (ex->flags & ~(GT_SELECTION | GT_USECRLF))
2100 FIXME("GETTEXTEX flags 0x%08lx not supported\n", ex->flags & ~(GT_SELECTION | GT_USECRLF));
2101
2102 if (ex->flags & GT_SELECTION)
2103 {
2104 LONG from, to;
2105 int nStartCur = ME_GetSelectionOfs(editor, &from, &to);
2106 start = editor->pCursors[nStartCur];
2107 nChars = to - from;
2108 }
2109 else
2110 {
2111 ME_SetCursorToStart(editor, &start);
2112 nChars = INT_MAX;
2113 }
2114 if (ex->codepage == CP_UNICODE)
2115 {
2116 return ME_GetTextW(editor, (LPWSTR)pText, ex->cb / sizeof(WCHAR) - 1,
2117 &start, nChars, ex->flags & GT_USECRLF, FALSE);
2118 }
2119 else
2120 {
2121 /* potentially each char may be a CR, why calculate the exact value with O(N) when
2122 we can just take a bigger buffer? :)
2123 The above assumption still holds with CR/LF counters, since CR->CRLF expansion
2124 occurs only in richedit 2.0 mode, in which line breaks have only one CR
2125 */
2126 int crlfmul = (ex->flags & GT_USECRLF) ? 2 : 1;
2127 DWORD buflen;
2128 LPWSTR buffer;
2129 LRESULT rc;
2130
2131 buflen = min(crlfmul * nChars, ex->cb - 1);
2132 buffer = malloc((buflen + 1) * sizeof(WCHAR));
2133
2134 nChars = ME_GetTextW(editor, buffer, buflen, &start, nChars, ex->flags & GT_USECRLF, FALSE);
2135 rc = WideCharToMultiByte(ex->codepage, 0, buffer, nChars + 1,
2136 (LPSTR)pText, ex->cb, ex->lpDefaultChar, ex->lpUsedDefChar);
2137 if (rc) rc--; /* do not count 0 terminator */
2138
2139 free(buffer);
2140 return rc;
2141 }
2142}
2143
2144static int get_text_range( ME_TextEditor *editor, WCHAR *buffer,
2145 const ME_Cursor *start, int len )
2146{
2147 if (!buffer) return 0;
2148 return ME_GetTextW( editor, buffer, INT_MAX, start, len, FALSE, FALSE );
2149}
2150
2151int set_selection( ME_TextEditor *editor, int to, int from )
2152{
2153 int end;
2154
2155 TRACE("%d - %d\n", to, from );
2156
2157 if (!editor->bHideSelection) ME_InvalidateSelection( editor );
2158 end = set_selection_cursors( editor, to, from );
2159 editor_ensure_visible( editor, &editor->pCursors[0] );
2160 if (!editor->bHideSelection) ME_InvalidateSelection( editor );
2161 update_caret( editor );
2162 ME_Repaint( editor );
2163 ME_SendSelChange( editor );
2164
2165 return end;
2166}
2167
2168typedef struct tagME_GlobalDestStruct
2169{
2170 HGLOBAL hData;
2171 int nLength;
2172} ME_GlobalDestStruct;
2173
2174static DWORD CALLBACK ME_ReadFromHGLOBALUnicode(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
2175{
2176 ME_GlobalDestStruct *pData = (ME_GlobalDestStruct *)dwCookie;
2177 int i;
2178 WORD *pSrc, *pDest;
2179
2180 cb = cb >> 1;
2181 pDest = (WORD *)lpBuff;
2182 pSrc = GlobalLock(pData->hData);
2183 for (i = 0; i<cb && pSrc[pData->nLength+i]; i++) {
2184 pDest[i] = pSrc[pData->nLength+i];
2185 }
2186 pData->nLength += i;
2187 *pcb = 2*i;
2188 GlobalUnlock(pData->hData);
2189 return 0;
2190}
2191
2192static DWORD CALLBACK ME_ReadFromHGLOBALRTF(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
2193{
2194 ME_GlobalDestStruct *pData = (ME_GlobalDestStruct *)dwCookie;
2195 int i;
2196 BYTE *pSrc, *pDest;
2197
2198 pDest = lpBuff;
2199 pSrc = GlobalLock(pData->hData);
2200 for (i = 0; i<cb && pSrc[pData->nLength+i]; i++) {
2201 pDest[i] = pSrc[pData->nLength+i];
2202 }
2203 pData->nLength += i;
2204 *pcb = i;
2205 GlobalUnlock(pData->hData);
2206 return 0;
2207}
2208
2209static HRESULT paste_rtf(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2210{
2211 EDITSTREAM es;
2212 ME_GlobalDestStruct gds;
2213 HRESULT hr;
2214
2215 gds.hData = med->hGlobal;
2216 gds.nLength = 0;
2217 es.dwCookie = (DWORD_PTR)&gds;
2218 es.pfnCallback = ME_ReadFromHGLOBALRTF;
2219 hr = ME_StreamIn( editor, SF_RTF | SFF_SELECTION, &es, FALSE ) == 0 ? E_FAIL : S_OK;
2220 ReleaseStgMedium( med );
2221 return hr;
2222}
2223
2224static HRESULT paste_text(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2225{
2226 EDITSTREAM es;
2227 ME_GlobalDestStruct gds;
2228 HRESULT hr;
2229
2230 gds.hData = med->hGlobal;
2231 gds.nLength = 0;
2232 es.dwCookie = (DWORD_PTR)&gds;
2233 es.pfnCallback = ME_ReadFromHGLOBALUnicode;
2234 hr = ME_StreamIn( editor, SF_TEXT | SF_UNICODE | SFF_SELECTION, &es, FALSE ) == 0 ? E_FAIL : S_OK;
2235 ReleaseStgMedium( med );
2236 return hr;
2237}
2238
2239static HRESULT paste_emf(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2240{
2241 HRESULT hr;
2242 SIZEL sz = {0, 0};
2243
2244 hr = insert_static_object( editor, med->hEnhMetaFile, NULL, &sz );
2245 if (SUCCEEDED(hr))
2246 {
2247 ME_CommitUndo( editor );
2248 ME_UpdateRepaint( editor, FALSE );
2249 }
2250 else
2251 ReleaseStgMedium( med );
2252
2253 return hr;
2254}
2255
2256static struct paste_format
2257{
2258 FORMATETC fmt;
2259 HRESULT (*paste)(ME_TextEditor *, FORMATETC *, STGMEDIUM *);
2260 const WCHAR *name;
2261} paste_formats[] =
2262{
2263 {{ -1, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, paste_rtf, L"Rich Text Format" },
2264 {{ CF_UNICODETEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, paste_text },
2265 {{ CF_ENHMETAFILE, NULL, DVASPECT_CONTENT, -1, TYMED_ENHMF }, paste_emf },
2266 {{ 0 }}
2267};
2268
2269static void init_paste_formats(void)
2270{
2271 struct paste_format *format;
2272 static int done;
2273
2274 if (!done)
2275 {
2276 for (format = paste_formats; format->fmt.cfFormat; format++)
2277 {
2278 if (format->name)
2279 format->fmt.cfFormat = RegisterClipboardFormatW( format->name );
2280 }
2281 done = 1;
2282 }
2283}
2284
2285static BOOL paste_special(ME_TextEditor *editor, UINT cf, REPASTESPECIAL *ps, BOOL check_only)
2286{
2287 HRESULT hr;
2288 STGMEDIUM med;
2289 struct paste_format *format;
2290 IDataObject *data;
2291
2292 /* Protect read-only edit control from modification */
2293 if (editor->props & TXTBIT_READONLY)
2294 {
2295 if (!check_only) editor_beep( editor, MB_ICONERROR );
2296 return FALSE;
2297 }
2298
2299 init_paste_formats();
2300
2301 if (ps && ps->dwAspect != DVASPECT_CONTENT)
2302 FIXME("Ignoring aspect %lx\n", ps->dwAspect);
2303
2304 hr = OleGetClipboard( &data );
2305 if (hr != S_OK) return FALSE;
2306
2307 if (cf == CF_TEXT) cf = CF_UNICODETEXT;
2308
2309 hr = S_FALSE;
2310 for (format = paste_formats; format->fmt.cfFormat; format++)
2311 {
2312 if (cf && cf != format->fmt.cfFormat) continue;
2313 hr = IDataObject_QueryGetData( data, &format->fmt );
2314 if (hr == S_OK)
2315 {
2316 if (!check_only)
2317 {
2318 hr = IDataObject_GetData( data, &format->fmt, &med );
2319 if (hr != S_OK) goto done;
2320 hr = format->paste( editor, &format->fmt, &med );
2321 }
2322 break;
2323 }
2324 }
2325
2326done:
2327 IDataObject_Release( data );
2328
2329 return hr == S_OK;
2330}
2331
2332static HRESULT editor_copy( ME_TextEditor *editor, ME_Cursor *start, int chars, IDataObject **data_out )
2333{
2334 IDataObject *data = NULL;
2335 HRESULT hr = S_OK;
2336
2337 if (editor->lpOleCallback)
2338 {
2339 CHARRANGE range;
2340 range.cpMin = ME_GetCursorOfs( start );
2341 range.cpMax = range.cpMin + chars;
2342 hr = IRichEditOleCallback_GetClipboardData( editor->lpOleCallback, &range, RECO_COPY, &data );
2343 }
2344
2345 if (FAILED( hr ) || !data)
2346 hr = ME_GetDataObject( editor, start, chars, &data );
2347
2348 if (SUCCEEDED( hr ))
2349 {
2350 if (data_out)
2351 *data_out = data;
2352 else
2353 {
2354 hr = OleSetClipboard( data );
2355 IDataObject_Release( data );
2356 }
2357 }
2358
2359 return hr;
2360}
2361
2362HRESULT editor_copy_or_cut( ME_TextEditor *editor, BOOL cut, ME_Cursor *start, int count,
2363 IDataObject **data_out )
2364{
2365 HRESULT hr;
2366
2367 if (cut && (editor->props & TXTBIT_READONLY))
2368 {
2369 return E_ACCESSDENIED;
2370 }
2371
2372 hr = editor_copy( editor, start, count, data_out );
2373 if (SUCCEEDED(hr) && cut)
2374 {
2375 ME_InternalDeleteText( editor, start, count, FALSE );
2376 ME_CommitUndo( editor );
2377 ME_UpdateRepaint( editor, TRUE );
2378 }
2379 return hr;
2380}
2381
2382static BOOL copy_or_cut( ME_TextEditor *editor, BOOL cut )
2383{
2384 HRESULT hr;
2385 LONG offs, count;
2386 int start_cursor = ME_GetSelectionOfs( editor, &offs, &count );
2387 ME_Cursor *sel_start = &editor->pCursors[start_cursor];
2388
2389 if (editor->password_char) return FALSE;
2390
2391 count -= offs;
2392 hr = editor_copy_or_cut( editor, cut, sel_start, count, NULL );
2393 if (FAILED( hr )) editor_beep( editor, MB_ICONERROR );
2394
2395 return SUCCEEDED( hr );
2396}
2397
2398static void ME_UpdateSelectionLinkAttribute(ME_TextEditor *editor)
2399{
2400 ME_Paragraph *start_para, *end_para;
2401 ME_Cursor *from, *to, start;
2402 int num_chars;
2403
2404 if (!editor->AutoURLDetect_bEnable) return;
2405
2406 ME_GetSelection(editor, &from, &to);
2407
2408 /* Find paragraph previous to the one that contains start cursor */
2409 start_para = from->para;
2410 if (para_prev( start_para )) start_para = para_prev( start_para );
2411
2412 /* Find paragraph that contains end cursor */
2413 end_para = para_next( to->para );
2414
2415 start.para = start_para;
2416 start.run = para_first_run( start_para );
2417 start.nOffset = 0;
2418 num_chars = end_para->nCharOfs - start_para->nCharOfs;
2419
2420 ME_UpdateLinkAttribute( editor, &start, num_chars );
2421}
2422
2423static BOOL handle_enter(ME_TextEditor *editor)
2424{
2425 BOOL shift_is_down = GetKeyState(VK_SHIFT) & 0x8000;
2426
2427 if (editor->props & TXTBIT_MULTILINE)
2428 {
2429 ME_Cursor cursor = editor->pCursors[0];
2430 ME_Paragraph *para = cursor.para;
2431 LONG from, to;
2432 ME_Style *style, *eop_style;
2433
2434 if (editor->props & TXTBIT_READONLY)
2435 {
2436 editor_beep( editor, MB_ICONERROR );
2437 return TRUE;
2438 }
2439
2440 ME_GetSelectionOfs(editor, &from, &to);
2441 if (editor->nTextLimit > ME_GetTextLength(editor) - (to-from))
2442 {
2443 if (!editor->bEmulateVersion10) /* v4.1 */
2444 {
2445 if (para->nFlags & MEPF_ROWEND)
2446 {
2447 /* Add a new table row after this row. */
2448 para = table_append_row( editor, para );
2449 para = para_next( para );
2450 editor->pCursors[0].para = para;
2451 editor->pCursors[0].run = para_first_run( para );
2452 editor->pCursors[0].nOffset = 0;
2453 editor->pCursors[1] = editor->pCursors[0];
2454 ME_CommitUndo(editor);
2455 ME_UpdateRepaint(editor, FALSE);
2456 return TRUE;
2457 }
2458 else if (para == editor->pCursors[1].para &&
2459 cursor.nOffset + cursor.run->nCharOfs == 0 &&
2460 para_prev( para ) && para_prev( para )->nFlags & MEPF_ROWSTART &&
2461 !para_prev( para )->nCharOfs)
2462 {
2463 /* Insert a newline before the table. */
2464 para = para_prev( para );
2465 para->nFlags &= ~MEPF_ROWSTART;
2466 editor->pCursors[0].para = para;
2467 editor->pCursors[0].run = para_first_run( para );
2468 editor->pCursors[1] = editor->pCursors[0];
2469 ME_InsertTextFromCursor( editor, 0, L"\r", 1, editor->pCursors[0].run->style );
2470 para = editor_first_para( editor );
2471 editor_set_default_para_fmt( editor, ¶->fmt );
2472 para->nFlags = 0;
2473 para_mark_rewrap( editor, para );
2474 editor->pCursors[0].para = para;
2475 editor->pCursors[0].run = para_first_run( para );
2476 editor->pCursors[1] = editor->pCursors[0];
2477 para_next( para )->nFlags |= MEPF_ROWSTART;
2478 ME_CommitCoalescingUndo(editor);
2479 ME_UpdateRepaint(editor, FALSE);
2480 return TRUE;
2481 }
2482 }
2483 else /* v1.0 - 3.0 */
2484 {
2485 ME_Paragraph *para = cursor.para;
2486 if (para_in_table( para ))
2487 {
2488 if (cursor.run->nFlags & MERF_ENDPARA)
2489 {
2490 if (from == to)
2491 {
2492 ME_ContinueCoalescingTransaction(editor);
2493 para = table_append_row( editor, para );
2494 editor->pCursors[0].para = para;
2495 editor->pCursors[0].run = para_first_run( para );
2496 editor->pCursors[0].nOffset = 0;
2497 editor->pCursors[1] = editor->pCursors[0];
2498 ME_CommitCoalescingUndo(editor);
2499 ME_UpdateRepaint(editor, FALSE);
2500 return TRUE;
2501 }
2502 }
2503 else
2504 {
2505 ME_ContinueCoalescingTransaction(editor);
2506 if (cursor.run->nCharOfs + cursor.nOffset == 0 &&
2507 para_prev( para ) && !para_in_table( para_prev( para ) ))
2508 {
2509 /* Insert newline before table */
2510 cursor.run = para_end_run( para_prev( para ) );
2511 if (cursor.run)
2512 {
2513 editor->pCursors[0].run = cursor.run;
2514 editor->pCursors[0].para = para_prev( para );
2515 }
2516 editor->pCursors[0].nOffset = 0;
2517 editor->pCursors[1] = editor->pCursors[0];
2518 ME_InsertTextFromCursor( editor, 0, L"\r", 1, editor->pCursors[0].run->style );
2519 }
2520 else
2521 {
2522 editor->pCursors[1] = editor->pCursors[0];
2523 para = table_append_row( editor, para );
2524 editor->pCursors[0].para = para;
2525 editor->pCursors[0].run = para_first_run( para );
2526 editor->pCursors[0].nOffset = 0;
2527 editor->pCursors[1] = editor->pCursors[0];
2528 }
2529 ME_CommitCoalescingUndo(editor);
2530 ME_UpdateRepaint(editor, FALSE);
2531 return TRUE;
2532 }
2533 }
2534 }
2535
2536 style = style_get_insert_style( editor, editor->pCursors );
2537
2538 /* Normally the new eop style is the insert style, however in a list it is copied from the existing
2539 eop style (this prevents the list label style changing when the new eop is inserted).
2540 No extra ref is taken here on eop_style. */
2541 if (para->fmt.wNumbering)
2542 eop_style = para->eop_run->style;
2543 else
2544 eop_style = style;
2545 ME_ContinueCoalescingTransaction(editor);
2546 if (shift_is_down)
2547 ME_InsertEndRowFromCursor(editor, 0);
2548 else
2549 if (!editor->bEmulateVersion10)
2550 ME_InsertTextFromCursor(editor, 0, L"\r", 1, eop_style);
2551 else
2552 ME_InsertTextFromCursor(editor, 0, L"\r\n", 2, eop_style);
2553 ME_CommitCoalescingUndo(editor);
2554 SetCursor(NULL);
2555
2556 ME_UpdateSelectionLinkAttribute(editor);
2557 ME_UpdateRepaint(editor, FALSE);
2558 ME_SaveTempStyle(editor, style); /* set the temp insert style for the new para */
2559 ME_ReleaseStyle(style);
2560 }
2561 return TRUE;
2562 }
2563 return FALSE;
2564}
2565
2566static BOOL
2567ME_KeyDown(ME_TextEditor *editor, WORD nKey)
2568{
2569 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2570 BOOL shift_is_down = GetKeyState(VK_SHIFT) & 0x8000;
2571
2572 if (editor->bMouseCaptured)
2573 return FALSE;
2574 if (nKey != VK_SHIFT && nKey != VK_CONTROL && nKey != VK_MENU)
2575 editor->nSelectionType = stPosition;
2576
2577 switch (nKey)
2578 {
2579 case VK_LEFT:
2580 case VK_RIGHT:
2581 case VK_HOME:
2582 case VK_END:
2583 editor->nUDArrowX = -1;
2584 /* fall through */
2585 case VK_UP:
2586 case VK_DOWN:
2587 case VK_PRIOR:
2588 case VK_NEXT:
2589 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
2590 ME_ArrowKey(editor, nKey, shift_is_down, ctrl_is_down);
2591 return TRUE;
2592 case VK_BACK:
2593 case VK_DELETE:
2594 editor->nUDArrowX = -1;
2595 /* FIXME backspace and delete aren't the same, they act different wrt paragraph style of the merged paragraph */
2596 if (editor->props & TXTBIT_READONLY)
2597 return FALSE;
2598 if (ME_IsSelection(editor))
2599 {
2600 ME_DeleteSelection(editor);
2601 ME_CommitUndo(editor);
2602 }
2603 else if (nKey == VK_DELETE)
2604 {
2605 /* Delete stops group typing.
2606 * (See MSDN remarks on EM_STOPGROUPTYPING message) */
2607 ME_DeleteTextAtCursor(editor, 1, 1);
2608 ME_CommitUndo(editor);
2609 }
2610 else if (ME_ArrowKey(editor, VK_LEFT, FALSE, FALSE))
2611 {
2612 BOOL bDeletionSucceeded;
2613 /* Backspace can be grouped for a single undo */
2614 ME_ContinueCoalescingTransaction(editor);
2615 bDeletionSucceeded = ME_DeleteTextAtCursor(editor, 1, 1);
2616 if (!bDeletionSucceeded && !editor->bEmulateVersion10) { /* v4.1 */
2617 /* Deletion was prevented so the cursor is moved back to where it was.
2618 * (e.g. this happens when trying to delete cell boundaries)
2619 */
2620 ME_ArrowKey(editor, VK_RIGHT, FALSE, FALSE);
2621 }
2622 ME_CommitCoalescingUndo(editor);
2623 }
2624 else
2625 return TRUE;
2626 table_move_from_row_start( editor );
2627 ME_UpdateSelectionLinkAttribute(editor);
2628 ME_UpdateRepaint(editor, FALSE);
2629 ME_SendRequestResize(editor, FALSE);
2630 return TRUE;
2631 case VK_RETURN:
2632 if (!editor->bEmulateVersion10)
2633 return handle_enter(editor);
2634 break;
2635 case 'A':
2636 if (ctrl_is_down)
2637 {
2638 set_selection( editor, 0, -1 );
2639 return TRUE;
2640 }
2641 break;
2642 case 'V':
2643 if (ctrl_is_down)
2644 return paste_special( editor, 0, NULL, FALSE );
2645 break;
2646 case 'C':
2647 case 'X':
2648 if (ctrl_is_down)
2649 return copy_or_cut(editor, nKey == 'X');
2650 break;
2651 case 'Z':
2652 if (ctrl_is_down)
2653 {
2654 ME_Undo(editor);
2655 return TRUE;
2656 }
2657 break;
2658 case 'Y':
2659 if (ctrl_is_down)
2660 {
2661 ME_Redo(editor);
2662 return TRUE;
2663 }
2664 break;
2665
2666 default:
2667 if (nKey != VK_SHIFT && nKey != VK_CONTROL && nKey && nKey != VK_MENU)
2668 editor->nUDArrowX = -1;
2669 if (ctrl_is_down)
2670 {
2671 if (nKey == 'W')
2672 {
2673 CHARFORMAT2W chf;
2674 char buf[2048];
2675 chf.cbSize = sizeof(chf);
2676
2677 ME_GetSelectionCharFormat(editor, &chf);
2678 ME_DumpStyleToBuf(&chf, buf);
2679 MessageBoxA(NULL, buf, "Style dump", MB_OK);
2680 }
2681 if (nKey == 'Q')
2682 {
2683 ME_CheckCharOffsets(editor);
2684 }
2685 }
2686 }
2687 return FALSE;
2688}
2689
2690static LRESULT handle_wm_char( ME_TextEditor *editor, WCHAR wstr, LPARAM flags )
2691{
2692 if (editor->bMouseCaptured)
2693 return 0;
2694
2695 if (editor->props & TXTBIT_READONLY)
2696 {
2697 editor_beep( editor, MB_ICONERROR );
2698 return 0; /* FIXME really 0 ? */
2699 }
2700
2701 if (editor->bEmulateVersion10 && wstr == '\r')
2702 handle_enter(editor);
2703
2704 if ((unsigned)wstr >= ' ' || wstr == '\t')
2705 {
2706 ME_Cursor cursor = editor->pCursors[0];
2707 ME_Paragraph *para = cursor.para;
2708 LONG from, to;
2709 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2710 ME_GetSelectionOfs(editor, &from, &to);
2711 if (wstr == '\t' &&
2712 /* v4.1 allows tabs to be inserted with ctrl key down */
2713 !(ctrl_is_down && !editor->bEmulateVersion10))
2714 {
2715 BOOL selected_row = FALSE;
2716
2717 if (ME_IsSelection(editor) &&
2718 cursor.run->nCharOfs + cursor.nOffset == 0 &&
2719 to == ME_GetCursorOfs(&editor->pCursors[0]) && para_prev( para ))
2720 {
2721 para = para_prev( para );
2722 selected_row = TRUE;
2723 }
2724 if (para_in_table( para ))
2725 {
2726 table_handle_tab( editor, selected_row );
2727 ME_CommitUndo(editor);
2728 return 0;
2729 }
2730 }
2731 else if (!editor->bEmulateVersion10) /* v4.1 */
2732 {
2733 if (para->nFlags & MEPF_ROWEND)
2734 {
2735 if (from == to)
2736 {
2737 para = para_next( para );
2738 if (para->nFlags & MEPF_ROWSTART) para = para_next( para );
2739 editor->pCursors[0].para = para;
2740 editor->pCursors[0].run = para_first_run( para );
2741 editor->pCursors[0].nOffset = 0;
2742 editor->pCursors[1] = editor->pCursors[0];
2743 }
2744 }
2745 }
2746 else /* v1.0 - 3.0 */
2747 {
2748 if (para_in_table( para ) && cursor.run->nFlags & MERF_ENDPARA && from == to)
2749 {
2750 /* Text should not be inserted at the end of the table. */
2751 editor_beep( editor, -1 );
2752 return 0;
2753 }
2754 }
2755 /* FIXME maybe it would make sense to call EM_REPLACESEL instead ? */
2756 /* WM_CHAR is restricted to nTextLimit */
2757 if(editor->nTextLimit > ME_GetTextLength(editor) - (to-from))
2758 {
2759 ME_Style *style = style_get_insert_style( editor, editor->pCursors );
2760 ME_ContinueCoalescingTransaction(editor);
2761 ME_InsertTextFromCursor(editor, 0, &wstr, 1, style);
2762 ME_ReleaseStyle(style);
2763 ME_CommitCoalescingUndo(editor);
2764 ITextHost_TxSetCursor(editor->texthost, NULL, FALSE);
2765 }
2766
2767 ME_UpdateSelectionLinkAttribute(editor);
2768 ME_UpdateRepaint(editor, FALSE);
2769 }
2770 return 0;
2771}
2772
2773/* Process the message and calculate the new click count.
2774 *
2775 * returns: The click count if it is mouse down event, else returns 0. */
2776static int ME_CalculateClickCount(ME_TextEditor *editor, UINT msg, WPARAM wParam,
2777 LPARAM lParam)
2778{
2779 static int clickNum = 0;
2780 if (msg < WM_MOUSEFIRST || msg > WM_MOUSELAST)
2781 return 0;
2782
2783 if ((msg == WM_LBUTTONDBLCLK) ||
2784 (msg == WM_RBUTTONDBLCLK) ||
2785 (msg == WM_MBUTTONDBLCLK) ||
2786 (msg == WM_XBUTTONDBLCLK))
2787 {
2788 msg -= (WM_LBUTTONDBLCLK - WM_LBUTTONDOWN);
2789 }
2790
2791 if ((msg == WM_LBUTTONDOWN) ||
2792 (msg == WM_RBUTTONDOWN) ||
2793 (msg == WM_MBUTTONDOWN) ||
2794 (msg == WM_XBUTTONDOWN))
2795 {
2796 static MSG prevClickMsg;
2797 MSG clickMsg;
2798 /* Compare the editor instead of the hwnd so that the this
2799 * can still be done for windowless richedit controls. */
2800 clickMsg.hwnd = (HWND)editor;
2801 clickMsg.message = msg;
2802 clickMsg.wParam = wParam;
2803 clickMsg.lParam = lParam;
2804 clickMsg.time = GetMessageTime();
2805 clickMsg.pt.x = (short)LOWORD(lParam);
2806 clickMsg.pt.y = (short)HIWORD(lParam);
2807 if ((clickNum != 0) &&
2808 (clickMsg.message == prevClickMsg.message) &&
2809 (clickMsg.hwnd == prevClickMsg.hwnd) &&
2810 (clickMsg.wParam == prevClickMsg.wParam) &&
2811 (clickMsg.time - prevClickMsg.time < GetDoubleClickTime()) &&
2812 (abs(clickMsg.pt.x - prevClickMsg.pt.x) < GetSystemMetrics(SM_CXDOUBLECLK)/2) &&
2813 (abs(clickMsg.pt.y - prevClickMsg.pt.y) < GetSystemMetrics(SM_CYDOUBLECLK)/2))
2814 {
2815 clickNum++;
2816 } else {
2817 clickNum = 1;
2818 }
2819 prevClickMsg = clickMsg;
2820 } else {
2821 return 0;
2822 }
2823 return clickNum;
2824}
2825
2826static BOOL is_link( ME_Run *run )
2827{
2828 return (run->style->fmt.dwMask & CFM_LINK) && (run->style->fmt.dwEffects & CFE_LINK);
2829}
2830
2831void editor_set_cursor( ME_TextEditor *editor, int x, int y )
2832{
2833 ME_Cursor pos;
2834 static HCURSOR cursor_arrow, cursor_hand, cursor_ibeam, cursor_reverse;
2835 HCURSOR cursor;
2836
2837 if (!cursor_arrow)
2838 {
2839 cursor_arrow = LoadCursorW( NULL, MAKEINTRESOURCEW( IDC_ARROW ) );
2840 cursor_hand = LoadCursorW( NULL, MAKEINTRESOURCEW( IDC_HAND ) );
2841 cursor_ibeam = LoadCursorW( NULL, MAKEINTRESOURCEW( IDC_IBEAM ) );
2842 cursor_reverse = LoadCursorW( dll_instance, MAKEINTRESOURCEW( OCR_REVERSE ) );
2843 }
2844
2845 cursor = cursor_ibeam;
2846
2847 if ((editor->nSelectionType == stLine && editor->bMouseCaptured) ||
2848 (!editor->bEmulateVersion10 && y < editor->rcFormat.top && x < editor->rcFormat.left))
2849 cursor = cursor_reverse;
2850 else if (y < editor->rcFormat.top || y > editor->rcFormat.bottom)
2851 {
2852 if (editor->bEmulateVersion10) cursor = cursor_arrow;
2853 else cursor = cursor_ibeam;
2854 }
2855 else if (x < editor->rcFormat.left) cursor = cursor_reverse;
2856 else if (cursor_from_coords( editor, x, y, &pos ))
2857 {
2858 ME_Run *run = pos.run;
2859
2860 if (is_link( run )) cursor = cursor_hand;
2861
2862 else if (ME_IsSelection( editor ))
2863 {
2864 LONG start, end;
2865 int offset = ME_GetCursorOfs( &pos );
2866
2867 ME_GetSelectionOfs( editor, &start, &end );
2868 if (start <= offset && end >= offset) cursor = cursor_arrow;
2869 }
2870 }
2871
2872 ITextHost_TxSetCursor( editor->texthost, cursor, cursor == cursor_ibeam );
2873}
2874
2875static LONG ME_GetSelectionType(ME_TextEditor *editor)
2876{
2877 LONG sel_type = SEL_EMPTY;
2878 LONG start, end;
2879
2880 ME_GetSelectionOfs(editor, &start, &end);
2881 if (start == end)
2882 sel_type = SEL_EMPTY;
2883 else
2884 {
2885 LONG object_count = 0, character_count = 0;
2886 int i;
2887
2888 for (i = 0; i < end - start; i++)
2889 {
2890 ME_Cursor cursor;
2891
2892 cursor_from_char_ofs( editor, start + i, &cursor );
2893 if (cursor.run->reobj) object_count++;
2894 else character_count++;
2895 if (character_count >= 2 && object_count >= 2)
2896 return (SEL_TEXT | SEL_MULTICHAR | SEL_OBJECT | SEL_MULTIOBJECT);
2897 }
2898 if (character_count)
2899 {
2900 sel_type |= SEL_TEXT;
2901 if (character_count >= 2)
2902 sel_type |= SEL_MULTICHAR;
2903 }
2904 if (object_count)
2905 {
2906 sel_type |= SEL_OBJECT;
2907 if (object_count >= 2)
2908 sel_type |= SEL_MULTIOBJECT;
2909 }
2910 }
2911 return sel_type;
2912}
2913
2914static BOOL ME_ShowContextMenu(ME_TextEditor *editor, int x, int y)
2915{
2916 CHARRANGE selrange;
2917 HMENU menu;
2918 int seltype;
2919 HWND hwnd, parent;
2920
2921 if (!editor->lpOleCallback || !editor->have_texthost2) return FALSE;
2922 if (FAILED( ITextHost2_TxGetWindow( editor->texthost, &hwnd ))) return FALSE;
2923 parent = GetParent( hwnd );
2924 if (!parent) parent = hwnd;
2925
2926 ME_GetSelectionOfs( editor, &selrange.cpMin, &selrange.cpMax );
2927 seltype = ME_GetSelectionType( editor );
2928 if (SUCCEEDED( IRichEditOleCallback_GetContextMenu( editor->lpOleCallback, seltype, NULL, &selrange, &menu ) ))
2929 {
2930 TrackPopupMenu( menu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, x, y, 0, parent, NULL );
2931 DestroyMenu( menu );
2932 }
2933 return TRUE;
2934}
2935
2936ME_TextEditor *ME_MakeEditor(ITextHost *texthost, BOOL bEmulateVersion10)
2937{
2938 ME_TextEditor *ed = malloc( sizeof(*ed) );
2939 int i;
2940 LONG selbarwidth;
2941 HRESULT hr;
2942 HDC hdc;
2943
2944 ed->sizeWindow.cx = ed->sizeWindow.cy = 0;
2945 if (ITextHost_QueryInterface( texthost, &IID_ITextHost2, (void **)&ed->texthost ) == S_OK)
2946 {
2947 ITextHost_Release( texthost );
2948 ed->have_texthost2 = TRUE;
2949 }
2950 else
2951 {
2952 ed->texthost = (ITextHost2 *)texthost;
2953 ed->have_texthost2 = FALSE;
2954 }
2955
2956 ed->bEmulateVersion10 = bEmulateVersion10;
2957 ed->in_place_active = FALSE;
2958 ed->total_rows = 0;
2959 ITextHost_TxGetPropertyBits( ed->texthost, TXTBIT_RICHTEXT | TXTBIT_MULTILINE | TXTBIT_READONLY |
2960 TXTBIT_USEPASSWORD | TXTBIT_HIDESELECTION | TXTBIT_SAVESELECTION |
2961 TXTBIT_AUTOWORDSEL | TXTBIT_VERTICAL | TXTBIT_WORDWRAP | TXTBIT_ALLOWBEEP |
2962 TXTBIT_DISABLEDRAG,
2963 &ed->props );
2964 ITextHost_TxGetScrollBars( ed->texthost, &ed->scrollbars );
2965 ed->pBuffer = ME_MakeText();
2966 ed->nZoomNumerator = ed->nZoomDenominator = 0;
2967 ed->nAvailWidth = 0; /* wrap to client area */
2968 list_init( &ed->style_list );
2969
2970 hdc = ITextHost_TxGetDC( ed->texthost );
2971 ME_MakeFirstParagraph( ed, hdc );
2972 /* The four cursors are for:
2973 * 0 - The position where the caret is shown
2974 * 1 - The anchored end of the selection (for normal selection)
2975 * 2 & 3 - The anchored start and end respectively for word, line,
2976 * or paragraph selection.
2977 */
2978 ed->nCursors = 4;
2979 ed->pCursors = malloc( ed->nCursors * sizeof(*ed->pCursors) );
2980 ME_SetCursorToStart(ed, &ed->pCursors[0]);
2981 ed->pCursors[1] = ed->pCursors[0];
2982 ed->pCursors[2] = ed->pCursors[0];
2983 ed->pCursors[3] = ed->pCursors[1];
2984 ed->nLastTotalLength = ed->nTotalLength = 0;
2985 ed->nLastTotalWidth = ed->nTotalWidth = 0;
2986 ed->nUDArrowX = -1;
2987 ed->nEventMask = 0;
2988 ed->nModifyStep = 0;
2989 ed->nTextLimit = TEXT_LIMIT_DEFAULT;
2990 list_init( &ed->undo_stack );
2991 list_init( &ed->redo_stack );
2992 ed->nUndoStackSize = 0;
2993 ed->nUndoLimit = STACK_SIZE_DEFAULT;
2994 ed->nUndoMode = umAddToUndo;
2995 ed->undo_ctl_state = undoActive;
2996 ed->nParagraphs = 1;
2997 ed->nLastSelStart = ed->nLastSelEnd = 0;
2998 ed->last_sel_start_para = ed->last_sel_end_para = ed->pCursors[0].para;
2999 ed->bHideSelection = FALSE;
3000 ed->pfnWordBreak = NULL;
3001 ed->richole = NULL;
3002 ed->lpOleCallback = NULL;
3003 ed->mode = TM_MULTILEVELUNDO | TM_MULTICODEPAGE;
3004 ed->mode |= (ed->props & TXTBIT_RICHTEXT) ? TM_RICHTEXT : TM_PLAINTEXT;
3005 ed->AutoURLDetect_bEnable = FALSE;
3006 ed->bHaveFocus = FALSE;
3007 ed->freeze_count = 0;
3008 ed->bMouseCaptured = FALSE;
3009 ed->caret_hidden = FALSE;
3010 ed->caret_height = 0;
3011 for (i=0; i<HFONT_CACHE_SIZE; i++)
3012 {
3013 ed->pFontCache[i].nRefs = 0;
3014 ed->pFontCache[i].nAge = 0;
3015 ed->pFontCache[i].hFont = NULL;
3016 }
3017
3018 ME_CheckCharOffsets(ed);
3019 SetRectEmpty(&ed->rcFormat);
3020 hr = ITextHost_TxGetSelectionBarWidth( ed->texthost, &selbarwidth );
3021 /* FIXME: Convert selbarwidth from HIMETRIC to pixels */
3022 if (hr == S_OK && selbarwidth) ed->selofs = SELECTIONBAR_WIDTH;
3023 else ed->selofs = 0;
3024 ed->nSelectionType = stPosition;
3025
3026 ed->password_char = 0;
3027 if (ed->props & TXTBIT_USEPASSWORD)
3028 ITextHost_TxGetPasswordChar( ed->texthost, &ed->password_char );
3029
3030 ed->bWordWrap = (ed->props & TXTBIT_WORDWRAP) && (ed->props & TXTBIT_MULTILINE);
3031
3032 ed->notified_cr.cpMin = ed->notified_cr.cpMax = 0;
3033
3034 /* Default scrollbar information */
3035 ed->vert_si.cbSize = sizeof(SCROLLINFO);
3036 ed->vert_si.nMin = 0;
3037 ed->vert_si.nMax = 0;
3038 ed->vert_si.nPage = 0;
3039 ed->vert_si.nPos = 0;
3040 ed->vert_sb_enabled = 0;
3041
3042 ed->horz_si.cbSize = sizeof(SCROLLINFO);
3043 ed->horz_si.nMin = 0;
3044 ed->horz_si.nMax = 0;
3045 ed->horz_si.nPage = 0;
3046 ed->horz_si.nPos = 0;
3047 ed->horz_sb_enabled = 0;
3048
3049 if (ed->scrollbars & ES_DISABLENOSCROLL)
3050 {
3051 if (ed->scrollbars & WS_VSCROLL)
3052 {
3053 ITextHost_TxSetScrollRange( ed->texthost, SB_VERT, 0, 1, TRUE );
3054 ITextHost_TxEnableScrollBar( ed->texthost, SB_VERT, ESB_DISABLE_BOTH );
3055 }
3056 if (ed->scrollbars & WS_HSCROLL)
3057 {
3058 ITextHost_TxSetScrollRange( ed->texthost, SB_HORZ, 0, 1, TRUE );
3059 ITextHost_TxEnableScrollBar( ed->texthost, SB_HORZ, ESB_DISABLE_BOTH );
3060 }
3061 }
3062
3063 ed->wheel_remain = 0;
3064
3065 ed->back_style = TXTBACK_OPAQUE;
3066 ITextHost_TxGetBackStyle( ed->texthost, &ed->back_style );
3067
3068 list_init( &ed->reobj_list );
3069 OleInitialize(NULL);
3070
3071 wrap_marked_paras_dc( ed, hdc, FALSE );
3072 ITextHost_TxReleaseDC( ed->texthost, hdc );
3073
3074 return ed;
3075}
3076
3077void ME_DestroyEditor(ME_TextEditor *editor)
3078{
3079 ME_DisplayItem *p = editor->pBuffer->pFirst, *pNext = NULL;
3080 ME_Style *s, *cursor2;
3081 int i;
3082
3083 ME_ClearTempStyle(editor);
3084 ME_EmptyUndoStack(editor);
3085 editor->pBuffer->pFirst = NULL;
3086 while(p)
3087 {
3088 pNext = p->next;
3089 if (p->type == diParagraph)
3090 para_destroy( editor, &p->member.para );
3091 else
3092 ME_DestroyDisplayItem(p);
3093 p = pNext;
3094 }
3095
3096 LIST_FOR_EACH_ENTRY_SAFE( s, cursor2, &editor->style_list, ME_Style, entry )
3097 ME_DestroyStyle( s );
3098
3099 ME_ReleaseStyle(editor->pBuffer->pDefaultStyle);
3100 for (i=0; i<HFONT_CACHE_SIZE; i++)
3101 {
3102 if (editor->pFontCache[i].hFont)
3103 DeleteObject(editor->pFontCache[i].hFont);
3104 }
3105 if(editor->lpOleCallback)
3106 IRichEditOleCallback_Release(editor->lpOleCallback);
3107
3108 OleUninitialize();
3109
3110 free(editor->pBuffer);
3111 free(editor->pCursors);
3112 free(editor);
3113}
3114
3115static inline int get_default_line_height( ME_TextEditor *editor )
3116{
3117 int height = 0;
3118
3119 if (editor->pBuffer && editor->pBuffer->pDefaultStyle)
3120 height = editor->pBuffer->pDefaultStyle->tm.tmHeight;
3121 if (height <= 0) height = 24;
3122
3123 return height;
3124}
3125
3126static inline int calc_wheel_change( int *remain, int amount_per_click )
3127{
3128 int change = amount_per_click * (float)*remain / WHEEL_DELTA;
3129 *remain -= WHEEL_DELTA * change / amount_per_click;
3130 return change;
3131}
3132
3133void link_notify(ME_TextEditor *editor, UINT msg, WPARAM wParam, LPARAM lParam)
3134{
3135 int x,y;
3136 ME_Cursor cursor; /* The start of the clicked text. */
3137 ME_Run *run;
3138 ENLINK info;
3139
3140 x = (short)LOWORD(lParam);
3141 y = (short)HIWORD(lParam);
3142 if (!cursor_from_coords( editor, x, y, &cursor )) return;
3143
3144 if (is_link( cursor.run ))
3145 { /* The clicked run has CFE_LINK set */
3146 info.nmhdr.hwndFrom = NULL;
3147 info.nmhdr.idFrom = 0;
3148 info.nmhdr.code = EN_LINK;
3149 info.msg = msg;
3150 info.wParam = wParam;
3151 info.lParam = lParam;
3152 cursor.nOffset = 0;
3153
3154 /* find the first contiguous run with CFE_LINK set */
3155 info.chrg.cpMin = ME_GetCursorOfs(&cursor);
3156 run = cursor.run;
3157 while ((run = run_prev( run )) && is_link( run ))
3158 info.chrg.cpMin -= run->len;
3159
3160 /* find the last contiguous run with CFE_LINK set */
3161 info.chrg.cpMax = ME_GetCursorOfs(&cursor) + cursor.run->len;
3162 run = cursor.run;
3163 while ((run = run_next( run )) && is_link( run ))
3164 info.chrg.cpMax += run->len;
3165
3166 ITextHost_TxNotify(editor->texthost, info.nmhdr.code, &info);
3167 }
3168}
3169
3170void ME_ReplaceSel(ME_TextEditor *editor, BOOL can_undo, const WCHAR *str, int len)
3171{
3172 LONG from, to;
3173 int nStartCursor;
3174 ME_Style *style;
3175
3176 nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3177 style = ME_GetSelectionInsertStyle(editor);
3178 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to-from, FALSE);
3179 ME_InsertTextFromCursor(editor, 0, str, len, style);
3180 ME_ReleaseStyle(style);
3181 /* drop temporary style if line end */
3182 /*
3183 * FIXME question: does abc\n mean: put abc,
3184 * clear temp style, put \n? (would require a change)
3185 */
3186 if (len>0 && str[len-1] == '\n')
3187 ME_ClearTempStyle(editor);
3188 ME_CommitUndo(editor);
3189 ME_UpdateSelectionLinkAttribute(editor);
3190 if (!can_undo)
3191 ME_EmptyUndoStack(editor);
3192 ME_UpdateRepaint(editor, FALSE);
3193}
3194
3195static void ME_SetText(ME_TextEditor *editor, void *text, BOOL unicode)
3196{
3197 LONG codepage = unicode ? CP_UNICODE : CP_ACP;
3198 int textLen;
3199
3200 LPWSTR wszText = ME_ToUnicode(codepage, text, &textLen);
3201 ME_InsertTextFromCursor(editor, 0, wszText, textLen, editor->pBuffer->pDefaultStyle);
3202 ME_EndToUnicode(codepage, wszText);
3203}
3204
3205static LRESULT handle_EM_SETCHARFORMAT( ME_TextEditor *editor, WPARAM flags, const CHARFORMAT2W *fmt_in )
3206{
3207 CHARFORMAT2W fmt;
3208 BOOL changed = TRUE;
3209 ME_Cursor start, end;
3210
3211 if (!cfany_to_cf2w( &fmt, fmt_in )) return 0;
3212
3213 if (flags & SCF_ALL)
3214 {
3215 if (editor->mode & TM_PLAINTEXT)
3216 {
3217 ME_SetDefaultCharFormat( editor, &fmt );
3218 }
3219 else
3220 {
3221 ME_SetCursorToStart( editor, &start );
3222 ME_SetCharFormat( editor, &start, NULL, &fmt );
3223 editor->nModifyStep = 1;
3224 }
3225 }
3226 else if (flags & SCF_SELECTION)
3227 {
3228 if (editor->mode & TM_PLAINTEXT) return 0;
3229 if (flags & SCF_WORD)
3230 {
3231 end = editor->pCursors[0];
3232 ME_MoveCursorWords( editor, &end, +1 );
3233 start = end;
3234 ME_MoveCursorWords( editor, &start, -1 );
3235 ME_SetCharFormat( editor, &start, &end, &fmt );
3236 }
3237 changed = ME_IsSelection( editor );
3238 ME_SetSelectionCharFormat( editor, &fmt );
3239 if (changed) editor->nModifyStep = 1;
3240 }
3241 else /* SCF_DEFAULT */
3242 {
3243 ME_SetDefaultCharFormat( editor, &fmt );
3244 }
3245
3246 ME_CommitUndo( editor );
3247 if (changed)
3248 {
3249 ME_WrapMarkedParagraphs( editor );
3250 ME_UpdateScrollBar( editor );
3251 }
3252 return 1;
3253}
3254
3255#define UNSUPPORTED_MSG(e) \
3256 case e: \
3257 FIXME(#e ": stub\n"); \
3258 *phresult = S_FALSE; \
3259 return 0;
3260
3261/* Handle messages for windowless and windowed richedit controls.
3262 *
3263 * The LRESULT that is returned is a return value for window procs,
3264 * and the phresult parameter is the COM return code needed by the
3265 * text services interface. */
3266LRESULT editor_handle_message( ME_TextEditor *editor, UINT msg, WPARAM wParam,
3267 LPARAM lParam, HRESULT* phresult )
3268{
3269 *phresult = S_OK;
3270
3271 switch(msg) {
3272
3273 UNSUPPORTED_MSG(EM_DISPLAYBAND)
3274 UNSUPPORTED_MSG(EM_FINDWORDBREAK)
3275 UNSUPPORTED_MSG(EM_FMTLINES)
3276 UNSUPPORTED_MSG(EM_FORMATRANGE)
3277 UNSUPPORTED_MSG(EM_GETBIDIOPTIONS)
3278 UNSUPPORTED_MSG(EM_GETEDITSTYLE)
3279 UNSUPPORTED_MSG(EM_GETIMECOMPMODE)
3280 UNSUPPORTED_MSG(EM_GETIMESTATUS)
3281 UNSUPPORTED_MSG(EM_SETIMESTATUS)
3282 UNSUPPORTED_MSG(EM_GETLANGOPTIONS)
3283 UNSUPPORTED_MSG(EM_GETREDONAME)
3284 UNSUPPORTED_MSG(EM_GETTYPOGRAPHYOPTIONS)
3285 UNSUPPORTED_MSG(EM_GETUNDONAME)
3286 UNSUPPORTED_MSG(EM_GETWORDBREAKPROCEX)
3287 UNSUPPORTED_MSG(EM_SETBIDIOPTIONS)
3288 UNSUPPORTED_MSG(EM_SETEDITSTYLE)
3289 UNSUPPORTED_MSG(EM_SETLANGOPTIONS)
3290 UNSUPPORTED_MSG(EM_SETMARGINS)
3291 UNSUPPORTED_MSG(EM_SETPALETTE)
3292 UNSUPPORTED_MSG(EM_SETTABSTOPS)
3293 UNSUPPORTED_MSG(EM_SETTYPOGRAPHYOPTIONS)
3294 UNSUPPORTED_MSG(EM_SETWORDBREAKPROCEX)
3295
3296/* Messages specific to Richedit controls */
3297
3298 case EM_STREAMIN:
3299 return ME_StreamIn(editor, wParam, (EDITSTREAM*)lParam, TRUE);
3300 case EM_STREAMOUT:
3301 return ME_StreamOut(editor, wParam, (EDITSTREAM *)lParam);
3302 case EM_EMPTYUNDOBUFFER:
3303 ME_EmptyUndoStack(editor);
3304 return 0;
3305 case EM_GETSEL:
3306 {
3307 /* Note: wParam/lParam can be NULL */
3308 LONG from, to;
3309 LONG *pfrom = wParam ? (LONG *)wParam : &from;
3310 LONG *pto = lParam ? (LONG *)lParam : &to;
3311 ME_GetSelectionOfs(editor, pfrom, pto);
3312 if ((*pfrom|*pto) & 0xFFFF0000)
3313 return -1;
3314 return MAKELONG(*pfrom,*pto);
3315 }
3316 case EM_EXGETSEL:
3317 {
3318 CHARRANGE *pRange = (CHARRANGE *)lParam;
3319 ME_GetSelectionOfs(editor, &pRange->cpMin, &pRange->cpMax);
3320 TRACE("EM_EXGETSEL = (%ld,%ld)\n", pRange->cpMin, pRange->cpMax);
3321 return 0;
3322 }
3323 case EM_SETUNDOLIMIT:
3324 {
3325 editor_enable_undo(editor);
3326 if ((int)wParam < 0)
3327 editor->nUndoLimit = STACK_SIZE_DEFAULT;
3328 else
3329 editor->nUndoLimit = min(wParam, STACK_SIZE_MAX);
3330 /* Setting a max stack size keeps wine from getting killed
3331 for hogging memory. Windows allocates all this memory at once, so
3332 no program would realistically set a value above our maximum. */
3333 return editor->nUndoLimit;
3334 }
3335 case EM_CANUNDO:
3336 return !list_empty( &editor->undo_stack );
3337 case EM_CANREDO:
3338 return !list_empty( &editor->redo_stack );
3339 case WM_UNDO: /* FIXME: actually not the same */
3340 case EM_UNDO:
3341 return ME_Undo(editor);
3342 case EM_REDO:
3343 return ME_Redo(editor);
3344 case EM_SETFONTSIZE:
3345 {
3346 CHARFORMAT2W cf;
3347 LONG tmp_size, size;
3348 BOOL is_increase = ((LONG)wParam > 0);
3349
3350 if (editor->mode & TM_PLAINTEXT)
3351 return FALSE;
3352
3353 cf.cbSize = sizeof(cf);
3354 cf.dwMask = CFM_SIZE;
3355 ME_GetSelectionCharFormat(editor, &cf);
3356 tmp_size = (cf.yHeight / 20) + wParam;
3357
3358 if (tmp_size <= 1)
3359 size = 1;
3360 else if (tmp_size > 12 && tmp_size < 28 && tmp_size % 2)
3361 size = tmp_size + (is_increase ? 1 : -1);
3362 else if (tmp_size > 28 && tmp_size < 36)
3363 size = is_increase ? 36 : 28;
3364 else if (tmp_size > 36 && tmp_size < 48)
3365 size = is_increase ? 48 : 36;
3366 else if (tmp_size > 48 && tmp_size < 72)
3367 size = is_increase ? 72 : 48;
3368 else if (tmp_size > 72 && tmp_size < 80)
3369 size = is_increase ? 80 : 72;
3370 else if (tmp_size > 80 && tmp_size < 1638)
3371 size = 10 * (is_increase ? (tmp_size / 10 + 1) : (tmp_size / 10));
3372 else if (tmp_size >= 1638)
3373 size = 1638;
3374 else
3375 size = tmp_size;
3376
3377 cf.yHeight = size * 20; /* convert twips to points */
3378 ME_SetSelectionCharFormat(editor, &cf);
3379 ME_CommitUndo(editor);
3380 ME_WrapMarkedParagraphs(editor);
3381 ME_UpdateScrollBar(editor);
3382
3383 return TRUE;
3384 }
3385 case EM_SETSEL:
3386 {
3387 return set_selection( editor, wParam, lParam );
3388 }
3389 case EM_SETSCROLLPOS:
3390 {
3391 POINT *point = (POINT *)lParam;
3392 scroll_abs( editor, point->x, point->y, TRUE );
3393 return 0;
3394 }
3395 case EM_AUTOURLDETECT:
3396 {
3397 if (wParam==1 || wParam ==0)
3398 {
3399 editor->AutoURLDetect_bEnable = (BOOL)wParam;
3400 return 0;
3401 }
3402 return E_INVALIDARG;
3403 }
3404 case EM_GETAUTOURLDETECT:
3405 {
3406 return editor->AutoURLDetect_bEnable;
3407 }
3408 case EM_EXSETSEL:
3409 {
3410 CHARRANGE range = *(CHARRANGE *)lParam;
3411
3412 return set_selection( editor, range.cpMin, range.cpMax );
3413 }
3414 case EM_SETTEXTEX:
3415 {
3416 LPWSTR wszText;
3417 SETTEXTEX *pStruct = (SETTEXTEX *)wParam;
3418 LONG from, to;
3419 int len;
3420 ME_Style *style;
3421 BOOL bRtf, bUnicode, bSelection, bUTF8;
3422 int oldModify = editor->nModifyStep;
3423 static const char utf8_bom[] = {0xef, 0xbb, 0xbf};
3424
3425 if (!pStruct) return 0;
3426
3427 /* If we detect ascii rtf at the start of the string,
3428 * we know it isn't unicode. */
3429 bRtf = (lParam && (!strncmp((char *)lParam, "{\\rtf", 5) ||
3430 !strncmp((char *)lParam, "{\\urtf", 6)));
3431 bUnicode = !bRtf && pStruct->codepage == CP_UNICODE;
3432 bUTF8 = (lParam && (!strncmp((char *)lParam, utf8_bom, 3)));
3433
3434 TRACE("EM_SETTEXTEX - %s, flags %ld, cp %d\n",
3435 bUnicode ? debugstr_w((LPCWSTR)lParam) : debugstr_a((LPCSTR)lParam),
3436 pStruct->flags, pStruct->codepage);
3437
3438 bSelection = (pStruct->flags & ST_SELECTION) != 0;
3439 if (bSelection) {
3440 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3441 style = ME_GetSelectionInsertStyle(editor);
3442 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to - from, FALSE);
3443 } else {
3444 ME_Cursor start;
3445 ME_SetCursorToStart(editor, &start);
3446 ME_InternalDeleteText(editor, &start, ME_GetTextLength(editor), FALSE);
3447 style = editor->pBuffer->pDefaultStyle;
3448 }
3449
3450 if (bRtf) {
3451 ME_StreamInRTFString(editor, bSelection, (char *)lParam);
3452 if (bSelection) {
3453 /* FIXME: The length returned doesn't include the rtf control
3454 * characters, only the actual text. */
3455 len = lParam ? strlen((char *)lParam) : 0;
3456 }
3457 } else {
3458 if (bUTF8 && !bUnicode) {
3459 wszText = ME_ToUnicode(CP_UTF8, (void *)(lParam+3), &len);
3460 ME_InsertTextFromCursor(editor, 0, wszText, len, style);
3461 ME_EndToUnicode(CP_UTF8, wszText);
3462 } else {
3463 wszText = ME_ToUnicode(pStruct->codepage, (void *)lParam, &len);
3464 ME_InsertTextFromCursor(editor, 0, wszText, len, style);
3465 ME_EndToUnicode(pStruct->codepage, wszText);
3466 }
3467 }
3468
3469 if (bSelection) {
3470 ME_ReleaseStyle(style);
3471 ME_UpdateSelectionLinkAttribute(editor);
3472 } else {
3473 ME_Cursor cursor;
3474 len = 1;
3475 ME_SetCursorToStart(editor, &cursor);
3476 ME_UpdateLinkAttribute(editor, &cursor, INT_MAX);
3477 }
3478 ME_CommitUndo(editor);
3479 if (!(pStruct->flags & ST_KEEPUNDO))
3480 {
3481 editor->nModifyStep = oldModify;
3482 ME_EmptyUndoStack(editor);
3483 }
3484 ME_UpdateRepaint(editor, FALSE);
3485 return len;
3486 }
3487 case EM_SELECTIONTYPE:
3488 return ME_GetSelectionType(editor);
3489 case EM_GETMODIFY:
3490 return editor->nModifyStep == 0 ? 0 : -1;
3491 case EM_SETMODIFY:
3492 {
3493 if (wParam)
3494 editor->nModifyStep = 1;
3495 else
3496 editor->nModifyStep = 0;
3497
3498 return 0;
3499 }
3500 case EM_SETEVENTMASK:
3501 {
3502 DWORD nOldMask = editor->nEventMask;
3503
3504 editor->nEventMask = lParam;
3505 return nOldMask;
3506 }
3507 case EM_GETEVENTMASK:
3508 return editor->nEventMask;
3509 case EM_SETCHARFORMAT:
3510 return handle_EM_SETCHARFORMAT( editor, wParam, (CHARFORMAT2W *)lParam );
3511 case EM_GETCHARFORMAT:
3512 {
3513 CHARFORMAT2W tmp, *dst = (CHARFORMAT2W *)lParam;
3514 if (dst->cbSize != sizeof(CHARFORMATA) &&
3515 dst->cbSize != sizeof(CHARFORMATW) &&
3516 dst->cbSize != sizeof(CHARFORMAT2A) &&
3517 dst->cbSize != sizeof(CHARFORMAT2W))
3518 return 0;
3519 tmp.cbSize = sizeof(tmp);
3520 if (!wParam)
3521 ME_GetDefaultCharFormat(editor, &tmp);
3522 else
3523 ME_GetSelectionCharFormat(editor, &tmp);
3524 cf2w_to_cfany(dst, &tmp);
3525 return tmp.dwMask;
3526 }
3527 case EM_SETPARAFORMAT:
3528 {
3529 BOOL result = editor_set_selection_para_fmt( editor, (PARAFORMAT2 *)lParam );
3530 ME_WrapMarkedParagraphs(editor);
3531 ME_UpdateScrollBar(editor);
3532 ME_CommitUndo(editor);
3533 return result;
3534 }
3535 case EM_GETPARAFORMAT:
3536 editor_get_selection_para_fmt( editor, (PARAFORMAT2 *)lParam );
3537 return ((PARAFORMAT2 *)lParam)->dwMask;
3538 case EM_GETFIRSTVISIBLELINE:
3539 {
3540 ME_Paragraph *para = editor_first_para( editor );
3541 ME_Row *row;
3542 int y = editor->vert_si.nPos;
3543 int count = 0;
3544
3545 while (para_next( para ))
3546 {
3547 if (y < para->pt.y + para->nHeight) break;
3548 count += para->nRows;
3549 para = para_next( para );
3550 }
3551
3552 row = para_first_row( para );
3553 while (row)
3554 {
3555 if (y < para->pt.y + row->pt.y + row->nHeight) break;
3556 count++;
3557 row = row_next( row );
3558 }
3559 return count;
3560 }
3561 case EM_HIDESELECTION:
3562 {
3563 editor->bHideSelection = (wParam != 0);
3564 ME_InvalidateSelection(editor);
3565 return 0;
3566 }
3567 case EM_LINESCROLL:
3568 {
3569 if (!(editor->props & TXTBIT_MULTILINE))
3570 return FALSE;
3571 ME_ScrollDown( editor, lParam * get_default_line_height( editor ) );
3572 return TRUE;
3573 }
3574 case WM_CLEAR:
3575 {
3576 LONG from, to;
3577 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3578 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to-from, FALSE);
3579 ME_CommitUndo(editor);
3580 ME_UpdateRepaint(editor, TRUE);
3581 return 0;
3582 }
3583 case EM_REPLACESEL:
3584 {
3585 WCHAR *text = (WCHAR *)lParam;
3586 int len = text ? lstrlenW( text ) : 0;
3587
3588 TRACE( "EM_REPLACESEL - %s\n", debugstr_w( text ) );
3589 ME_ReplaceSel( editor, !!wParam, text, len );
3590 return len;
3591 }
3592 case EM_SCROLLCARET:
3593 editor_ensure_visible( editor, &editor->pCursors[0] );
3594 return 0;
3595 case WM_SETFONT:
3596 {
3597 LOGFONTW lf;
3598 CHARFORMAT2W fmt;
3599 HDC hDC;
3600 BOOL bRepaint = LOWORD(lParam);
3601
3602 if (!wParam)
3603 wParam = (WPARAM)GetStockObject(SYSTEM_FONT);
3604
3605 if (!GetObjectW((HGDIOBJ)wParam, sizeof(LOGFONTW), &lf))
3606 return 0;
3607
3608 hDC = ITextHost_TxGetDC(editor->texthost);
3609 ME_CharFormatFromLogFont(hDC, &lf, &fmt);
3610 ITextHost_TxReleaseDC(editor->texthost, hDC);
3611 if (editor->mode & TM_RICHTEXT) {
3612 ME_Cursor start;
3613 ME_SetCursorToStart(editor, &start);
3614 ME_SetCharFormat(editor, &start, NULL, &fmt);
3615 }
3616 ME_SetDefaultCharFormat(editor, &fmt);
3617
3618 ME_CommitUndo(editor);
3619 editor_mark_rewrap_all( editor );
3620 ME_WrapMarkedParagraphs(editor);
3621 ME_UpdateScrollBar(editor);
3622 if (bRepaint)
3623 ME_Repaint(editor);
3624#ifdef __REACTOS__
3625 if (ImmIsIME(GetKeyboardLayout(0)))
3626 {
3627 HIMC hIMC = ImmGetContext(editor->hWnd);
3628 ImmSetCompositionFontW(hIMC, &lf);
3629 ImmReleaseContext(editor->hWnd, hIMC);
3630 }
3631#endif
3632 return 0;
3633 }
3634 case WM_SETTEXT:
3635 {
3636 ME_Cursor cursor;
3637 ME_SetCursorToStart(editor, &cursor);
3638 ME_InternalDeleteText(editor, &cursor, ME_GetTextLength(editor), FALSE);
3639 if (lParam)
3640 {
3641 TRACE("WM_SETTEXT lParam==%Ix\n",lParam);
3642 if (!strncmp((char *)lParam, "{\\rtf", 5) ||
3643 !strncmp((char *)lParam, "{\\urtf", 6))
3644 {
3645 /* Undocumented: WM_SETTEXT supports RTF text */
3646 ME_StreamInRTFString(editor, 0, (char *)lParam);
3647 }
3648 else
3649 ME_SetText( editor, (void*)lParam, TRUE );
3650 }
3651 else
3652 TRACE("WM_SETTEXT - NULL\n");
3653 ME_SetCursorToStart(editor, &cursor);
3654 ME_UpdateLinkAttribute(editor, &cursor, INT_MAX);
3655 set_selection_cursors(editor, 0, 0);
3656 editor->nModifyStep = 0;
3657 ME_CommitUndo(editor);
3658 ME_EmptyUndoStack(editor);
3659 ME_UpdateRepaint(editor, FALSE);
3660 return 1;
3661 }
3662 case EM_CANPASTE:
3663 return paste_special( editor, 0, NULL, TRUE );
3664 case WM_PASTE:
3665 case WM_MBUTTONDOWN:
3666 wParam = 0;
3667 lParam = 0;
3668 /* fall through */
3669 case EM_PASTESPECIAL:
3670 paste_special( editor, wParam, (REPASTESPECIAL *)lParam, FALSE );
3671 return 0;
3672 case WM_CUT:
3673 case WM_COPY:
3674 copy_or_cut(editor, msg == WM_CUT);
3675 return 0;
3676 case WM_GETTEXTLENGTH:
3677 {
3678 GETTEXTLENGTHEX how;
3679 how.flags = GTL_CLOSE | (editor->bEmulateVersion10 ? 0 : GTL_USECRLF) | GTL_NUMCHARS;
3680 how.codepage = CP_UNICODE;
3681 return ME_GetTextLengthEx(editor, &how);
3682 }
3683 case EM_GETTEXTLENGTHEX:
3684 return ME_GetTextLengthEx(editor, (GETTEXTLENGTHEX *)wParam);
3685 case WM_GETTEXT:
3686 {
3687 GETTEXTEX ex;
3688 ex.cb = wParam * sizeof(WCHAR);
3689 ex.flags = GT_USECRLF;
3690 ex.codepage = CP_UNICODE;
3691 ex.lpDefaultChar = NULL;
3692 ex.lpUsedDefChar = NULL;
3693 return ME_GetTextEx(editor, &ex, lParam);
3694 }
3695 case EM_GETTEXTEX:
3696 return ME_GetTextEx(editor, (GETTEXTEX*)wParam, lParam);
3697 case EM_GETSELTEXT:
3698 {
3699 LONG nFrom, nTo;
3700 int nStartCur = ME_GetSelectionOfs(editor, &nFrom, &nTo);
3701 ME_Cursor *from = &editor->pCursors[nStartCur];
3702 return get_text_range( editor, (WCHAR *)lParam, from, nTo - nFrom );
3703 }
3704 case EM_GETSCROLLPOS:
3705 {
3706 POINT *point = (POINT *)lParam;
3707 point->x = editor->horz_si.nPos;
3708 point->y = editor->vert_si.nPos;
3709 /* 16-bit scaled value is returned as stored in scrollinfo */
3710 if (editor->horz_si.nMax > 0xffff)
3711 point->x = MulDiv(point->x, 0xffff, editor->horz_si.nMax);
3712 if (editor->vert_si.nMax > 0xffff)
3713 point->y = MulDiv(point->y, 0xffff, editor->vert_si.nMax);
3714 return 1;
3715 }
3716 case EM_GETTEXTRANGE:
3717 {
3718 TEXTRANGEW *rng = (TEXTRANGEW *)lParam;
3719 ME_Cursor start;
3720 int nStart = rng->chrg.cpMin;
3721 int nEnd = rng->chrg.cpMax;
3722 int textlength = ME_GetTextLength(editor);
3723
3724 TRACE( "EM_GETTEXTRANGE min = %ld max = %ld textlength = %d\n", rng->chrg.cpMin, rng->chrg.cpMax, textlength );
3725 if (nStart < 0) return 0;
3726 if ((nStart == 0 && nEnd == -1) || nEnd > textlength)
3727 nEnd = textlength;
3728 if (nStart >= nEnd) return 0;
3729
3730 cursor_from_char_ofs( editor, nStart, &start );
3731 return get_text_range( editor, rng->lpstrText, &start, nEnd - nStart );
3732 }
3733 case EM_GETLINE:
3734 {
3735 ME_Row *row;
3736 ME_Run *run;
3737 const unsigned int nMaxChars = *(WORD *) lParam;
3738 unsigned int nCharsLeft = nMaxChars;
3739 char *dest = (char *) lParam;
3740 ME_Cursor start, end;
3741
3742 TRACE( "EM_GETLINE: row=%d, nMaxChars=%d\n", (int)wParam, nMaxChars );
3743
3744 row = row_from_row_number( editor, wParam );
3745 if (row == NULL) return 0;
3746
3747 row_first_cursor( row, &start );
3748 row_end_cursor( row, &end, TRUE );
3749 run = start.run;
3750 while (nCharsLeft)
3751 {
3752 WCHAR *str;
3753 unsigned int nCopy;
3754 int ofs = (run == start.run) ? start.nOffset : 0;
3755 int len = (run == end.run) ? end.nOffset : run->len;
3756
3757 str = get_text( run, ofs );
3758 nCopy = min( nCharsLeft, len );
3759
3760 memcpy(dest, str, nCopy * sizeof(WCHAR));
3761 dest += nCopy * sizeof(WCHAR);
3762 nCharsLeft -= nCopy;
3763 if (run == end.run) break;
3764 run = row_next_run( row, run );
3765 }
3766
3767 /* append line termination, space allowing */
3768 if (nCharsLeft > 0) *((WCHAR *)dest) = '\0';
3769
3770 TRACE("EM_GETLINE: got %u characters\n", nMaxChars - nCharsLeft);
3771 return nMaxChars - nCharsLeft;
3772 }
3773 case EM_GETLINECOUNT:
3774 {
3775 int count = editor->total_rows;
3776 ME_Run *prev_run, *last_run;
3777
3778 last_run = para_end_run( para_prev( editor_end_para( editor ) ) );
3779 prev_run = run_prev_all_paras( last_run );
3780
3781 if (editor->bEmulateVersion10 && prev_run && last_run->nCharOfs == 0 &&
3782 prev_run->len == 1 && *get_text( prev_run, 0 ) == '\r')
3783 {
3784 /* In 1.0 emulation, the last solitary \r at the very end of the text
3785 (if one exists) is NOT a line break.
3786 FIXME: this is an ugly hack. This should have a more regular model. */
3787 count--;
3788 }
3789
3790 count = max(1, count);
3791 TRACE("EM_GETLINECOUNT: count==%d\n", count);
3792 return count;
3793 }
3794 case EM_LINEFROMCHAR:
3795 {
3796 if (wParam == -1) wParam = ME_GetCursorOfs( editor->pCursors + 1 );
3797 return row_number_from_char_ofs( editor, wParam );
3798 }
3799 case EM_EXLINEFROMCHAR:
3800 {
3801 if (lParam == -1) lParam = ME_GetCursorOfs( editor->pCursors + 1 );
3802 return row_number_from_char_ofs( editor, lParam );
3803 }
3804 case EM_LINEINDEX:
3805 {
3806 ME_Row *row;
3807 ME_Cursor cursor;
3808 int ofs;
3809
3810 if (wParam == -1) row = row_from_cursor( editor->pCursors );
3811 else row = row_from_row_number( editor, wParam );
3812 if (!row) return -1;
3813
3814 row_first_cursor( row, &cursor );
3815 ofs = ME_GetCursorOfs( &cursor );
3816 TRACE( "EM_LINEINDEX: nCharOfs==%d\n", ofs );
3817 return ofs;
3818 }
3819 case EM_LINELENGTH:
3820 {
3821 ME_Row *row;
3822 int start_ofs, end_ofs;
3823 ME_Cursor cursor;
3824
3825 if (wParam > ME_GetTextLength(editor))
3826 return 0;
3827 if (wParam == -1)
3828 {
3829 FIXME("EM_LINELENGTH: returning number of unselected characters on lines with selection unsupported.\n");
3830 return 0;
3831 }
3832 cursor_from_char_ofs( editor, wParam, &cursor );
3833 row = row_from_cursor( &cursor );
3834 row_first_cursor( row, &cursor );
3835 start_ofs = ME_GetCursorOfs( &cursor );
3836 row_end_cursor( row, &cursor, FALSE );
3837 end_ofs = ME_GetCursorOfs( &cursor );
3838 TRACE( "EM_LINELENGTH(%Id)==%d\n", wParam, end_ofs - start_ofs );
3839 return end_ofs - start_ofs;
3840 }
3841 case EM_EXLIMITTEXT:
3842 {
3843 if ((int)lParam < 0)
3844 return 0;
3845 if (lParam == 0)
3846 editor->nTextLimit = 65536;
3847 else
3848 editor->nTextLimit = (int) lParam;
3849 return 0;
3850 }
3851 case EM_LIMITTEXT:
3852 {
3853 if (wParam == 0)
3854 editor->nTextLimit = 65536;
3855 else
3856 editor->nTextLimit = (int) wParam;
3857 return 0;
3858 }
3859 case EM_GETLIMITTEXT:
3860 {
3861 return editor->nTextLimit;
3862 }
3863 case EM_FINDTEXT:
3864 case EM_FINDTEXTW:
3865 {
3866 FINDTEXTW *ft = (FINDTEXTW *)lParam;
3867 return ME_FindText(editor, wParam, &ft->chrg, ft->lpstrText, NULL);
3868 }
3869 case EM_FINDTEXTEX:
3870 case EM_FINDTEXTEXW:
3871 {
3872 FINDTEXTEXW *ex = (FINDTEXTEXW *)lParam;
3873 return ME_FindText(editor, wParam, &ex->chrg, ex->lpstrText, &ex->chrgText);
3874 }
3875 case EM_GETZOOM:
3876 if (!wParam || !lParam)
3877 return FALSE;
3878 *(int *)wParam = editor->nZoomNumerator;
3879 *(int *)lParam = editor->nZoomDenominator;
3880 return TRUE;
3881 case EM_SETZOOM:
3882 return ME_SetZoom(editor, wParam, lParam);
3883 case EM_CHARFROMPOS:
3884 {
3885 ME_Cursor cursor;
3886 POINTL *pt = (POINTL *)lParam;
3887
3888 cursor_from_coords(editor, pt->x, pt->y, &cursor);
3889 return ME_GetCursorOfs(&cursor);
3890 }
3891 case EM_POSFROMCHAR:
3892 {
3893 ME_Cursor cursor;
3894 int nCharOfs, nLength;
3895 POINTL pt = {0,0};
3896
3897 nCharOfs = wParam;
3898 /* detect which API version we're dealing with */
3899 if (wParam >= 0x40000)
3900 nCharOfs = lParam;
3901 nLength = ME_GetTextLength(editor);
3902 nCharOfs = min(nCharOfs, nLength);
3903 nCharOfs = max(nCharOfs, 0);
3904
3905 cursor_from_char_ofs( editor, nCharOfs, &cursor );
3906 pt.y = cursor.run->pt.y;
3907 pt.x = cursor.run->pt.x +
3908 ME_PointFromChar( editor, cursor.run, cursor.nOffset, TRUE );
3909 pt.y += cursor.para->pt.y + editor->rcFormat.top;
3910 pt.x += editor->rcFormat.left;
3911
3912 pt.x -= editor->horz_si.nPos;
3913 pt.y -= editor->vert_si.nPos;
3914
3915 if (wParam >= 0x40000) *(POINTL *)wParam = pt;
3916
3917 return (wParam >= 0x40000) ? 0 : MAKELONG( pt.x, pt.y );
3918 }
3919 case WM_LBUTTONDBLCLK:
3920 case WM_LBUTTONDOWN:
3921 {
3922 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
3923 ITextHost_TxSetFocus(editor->texthost);
3924 ME_LButtonDown(editor, (short)LOWORD(lParam), (short)HIWORD(lParam),
3925 ME_CalculateClickCount(editor, msg, wParam, lParam));
3926 ITextHost_TxSetCapture(editor->texthost, TRUE);
3927 editor->bMouseCaptured = TRUE;
3928 link_notify( editor, msg, wParam, lParam );
3929 break;
3930 }
3931 case WM_MOUSEMOVE:
3932 if (editor->bMouseCaptured)
3933 ME_MouseMove(editor, (short)LOWORD(lParam), (short)HIWORD(lParam));
3934 else
3935 link_notify( editor, msg, wParam, lParam );
3936 break;
3937 case WM_LBUTTONUP:
3938 if (editor->bMouseCaptured) {
3939 ITextHost_TxSetCapture(editor->texthost, FALSE);
3940 editor->bMouseCaptured = FALSE;
3941 }
3942 if (editor->nSelectionType == stDocument)
3943 editor->nSelectionType = stPosition;
3944 else
3945 {
3946 link_notify( editor, msg, wParam, lParam );
3947 }
3948 break;
3949 case WM_RBUTTONUP:
3950 case WM_RBUTTONDOWN:
3951 case WM_RBUTTONDBLCLK:
3952 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
3953 link_notify( editor, msg, wParam, lParam );
3954 goto do_default;
3955 case WM_CONTEXTMENU:
3956 if (!ME_ShowContextMenu(editor, (short)LOWORD(lParam), (short)HIWORD(lParam)))
3957 goto do_default;
3958 break;
3959 case WM_SETFOCUS:
3960 editor->bHaveFocus = TRUE;
3961 create_caret(editor);
3962 update_caret(editor);
3963 ITextHost_TxNotify( editor->texthost, EN_SETFOCUS, NULL );
3964 if (!editor->bHideSelection && (editor->props & TXTBIT_HIDESELECTION))
3965 ME_InvalidateSelection( editor );
3966 return 0;
3967 case WM_KILLFOCUS:
3968 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
3969 editor->bHaveFocus = FALSE;
3970 editor->wheel_remain = 0;
3971 hide_caret(editor);
3972 DestroyCaret();
3973 ITextHost_TxNotify( editor->texthost, EN_KILLFOCUS, NULL );
3974 if (!editor->bHideSelection && (editor->props & TXTBIT_HIDESELECTION))
3975 ME_InvalidateSelection( editor );
3976 return 0;
3977 case WM_COMMAND:
3978 TRACE("editor wnd command = %d\n", LOWORD(wParam));
3979 return 0;
3980 case WM_KEYDOWN:
3981 if (ME_KeyDown(editor, LOWORD(wParam)))
3982 return 0;
3983 goto do_default;
3984 case WM_CHAR:
3985 return handle_wm_char( editor, wParam, lParam );
3986 case WM_UNICHAR:
3987 if (wParam == UNICODE_NOCHAR) return TRUE;
3988 if (wParam <= 0x000fffff)
3989 {
3990 if (wParam > 0xffff) /* convert to surrogates */
3991 {
3992 wParam -= 0x10000;
3993 handle_wm_char( editor, (wParam >> 10) + 0xd800, 0 );
3994 handle_wm_char( editor, (wParam & 0x03ff) + 0xdc00, 0 );
3995 }
3996 else
3997 handle_wm_char( editor, wParam, 0 );
3998 }
3999 return 0;
4000 case EM_STOPGROUPTYPING:
4001 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4002 return 0;
4003 case WM_HSCROLL:
4004 {
4005 const int scrollUnit = 7;
4006
4007 switch(LOWORD(wParam))
4008 {
4009 case SB_LEFT:
4010 scroll_abs( editor, 0, 0, TRUE );
4011 break;
4012 case SB_RIGHT:
4013 scroll_abs( editor, editor->horz_si.nMax - (int)editor->horz_si.nPage,
4014 editor->vert_si.nMax - (int)editor->vert_si.nPage, TRUE );
4015 break;
4016 case SB_LINELEFT:
4017 ME_ScrollLeft(editor, scrollUnit);
4018 break;
4019 case SB_LINERIGHT:
4020 ME_ScrollRight(editor, scrollUnit);
4021 break;
4022 case SB_PAGELEFT:
4023 ME_ScrollLeft(editor, editor->sizeWindow.cx);
4024 break;
4025 case SB_PAGERIGHT:
4026 ME_ScrollRight(editor, editor->sizeWindow.cx);
4027 break;
4028 case SB_THUMBTRACK:
4029 case SB_THUMBPOSITION:
4030 {
4031 int pos = HIWORD(wParam);
4032 if (editor->horz_si.nMax > 0xffff)
4033 pos = MulDiv(pos, editor->horz_si.nMax, 0xffff);
4034 scroll_h_abs( editor, pos, FALSE );
4035 break;
4036 }
4037 }
4038 break;
4039 }
4040 case EM_SCROLL: /* fall through */
4041 case WM_VSCROLL:
4042 {
4043 int origNPos;
4044 int lineHeight = get_default_line_height( editor );
4045
4046 origNPos = editor->vert_si.nPos;
4047
4048 switch(LOWORD(wParam))
4049 {
4050 case SB_TOP:
4051 scroll_abs( editor, 0, 0, TRUE );
4052 break;
4053 case SB_BOTTOM:
4054 scroll_abs( editor, editor->horz_si.nMax - (int)editor->horz_si.nPage,
4055 editor->vert_si.nMax - (int)editor->vert_si.nPage, TRUE );
4056 break;
4057 case SB_LINEUP:
4058 ME_ScrollUp(editor,lineHeight);
4059 break;
4060 case SB_LINEDOWN:
4061 ME_ScrollDown(editor,lineHeight);
4062 break;
4063 case SB_PAGEUP:
4064 ME_ScrollUp(editor,editor->sizeWindow.cy);
4065 break;
4066 case SB_PAGEDOWN:
4067 ME_ScrollDown(editor,editor->sizeWindow.cy);
4068 break;
4069 case SB_THUMBTRACK:
4070 case SB_THUMBPOSITION:
4071 {
4072 int pos = HIWORD(wParam);
4073 if (editor->vert_si.nMax > 0xffff)
4074 pos = MulDiv(pos, editor->vert_si.nMax, 0xffff);
4075 scroll_v_abs( editor, pos, FALSE );
4076 break;
4077 }
4078 }
4079 if (msg == EM_SCROLL)
4080 return 0x00010000 | (((editor->vert_si.nPos - origNPos)/lineHeight) & 0xffff);
4081 break;
4082 }
4083 case WM_MOUSEWHEEL:
4084 {
4085 int delta = GET_WHEEL_DELTA_WPARAM( wParam );
4086 BOOL ctrl_is_down = GetKeyState( VK_CONTROL ) & 0x8000;
4087
4088 /* if scrolling changes direction, ignore left overs */
4089 if ((delta < 0 && editor->wheel_remain < 0) ||
4090 (delta > 0 && editor->wheel_remain > 0))
4091 editor->wheel_remain += delta;
4092 else
4093 editor->wheel_remain = delta;
4094
4095 if (editor->wheel_remain)
4096 {
4097 if (ctrl_is_down) {
4098 int numerator;
4099 if (!editor->nZoomNumerator || !editor->nZoomDenominator)
4100 {
4101 numerator = 100;
4102 } else {
4103 numerator = editor->nZoomNumerator * 100 / editor->nZoomDenominator;
4104 }
4105 numerator += calc_wheel_change( &editor->wheel_remain, 10 );
4106 if (numerator >= 10 && numerator <= 500)
4107 ME_SetZoom(editor, numerator, 100);
4108 } else {
4109 UINT max_lines = 3;
4110 int lines = 0;
4111
4112 SystemParametersInfoW( SPI_GETWHEELSCROLLLINES, 0, &max_lines, 0 );
4113 if (max_lines)
4114 lines = calc_wheel_change( &editor->wheel_remain, (int)max_lines );
4115 if (lines)
4116 ME_ScrollDown( editor, -lines * get_default_line_height( editor ) );
4117 }
4118 }
4119 break;
4120 }
4121 case EM_REQUESTRESIZE:
4122 ME_SendRequestResize(editor, TRUE);
4123 return 0;
4124#ifndef __REACTOS__
4125 /* IME messages to make richedit controls IME aware */
4126#endif
4127 case WM_IME_SETCONTEXT:
4128#ifdef __REACTOS__
4129 {
4130 if (FALSE) /* FIXME: Condition */
4131 lParam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
4132
4133 if (wParam)
4134 {
4135 HIMC hIMC = ImmGetContext(editor->hWnd);
4136 LPINPUTCONTEXTDX pIC = (LPINPUTCONTEXTDX)ImmLockIMC(hIMC);
4137 if (pIC)
4138 {
4139 pIC->dwUIFlags &= ~0x40000;
4140 ImmUnlockIMC(hIMC);
4141 }
4142 if (FALSE) /* FIXME: Condition */
4143 ImmNotifyIME(hIMC, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
4144 ImmReleaseContext(editor->hWnd, hIMC);
4145 }
4146
4147 return DefWindowProcW(editor->hWnd, msg, wParam, lParam);
4148 }
4149#endif
4150 case WM_IME_CONTROL:
4151#ifdef __REACTOS__
4152 return DefWindowProcW(editor->hWnd, msg, wParam, lParam);
4153#endif
4154 case WM_IME_SELECT:
4155#ifdef __REACTOS__
4156 return DefWindowProcW(editor->hWnd, msg, wParam, lParam);
4157#endif
4158 case WM_IME_COMPOSITIONFULL:
4159 return 0;
4160 case WM_IME_STARTCOMPOSITION:
4161 {
4162#ifdef __REACTOS__
4163 return DefWindowProcW(editor->hWnd, msg, wParam, lParam);
4164#else
4165 editor->imeStartIndex=ME_GetCursorOfs(&editor->pCursors[0]);
4166 ME_DeleteSelection(editor);
4167 ME_CommitUndo(editor);
4168 ME_UpdateRepaint(editor, FALSE);
4169#endif
4170 return 0;
4171 }
4172 case WM_IME_COMPOSITION:
4173 {
4174 HIMC hIMC;
4175
4176 ME_Style *style = style_get_insert_style( editor, editor->pCursors );
4177 hIMC = ITextHost_TxImmGetContext(editor->texthost);
4178 ME_DeleteSelection(editor);
4179 ME_SaveTempStyle(editor, style);
4180 if (lParam & (GCS_RESULTSTR|GCS_COMPSTR))
4181 {
4182 LPWSTR lpCompStr = NULL;
4183 DWORD dwBufLen;
4184 DWORD dwIndex = lParam & GCS_RESULTSTR;
4185 if (!dwIndex)
4186 dwIndex = GCS_COMPSTR;
4187
4188 dwBufLen = ImmGetCompositionStringW(hIMC, dwIndex, NULL, 0);
4189 lpCompStr = malloc(dwBufLen + sizeof(WCHAR));
4190 ImmGetCompositionStringW(hIMC, dwIndex, lpCompStr, dwBufLen);
4191 lpCompStr[dwBufLen/sizeof(WCHAR)] = 0;
4192#ifndef __REACTOS__
4193 ME_InsertTextFromCursor(editor,0,lpCompStr,dwBufLen/sizeof(WCHAR),style);
4194#endif
4195 free(lpCompStr);
4196
4197#ifndef __REACTOS__
4198 if (dwIndex == GCS_COMPSTR)
4199 set_selection_cursors(editor,editor->imeStartIndex,
4200 editor->imeStartIndex + dwBufLen/sizeof(WCHAR));
4201 else
4202 editor->imeStartIndex = ME_GetCursorOfs(&editor->pCursors[0]);
4203#endif
4204 }
4205 ITextHost_TxImmReleaseContext(editor->texthost, hIMC);
4206 ME_ReleaseStyle(style);
4207 ME_CommitUndo(editor);
4208 ME_UpdateRepaint(editor, FALSE);
4209#ifdef __REACTOS__
4210 return DefWindowProcW(editor->hWnd, msg, wParam, lParam);
4211#else
4212 return 0;
4213#endif
4214 }
4215 case WM_IME_ENDCOMPOSITION:
4216 {
4217#ifdef __REACTOS__
4218 return DefWindowProcW(editor->hWnd, msg, wParam, lParam);
4219#else
4220 ME_DeleteSelection(editor);
4221 editor->imeStartIndex=-1;
4222 return 0;
4223#endif
4224 }
4225 case EM_GETOLEINTERFACE:
4226 IRichEditOle_AddRef( editor->richole );
4227 *(IRichEditOle **)lParam = editor->richole;
4228 return 1;
4229
4230 case EM_SETOLECALLBACK:
4231 if(editor->lpOleCallback)
4232 IRichEditOleCallback_Release(editor->lpOleCallback);
4233 editor->lpOleCallback = (IRichEditOleCallback*)lParam;
4234 if(editor->lpOleCallback)
4235 IRichEditOleCallback_AddRef(editor->lpOleCallback);
4236 return TRUE;
4237 case EM_GETWORDBREAKPROC:
4238 return (LRESULT)editor->pfnWordBreak;
4239 case EM_SETWORDBREAKPROC:
4240 {
4241 EDITWORDBREAKPROCW pfnOld = editor->pfnWordBreak;
4242
4243 editor->pfnWordBreak = (EDITWORDBREAKPROCW)lParam;
4244 return (LRESULT)pfnOld;
4245 }
4246 case EM_GETTEXTMODE:
4247 return editor->mode;
4248 case EM_SETTEXTMODE:
4249 {
4250 int mask = 0;
4251 int changes = 0;
4252
4253 if (ME_GetTextLength(editor) ||
4254 !list_empty( &editor->undo_stack ) || !list_empty( &editor->redo_stack ))
4255 return E_UNEXPECTED;
4256
4257 /* Check for mutually exclusive flags in adjacent bits of wParam */
4258 if ((wParam & (TM_RICHTEXT | TM_MULTILEVELUNDO | TM_MULTICODEPAGE)) &
4259 (wParam & (TM_PLAINTEXT | TM_SINGLELEVELUNDO | TM_SINGLECODEPAGE)) << 1)
4260 return E_INVALIDARG;
4261
4262 if (wParam & (TM_RICHTEXT | TM_PLAINTEXT))
4263 {
4264 mask |= TM_RICHTEXT | TM_PLAINTEXT;
4265 changes |= wParam & (TM_RICHTEXT | TM_PLAINTEXT);
4266 if (wParam & TM_PLAINTEXT) {
4267 /* Clear selection since it should be possible to select the
4268 * end of text run for rich text */
4269 ME_InvalidateSelection(editor);
4270 ME_SetCursorToStart(editor, &editor->pCursors[0]);
4271 editor->pCursors[1] = editor->pCursors[0];
4272 /* plain text can only have the default style. */
4273 ME_ClearTempStyle(editor);
4274 ME_AddRefStyle(editor->pBuffer->pDefaultStyle);
4275 ME_ReleaseStyle( editor->pCursors[0].run->style );
4276 editor->pCursors[0].run->style = editor->pBuffer->pDefaultStyle;
4277 }
4278 }
4279 /* FIXME: Currently no support for undo level and code page options */
4280 editor->mode = (editor->mode & ~mask) | changes;
4281 return 0;
4282 }
4283 case EM_SETTARGETDEVICE:
4284 if (wParam == 0)
4285 {
4286 BOOL new = (lParam == 0 && (editor->props & TXTBIT_MULTILINE));
4287 if (editor->nAvailWidth || editor->bWordWrap != new)
4288 {
4289 editor->bWordWrap = new;
4290 editor->nAvailWidth = 0; /* wrap to client area */
4291 ME_RewrapRepaint(editor);
4292 }
4293 } else {
4294 int width = max(0, lParam);
4295 if ((editor->props & TXTBIT_MULTILINE) &&
4296 (!editor->bWordWrap || editor->nAvailWidth != width))
4297 {
4298 editor->nAvailWidth = width;
4299 editor->bWordWrap = TRUE;
4300 ME_RewrapRepaint(editor);
4301 }
4302 FIXME("EM_SETTARGETDEVICE doesn't use non-NULL target devices\n");
4303 }
4304 return TRUE;
4305 default:
4306 do_default:
4307 *phresult = S_FALSE;
4308 break;
4309 }
4310 return 0L;
4311}
4312
4313/* Fill buffer with srcChars unicode characters from the start cursor.
4314 *
4315 * buffer: destination buffer
4316 * buflen: length of buffer in characters excluding the NULL terminator.
4317 * start: start of editor text to copy into buffer.
4318 * srcChars: Number of characters to use from the editor text.
4319 * bCRLF: if true, replaces all end of lines with \r\n pairs.
4320 *
4321 * returns the number of characters written excluding the NULL terminator.
4322 *
4323 * The written text is always NULL terminated.
4324 */
4325int ME_GetTextW(ME_TextEditor *editor, WCHAR *buffer, int buflen,
4326 const ME_Cursor *start, int srcChars, BOOL bCRLF,
4327 BOOL bEOP)
4328{
4329 ME_Run *run, *next_run;
4330 const WCHAR *pStart = buffer;
4331 const WCHAR *str;
4332 int nLen;
4333
4334 /* bCRLF flag is only honored in 2.0 and up. 1.0 must always return text verbatim */
4335 if (editor->bEmulateVersion10) bCRLF = FALSE;
4336
4337 run = start->run;
4338 next_run = run_next_all_paras( run );
4339
4340 nLen = run->len - start->nOffset;
4341 str = get_text( run, start->nOffset );
4342
4343 while (srcChars && buflen && next_run)
4344 {
4345 if (bCRLF && run->nFlags & MERF_ENDPARA && ~run->nFlags & MERF_ENDCELL)
4346 {
4347 if (buflen == 1) break;
4348 /* FIXME: native fails to reduce srcChars here for WM_GETTEXT or
4349 * EM_GETTEXTEX, however, this is done for copying text which
4350 * also uses this function. */
4351 srcChars -= min(nLen, srcChars);
4352 nLen = 2;
4353 str = L"\r\n";
4354 }
4355 else
4356 {
4357 nLen = min(nLen, srcChars);
4358 srcChars -= nLen;
4359 }
4360
4361 nLen = min(nLen, buflen);
4362 buflen -= nLen;
4363
4364 CopyMemory(buffer, str, sizeof(WCHAR) * nLen);
4365
4366 buffer += nLen;
4367
4368 run = next_run;
4369 next_run = run_next_all_paras( run );
4370
4371 nLen = run->len;
4372 str = get_text( run, 0 );
4373 }
4374 /* append '\r' to the last paragraph. */
4375 if (run == para_end_run( para_prev( editor_end_para( editor ) ) ) && bEOP && buflen)
4376 {
4377 *buffer = '\r';
4378 buffer ++;
4379 }
4380 *buffer = 0;
4381 return buffer - pStart;
4382}
4383
4384static int __cdecl wchar_comp( const void *key, const void *elem )
4385{
4386 return *(const WCHAR *)key - *(const WCHAR *)elem;
4387}
4388
4389/* neutral characters end the url if the next non-neutral character is a space character,
4390 otherwise they are included in the url. */
4391static BOOL isurlneutral( WCHAR c )
4392{
4393 /* NB this list is sorted */
4394 static const WCHAR neutral_chars[] = L"!\"'(),-.:;<>?[]{}";
4395
4396 /* Some shortcuts */
4397 if (isalnum( c )) return FALSE;
4398 if (c > L'}') return FALSE;
4399
4400 return !!bsearch( &c, neutral_chars, ARRAY_SIZE( neutral_chars ) - 1, sizeof(c), wchar_comp );
4401}
4402
4403/**
4404 * This proc takes a selection, and scans it forward in order to select the span
4405 * of a possible URL candidate. A possible URL candidate must start with isalnum
4406 * or one of the following special characters: *|/\+%#@ and must consist entirely
4407 * of the characters allowed to start the URL, plus : (colon) which may occur
4408 * at most once, and not at either end.
4409 */
4410static BOOL ME_FindNextURLCandidate(ME_TextEditor *editor,
4411 const ME_Cursor *start,
4412 int nChars,
4413 ME_Cursor *candidate_min,
4414 ME_Cursor *candidate_max)
4415{
4416 ME_Cursor cursor = *start, neutral_end, space_end;
4417 BOOL candidateStarted = FALSE, quoted = FALSE;
4418 WCHAR c;
4419
4420 while (nChars > 0)
4421 {
4422 WCHAR *str = get_text( cursor.run, 0 );
4423 int run_len = cursor.run->len;
4424
4425 nChars -= run_len - cursor.nOffset;
4426
4427 /* Find start of candidate */
4428 if (!candidateStarted)
4429 {
4430 while (cursor.nOffset < run_len)
4431 {
4432 c = str[cursor.nOffset];
4433 if (!iswspace( c ) && !isurlneutral( c ))
4434 {
4435 *candidate_min = cursor;
4436 candidateStarted = TRUE;
4437 neutral_end.para = NULL;
4438 space_end.para = NULL;
4439 cursor.nOffset++;
4440 break;
4441 }
4442 quoted = (c == '<');
4443 cursor.nOffset++;
4444 }
4445 }
4446
4447 /* Find end of candidate */
4448 if (candidateStarted)
4449 {
4450 while (cursor.nOffset < run_len)
4451 {
4452 c = str[cursor.nOffset];
4453 if (iswspace( c ))
4454 {
4455 if (quoted && c != '\r')
4456 {
4457 if (!space_end.para)
4458 {
4459 if (neutral_end.para)
4460 space_end = neutral_end;
4461 else
4462 space_end = cursor;
4463 }
4464 }
4465 else
4466 goto done;
4467 }
4468 else if (isurlneutral( c ))
4469 {
4470 if (quoted && c == '>')
4471 {
4472 neutral_end.para = NULL;
4473 space_end.para = NULL;
4474 goto done;
4475 }
4476 if (!neutral_end.para)
4477 neutral_end = cursor;
4478 }
4479 else
4480 neutral_end.para = NULL;
4481
4482 cursor.nOffset++;
4483 }
4484 }
4485
4486 cursor.nOffset = 0;
4487 if (!cursor_next_run( &cursor, TRUE ))
4488 goto done;
4489 }
4490
4491done:
4492 if (candidateStarted)
4493 {
4494 if (space_end.para)
4495 *candidate_max = space_end;
4496 else if (neutral_end.para)
4497 *candidate_max = neutral_end;
4498 else
4499 *candidate_max = cursor;
4500 return TRUE;
4501 }
4502 *candidate_max = *candidate_min = cursor;
4503 return FALSE;
4504}
4505
4506/**
4507 * This proc evaluates the selection and returns TRUE if it can be considered an URL
4508 */
4509static BOOL ME_IsCandidateAnURL(ME_TextEditor *editor, const ME_Cursor *start, int nChars)
4510{
4511#define MAX_PREFIX_LEN 9
4512#define X(str) str, ARRAY_SIZE(str) - 1
4513 struct prefix_s {
4514#ifdef __REACTOS__
4515 const WCHAR text[MAX_PREFIX_LEN + 1];
4516#else
4517 const WCHAR text[MAX_PREFIX_LEN];
4518#endif
4519 int length;
4520 }prefixes[] = {
4521 {X(L"prospero:")},
4522 {X(L"telnet:")},
4523 {X(L"gopher:")},
4524 {X(L"mailto:")},
4525 {X(L"https:")},
4526 {X(L"file:")},
4527 {X(L"news:")},
4528 {X(L"wais:")},
4529 {X(L"nntp:")},
4530 {X(L"http:")},
4531 {X(L"www.")},
4532 {X(L"ftp:")},
4533 };
4534#undef X
4535 WCHAR bufferW[MAX_PREFIX_LEN + 1];
4536 unsigned int i;
4537
4538 ME_GetTextW(editor, bufferW, MAX_PREFIX_LEN, start, nChars, FALSE, FALSE);
4539 for (i = 0; i < ARRAY_SIZE(prefixes); i++)
4540 {
4541 if (nChars < prefixes[i].length) continue;
4542 if (!memcmp(prefixes[i].text, bufferW, prefixes[i].length * sizeof(WCHAR)))
4543 return TRUE;
4544 }
4545 return FALSE;
4546#undef MAX_PREFIX_LEN
4547}
4548
4549/**
4550 * This proc walks through the indicated selection and evaluates whether each
4551 * section identified by ME_FindNextURLCandidate and in-between sections have
4552 * their proper CFE_LINK attributes set or unset. If the CFE_LINK attribute is
4553 * not what it is supposed to be, this proc sets or unsets it as appropriate.
4554 *
4555 * Since this function can cause runs to be split, do not depend on the value
4556 * of the start cursor at the end of the function.
4557 *
4558 * nChars may be set to INT_MAX to update to the end of the text.
4559 *
4560 * Returns TRUE if at least one section was modified.
4561 */
4562static BOOL ME_UpdateLinkAttribute(ME_TextEditor *editor, ME_Cursor *start, int nChars)
4563{
4564 BOOL modified = FALSE;
4565 ME_Cursor startCur = *start;
4566
4567 if (!editor->AutoURLDetect_bEnable) return FALSE;
4568
4569 do
4570 {
4571 CHARFORMAT2W link;
4572 ME_Cursor candidateStart, candidateEnd;
4573
4574 if (ME_FindNextURLCandidate(editor, &startCur, nChars,
4575 &candidateStart, &candidateEnd))
4576 {
4577 /* Section before candidate is not an URL */
4578 int cMin = ME_GetCursorOfs(&candidateStart);
4579 int cMax = ME_GetCursorOfs(&candidateEnd);
4580
4581 if (!ME_IsCandidateAnURL(editor, &candidateStart, cMax - cMin))
4582 candidateStart = candidateEnd;
4583 nChars -= cMax - ME_GetCursorOfs(&startCur);
4584 }
4585 else
4586 {
4587 /* No more candidates until end of selection */
4588 nChars = 0;
4589 }
4590
4591 if (startCur.run != candidateStart.run ||
4592 startCur.nOffset != candidateStart.nOffset)
4593 {
4594 /* CFE_LINK effect should be consistently unset */
4595 link.cbSize = sizeof(link);
4596 ME_GetCharFormat(editor, &startCur, &candidateStart, &link);
4597 if (!(link.dwMask & CFM_LINK) || (link.dwEffects & CFE_LINK))
4598 {
4599 /* CFE_LINK must be unset from this range */
4600 memset(&link, 0, sizeof(CHARFORMAT2W));
4601 link.cbSize = sizeof(link);
4602 link.dwMask = CFM_LINK;
4603 link.dwEffects = 0;
4604 ME_SetCharFormat(editor, &startCur, &candidateStart, &link);
4605 /* Update candidateEnd since setting character formats may split
4606 * runs, which can cause a cursor to be at an invalid offset within
4607 * a split run. */
4608 while (candidateEnd.nOffset >= candidateEnd.run->len)
4609 {
4610 candidateEnd.nOffset -= candidateEnd.run->len;
4611 candidateEnd.run = run_next_all_paras( candidateEnd.run );
4612 }
4613 modified = TRUE;
4614 }
4615 }
4616 if (candidateStart.run != candidateEnd.run ||
4617 candidateStart.nOffset != candidateEnd.nOffset)
4618 {
4619 /* CFE_LINK effect should be consistently set */
4620 link.cbSize = sizeof(link);
4621 ME_GetCharFormat(editor, &candidateStart, &candidateEnd, &link);
4622 if (!(link.dwMask & CFM_LINK) || !(link.dwEffects & CFE_LINK))
4623 {
4624 /* CFE_LINK must be set on this range */
4625 memset(&link, 0, sizeof(CHARFORMAT2W));
4626 link.cbSize = sizeof(link);
4627 link.dwMask = CFM_LINK;
4628 link.dwEffects = CFE_LINK;
4629 ME_SetCharFormat(editor, &candidateStart, &candidateEnd, &link);
4630 modified = TRUE;
4631 }
4632 }
4633 startCur = candidateEnd;
4634 } while (nChars > 0);
4635 return modified;
4636}