[RICHED20] Sync with Wine Staging 3.9. CORE-14656
[reactos.git] / dll / win32 / riched20 / editor.c
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 #define NONAMELESSUNION
228
229 #include "editor.h"
230 #include "commdlg.h"
231 #include "winreg.h"
232 #define NO_SHLWAPI_STREAM
233 #include "shlwapi.h"
234 #include "rtf.h"
235 #include "imm.h"
236 #include "res.h"
237
238 #ifdef __REACTOS__
239 #include <reactos/undocuser.h>
240 #endif
241
242 #define STACK_SIZE_DEFAULT 100
243 #define STACK_SIZE_MAX 1000
244
245 #define TEXT_LIMIT_DEFAULT 32767
246
247 WINE_DEFAULT_DEBUG_CHANNEL(richedit);
248
249 static BOOL ME_RegisterEditorClass(HINSTANCE);
250 static BOOL ME_UpdateLinkAttribute(ME_TextEditor *editor, ME_Cursor *start, int nChars);
251
252 static const WCHAR REListBox20W[] = {'R','E','L','i','s','t','B','o','x','2','0','W', 0};
253 static const WCHAR REComboBox20W[] = {'R','E','C','o','m','b','o','B','o','x','2','0','W', 0};
254 static HCURSOR hLeft;
255
256 BOOL me_debug = FALSE;
257 HANDLE me_heap = NULL;
258
259 static BOOL ME_ListBoxRegistered = FALSE;
260 static BOOL ME_ComboBoxRegistered = FALSE;
261
262 static inline BOOL is_version_nt(void)
263 {
264 return !(GetVersion() & 0x80000000);
265 }
266
267 static ME_TextBuffer *ME_MakeText(void) {
268 ME_TextBuffer *buf = heap_alloc(sizeof(*buf));
269 ME_DisplayItem *p1 = ME_MakeDI(diTextStart);
270 ME_DisplayItem *p2 = ME_MakeDI(diTextEnd);
271
272 p1->prev = NULL;
273 p1->next = p2;
274 p2->prev = p1;
275 p2->next = NULL;
276 p1->member.para.next_para = p2;
277 p2->member.para.prev_para = p1;
278 p2->member.para.nCharOfs = 0;
279
280 buf->pFirst = p1;
281 buf->pLast = p2;
282 buf->pCharStyle = NULL;
283
284 return buf;
285 }
286
287
288 static LRESULT ME_StreamInText(ME_TextEditor *editor, DWORD dwFormat, ME_InStream *stream, ME_Style *style)
289 {
290 WCHAR *pText;
291 LRESULT total_bytes_read = 0;
292 BOOL is_read = FALSE;
293 DWORD cp = CP_ACP, copy = 0;
294 char conv_buf[4 + STREAMIN_BUFFER_SIZE]; /* up to 4 additional UTF-8 bytes */
295
296 static const char bom_utf8[] = {0xEF, 0xBB, 0xBF};
297
298 TRACE("%08x %p\n", dwFormat, stream);
299
300 do {
301 LONG nWideChars = 0;
302 WCHAR wszText[STREAMIN_BUFFER_SIZE+1];
303
304 if (!stream->dwSize)
305 {
306 ME_StreamInFill(stream);
307 if (stream->editstream->dwError)
308 break;
309 if (!stream->dwSize)
310 break;
311 total_bytes_read += stream->dwSize;
312 }
313
314 if (!(dwFormat & SF_UNICODE))
315 {
316 char * buf = stream->buffer;
317 DWORD size = stream->dwSize, end;
318
319 if (!is_read)
320 {
321 is_read = TRUE;
322 if (stream->dwSize >= 3 && !memcmp(stream->buffer, bom_utf8, 3))
323 {
324 cp = CP_UTF8;
325 buf += 3;
326 size -= 3;
327 }
328 }
329
330 if (cp == CP_UTF8)
331 {
332 if (copy)
333 {
334 memcpy(conv_buf + copy, buf, size);
335 buf = conv_buf;
336 size += copy;
337 }
338 end = size;
339 while ((buf[end-1] & 0xC0) == 0x80)
340 {
341 --end;
342 --total_bytes_read; /* strange, but seems to match windows */
343 }
344 if (buf[end-1] & 0x80)
345 {
346 DWORD need = 0;
347 if ((buf[end-1] & 0xE0) == 0xC0)
348 need = 1;
349 if ((buf[end-1] & 0xF0) == 0xE0)
350 need = 2;
351 if ((buf[end-1] & 0xF8) == 0xF0)
352 need = 3;
353
354 if (size - end >= need)
355 {
356 /* we have enough bytes for this sequence */
357 end = size;
358 }
359 else
360 {
361 /* need more bytes, so don't transcode this sequence */
362 --end;
363 }
364 }
365 }
366 else
367 end = size;
368
369 nWideChars = MultiByteToWideChar(cp, 0, buf, end, wszText, STREAMIN_BUFFER_SIZE);
370 pText = wszText;
371
372 if (cp == CP_UTF8)
373 {
374 if (end != size)
375 {
376 memcpy(conv_buf, buf + end, size - end);
377 copy = size - end;
378 }
379 }
380 }
381 else
382 {
383 nWideChars = stream->dwSize >> 1;
384 pText = (WCHAR *)stream->buffer;
385 }
386
387 ME_InsertTextFromCursor(editor, 0, pText, nWideChars, style);
388 if (stream->dwSize == 0)
389 break;
390 stream->dwSize = 0;
391 } while(1);
392 return total_bytes_read;
393 }
394
395 static void ME_ApplyBorderProperties(RTF_Info *info,
396 ME_BorderRect *borderRect,
397 RTFBorder *borderDef)
398 {
399 int i, colorNum;
400 ME_Border *pBorders[] = {&borderRect->top,
401 &borderRect->left,
402 &borderRect->bottom,
403 &borderRect->right};
404 for (i = 0; i < 4; i++)
405 {
406 RTFColor *colorDef = info->colorList;
407 pBorders[i]->width = borderDef[i].width;
408 colorNum = borderDef[i].color;
409 while (colorDef && colorDef->rtfCNum != colorNum)
410 colorDef = colorDef->rtfNextColor;
411 if (colorDef)
412 pBorders[i]->colorRef = RGB(
413 colorDef->rtfCRed >= 0 ? colorDef->rtfCRed : 0,
414 colorDef->rtfCGreen >= 0 ? colorDef->rtfCGreen : 0,
415 colorDef->rtfCBlue >= 0 ? colorDef->rtfCBlue : 0);
416 else
417 pBorders[i]->colorRef = RGB(0, 0, 0);
418 }
419 }
420
421 void ME_RTFCharAttrHook(RTF_Info *info)
422 {
423 CHARFORMAT2W fmt;
424 fmt.cbSize = sizeof(fmt);
425 fmt.dwMask = 0;
426 fmt.dwEffects = 0;
427
428 switch(info->rtfMinor)
429 {
430 case rtfPlain:
431 /* FIXME add more flags once they're implemented */
432 fmt.dwMask = CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE | CFM_UNDERLINETYPE | CFM_STRIKEOUT |
433 CFM_COLOR | CFM_BACKCOLOR | CFM_SIZE | CFM_WEIGHT;
434 fmt.dwEffects = CFE_AUTOCOLOR | CFE_AUTOBACKCOLOR;
435 fmt.yHeight = 12*20; /* 12pt */
436 fmt.wWeight = FW_NORMAL;
437 fmt.bUnderlineType = CFU_UNDERLINE;
438 break;
439 case rtfBold:
440 fmt.dwMask = CFM_BOLD | CFM_WEIGHT;
441 fmt.dwEffects = info->rtfParam ? CFE_BOLD : 0;
442 fmt.wWeight = info->rtfParam ? FW_BOLD : FW_NORMAL;
443 break;
444 case rtfItalic:
445 fmt.dwMask = CFM_ITALIC;
446 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
447 break;
448 case rtfUnderline:
449 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
450 fmt.bUnderlineType = CFU_UNDERLINE;
451 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
452 break;
453 case rtfDotUnderline:
454 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
455 fmt.bUnderlineType = CFU_UNDERLINEDOTTED;
456 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
457 break;
458 case rtfDbUnderline:
459 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
460 fmt.bUnderlineType = CFU_UNDERLINEDOUBLE;
461 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
462 break;
463 case rtfWordUnderline:
464 fmt.dwMask = CFM_UNDERLINETYPE | CFM_UNDERLINE;
465 fmt.bUnderlineType = CFU_UNDERLINEWORD;
466 fmt.dwEffects = info->rtfParam ? CFE_UNDERLINE : 0;
467 break;
468 case rtfNoUnderline:
469 fmt.dwMask = CFM_UNDERLINE;
470 fmt.dwEffects = 0;
471 break;
472 case rtfStrikeThru:
473 fmt.dwMask = CFM_STRIKEOUT;
474 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
475 break;
476 case rtfSubScript:
477 case rtfSuperScript:
478 case rtfSubScrShrink:
479 case rtfSuperScrShrink:
480 case rtfNoSuperSub:
481 fmt.dwMask = CFM_SUBSCRIPT|CFM_SUPERSCRIPT;
482 if (info->rtfMinor == rtfSubScrShrink) fmt.dwEffects = CFE_SUBSCRIPT;
483 if (info->rtfMinor == rtfSuperScrShrink) fmt.dwEffects = CFE_SUPERSCRIPT;
484 if (info->rtfMinor == rtfNoSuperSub) fmt.dwEffects = 0;
485 break;
486 case rtfInvisible:
487 fmt.dwMask = CFM_HIDDEN;
488 fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0;
489 break;
490 case rtfBackColor:
491 fmt.dwMask = CFM_BACKCOLOR;
492 fmt.dwEffects = 0;
493 if (info->rtfParam == 0)
494 fmt.dwEffects = CFE_AUTOBACKCOLOR;
495 else if (info->rtfParam != rtfNoParam)
496 {
497 RTFColor *c = RTFGetColor(info, info->rtfParam);
498 if (c && c->rtfCBlue >= 0)
499 fmt.crBackColor = (c->rtfCBlue<<16)|(c->rtfCGreen<<8)|(c->rtfCRed);
500 else
501 fmt.dwEffects = CFE_AUTOBACKCOLOR;
502 }
503 break;
504 case rtfForeColor:
505 fmt.dwMask = CFM_COLOR;
506 fmt.dwEffects = 0;
507 if (info->rtfParam == 0)
508 fmt.dwEffects = CFE_AUTOCOLOR;
509 else if (info->rtfParam != rtfNoParam)
510 {
511 RTFColor *c = RTFGetColor(info, info->rtfParam);
512 if (c && c->rtfCBlue >= 0)
513 fmt.crTextColor = (c->rtfCBlue<<16)|(c->rtfCGreen<<8)|(c->rtfCRed);
514 else {
515 fmt.dwEffects = CFE_AUTOCOLOR;
516 }
517 }
518 break;
519 case rtfFontNum:
520 if (info->rtfParam != rtfNoParam)
521 {
522 RTFFont *f = RTFGetFont(info, info->rtfParam);
523 if (f)
524 {
525 MultiByteToWideChar(CP_ACP, 0, f->rtfFName, -1, fmt.szFaceName, sizeof(fmt.szFaceName)/sizeof(WCHAR));
526 fmt.szFaceName[sizeof(fmt.szFaceName)/sizeof(WCHAR)-1] = '\0';
527 fmt.bCharSet = f->rtfFCharSet;
528 fmt.dwMask = CFM_FACE | CFM_CHARSET;
529 fmt.bPitchAndFamily = f->rtfFPitch | (f->rtfFFamily << 4);
530 }
531 }
532 break;
533 case rtfFontSize:
534 fmt.dwMask = CFM_SIZE;
535 if (info->rtfParam != rtfNoParam)
536 fmt.yHeight = info->rtfParam*10;
537 break;
538 }
539 if (fmt.dwMask) {
540 ME_Style *style2;
541 RTFFlushOutputBuffer(info);
542 /* FIXME too slow ? how come ? */
543 style2 = ME_ApplyStyle(info->editor, info->style, &fmt);
544 ME_ReleaseStyle(info->style);
545 info->style = style2;
546 info->styleChanged = TRUE;
547 }
548 }
549
550 /* FIXME this function doesn't get any information about context of the RTF tag, which is very bad,
551 the same tags mean different things in different contexts */
552 void ME_RTFParAttrHook(RTF_Info *info)
553 {
554 switch(info->rtfMinor)
555 {
556 case rtfParDef: /* restores default paragraph attributes */
557 if (!info->editor->bEmulateVersion10) /* v4.1 */
558 info->borderType = RTFBorderParaLeft;
559 else /* v1.0 - 3.0 */
560 info->borderType = RTFBorderParaTop;
561 info->fmt.dwMask = PFM_ALIGNMENT | PFM_BORDER | PFM_LINESPACING | PFM_TABSTOPS |
562 PFM_OFFSET | PFM_RIGHTINDENT | PFM_SPACEAFTER | PFM_SPACEBEFORE |
563 PFM_STARTINDENT | PFM_RTLPARA | PFM_NUMBERING | PFM_NUMBERINGSTART |
564 PFM_NUMBERINGSTYLE | PFM_NUMBERINGTAB;
565 /* TODO: shading */
566 info->fmt.wAlignment = PFA_LEFT;
567 info->fmt.cTabCount = 0;
568 info->fmt.dxOffset = info->fmt.dxStartIndent = info->fmt.dxRightIndent = 0;
569 info->fmt.wBorderWidth = info->fmt.wBorders = 0;
570 info->fmt.wBorderSpace = 0;
571 info->fmt.bLineSpacingRule = 0;
572 info->fmt.dySpaceBefore = info->fmt.dySpaceAfter = 0;
573 info->fmt.dyLineSpacing = 0;
574 info->fmt.wEffects &= ~PFE_RTLPARA;
575 info->fmt.wNumbering = 0;
576 info->fmt.wNumberingStart = 0;
577 info->fmt.wNumberingStyle = 0;
578 info->fmt.wNumberingTab = 0;
579
580 if (!info->editor->bEmulateVersion10) /* v4.1 */
581 {
582 if (info->tableDef && info->tableDef->tableRowStart &&
583 info->tableDef->tableRowStart->member.para.nFlags & MEPF_ROWEND)
584 {
585 ME_Cursor cursor;
586 ME_DisplayItem *para;
587 /* We are just after a table row. */
588 RTFFlushOutputBuffer(info);
589 cursor = info->editor->pCursors[0];
590 para = cursor.pPara;
591 if (para == info->tableDef->tableRowStart->member.para.next_para
592 && !cursor.nOffset && !cursor.pRun->member.run.nCharOfs)
593 {
594 /* Since the table row end, no text has been inserted, and the \intbl
595 * control word has not be used. We can confirm that we are not in a
596 * table anymore.
597 */
598 info->tableDef->tableRowStart = NULL;
599 info->canInheritInTbl = FALSE;
600 }
601 }
602 } else { /* v1.0 - v3.0 */
603 info->fmt.dwMask |= PFM_TABLE;
604 info->fmt.wEffects &= ~PFE_TABLE;
605 }
606 break;
607 case rtfNestLevel:
608 if (!info->editor->bEmulateVersion10) /* v4.1 */
609 {
610 while (info->rtfParam > info->nestingLevel) {
611 RTFTable *tableDef = heap_alloc_zero(sizeof(*tableDef));
612 tableDef->parent = info->tableDef;
613 info->tableDef = tableDef;
614
615 RTFFlushOutputBuffer(info);
616 if (tableDef->tableRowStart &&
617 tableDef->tableRowStart->member.para.nFlags & MEPF_ROWEND)
618 {
619 ME_DisplayItem *para = tableDef->tableRowStart;
620 para = para->member.para.next_para;
621 para = ME_InsertTableRowStartAtParagraph(info->editor, para);
622 tableDef->tableRowStart = para;
623 } else {
624 ME_Cursor cursor;
625 WCHAR endl = '\r';
626 cursor = info->editor->pCursors[0];
627 if (cursor.nOffset || cursor.pRun->member.run.nCharOfs)
628 ME_InsertTextFromCursor(info->editor, 0, &endl, 1, info->style);
629 tableDef->tableRowStart = ME_InsertTableRowStartFromCursor(info->editor);
630 }
631
632 info->nestingLevel++;
633 }
634 info->canInheritInTbl = FALSE;
635 }
636 break;
637 case rtfInTable:
638 {
639 if (!info->editor->bEmulateVersion10) /* v4.1 */
640 {
641 if (info->nestingLevel < 1)
642 {
643 RTFTable *tableDef;
644 if (!info->tableDef)
645 info->tableDef = heap_alloc_zero(sizeof(*info->tableDef));
646 tableDef = info->tableDef;
647 RTFFlushOutputBuffer(info);
648 if (tableDef->tableRowStart &&
649 tableDef->tableRowStart->member.para.nFlags & MEPF_ROWEND)
650 {
651 ME_DisplayItem *para = tableDef->tableRowStart;
652 para = para->member.para.next_para;
653 para = ME_InsertTableRowStartAtParagraph(info->editor, para);
654 tableDef->tableRowStart = para;
655 } else {
656 ME_Cursor cursor;
657 WCHAR endl = '\r';
658 cursor = info->editor->pCursors[0];
659 if (cursor.nOffset || cursor.pRun->member.run.nCharOfs)
660 ME_InsertTextFromCursor(info->editor, 0, &endl, 1, info->style);
661 tableDef->tableRowStart = ME_InsertTableRowStartFromCursor(info->editor);
662 }
663 info->nestingLevel = 1;
664 info->canInheritInTbl = TRUE;
665 }
666 return;
667 } else { /* v1.0 - v3.0 */
668 info->fmt.dwMask |= PFM_TABLE;
669 info->fmt.wEffects |= PFE_TABLE;
670 }
671 break;
672 }
673 case rtfFirstIndent:
674 case rtfLeftIndent:
675 if ((info->fmt.dwMask & (PFM_STARTINDENT | PFM_OFFSET)) != (PFM_STARTINDENT | PFM_OFFSET))
676 {
677 PARAFORMAT2 fmt;
678 fmt.cbSize = sizeof(fmt);
679 ME_GetSelectionParaFormat(info->editor, &fmt);
680 info->fmt.dwMask |= PFM_STARTINDENT | PFM_OFFSET;
681 info->fmt.dxStartIndent = fmt.dxStartIndent;
682 info->fmt.dxOffset = fmt.dxOffset;
683 }
684 if (info->rtfMinor == rtfFirstIndent)
685 {
686 info->fmt.dxStartIndent += info->fmt.dxOffset + info->rtfParam;
687 info->fmt.dxOffset = -info->rtfParam;
688 }
689 else
690 info->fmt.dxStartIndent = info->rtfParam - info->fmt.dxOffset;
691 break;
692 case rtfRightIndent:
693 info->fmt.dwMask |= PFM_RIGHTINDENT;
694 info->fmt.dxRightIndent = info->rtfParam;
695 break;
696 case rtfQuadLeft:
697 case rtfQuadJust:
698 info->fmt.dwMask |= PFM_ALIGNMENT;
699 info->fmt.wAlignment = PFA_LEFT;
700 break;
701 case rtfQuadRight:
702 info->fmt.dwMask |= PFM_ALIGNMENT;
703 info->fmt.wAlignment = PFA_RIGHT;
704 break;
705 case rtfQuadCenter:
706 info->fmt.dwMask |= PFM_ALIGNMENT;
707 info->fmt.wAlignment = PFA_CENTER;
708 break;
709 case rtfTabPos:
710 if (!(info->fmt.dwMask & PFM_TABSTOPS))
711 {
712 PARAFORMAT2 fmt;
713 fmt.cbSize = sizeof(fmt);
714 ME_GetSelectionParaFormat(info->editor, &fmt);
715 memcpy(info->fmt.rgxTabs, fmt.rgxTabs,
716 fmt.cTabCount * sizeof(fmt.rgxTabs[0]));
717 info->fmt.cTabCount = fmt.cTabCount;
718 info->fmt.dwMask |= PFM_TABSTOPS;
719 }
720 if (info->fmt.cTabCount < MAX_TAB_STOPS && info->rtfParam < 0x1000000)
721 info->fmt.rgxTabs[info->fmt.cTabCount++] = info->rtfParam;
722 break;
723 case rtfKeep:
724 info->fmt.dwMask |= PFM_KEEP;
725 info->fmt.wEffects |= PFE_KEEP;
726 break;
727 case rtfNoWidowControl:
728 info->fmt.dwMask |= PFM_NOWIDOWCONTROL;
729 info->fmt.wEffects |= PFE_NOWIDOWCONTROL;
730 break;
731 case rtfKeepNext:
732 info->fmt.dwMask |= PFM_KEEPNEXT;
733 info->fmt.wEffects |= PFE_KEEPNEXT;
734 break;
735 case rtfSpaceAfter:
736 info->fmt.dwMask |= PFM_SPACEAFTER;
737 info->fmt.dySpaceAfter = info->rtfParam;
738 break;
739 case rtfSpaceBefore:
740 info->fmt.dwMask |= PFM_SPACEBEFORE;
741 info->fmt.dySpaceBefore = info->rtfParam;
742 break;
743 case rtfSpaceBetween:
744 info->fmt.dwMask |= PFM_LINESPACING;
745 if ((int)info->rtfParam > 0)
746 {
747 info->fmt.dyLineSpacing = info->rtfParam;
748 info->fmt.bLineSpacingRule = 3;
749 }
750 else
751 {
752 info->fmt.dyLineSpacing = info->rtfParam;
753 info->fmt.bLineSpacingRule = 4;
754 }
755 break;
756 case rtfSpaceMultiply:
757 info->fmt.dwMask |= PFM_LINESPACING;
758 info->fmt.dyLineSpacing = info->rtfParam * 20;
759 info->fmt.bLineSpacingRule = 5;
760 break;
761 case rtfParBullet:
762 info->fmt.dwMask |= PFM_NUMBERING;
763 info->fmt.wNumbering = PFN_BULLET;
764 break;
765 case rtfParSimple:
766 info->fmt.dwMask |= PFM_NUMBERING;
767 info->fmt.wNumbering = 2; /* FIXME: MSDN says it's not used ?? */
768 break;
769 case rtfBorderLeft:
770 info->borderType = RTFBorderParaLeft;
771 info->fmt.wBorders |= 1;
772 info->fmt.dwMask |= PFM_BORDER;
773 break;
774 case rtfBorderRight:
775 info->borderType = RTFBorderParaRight;
776 info->fmt.wBorders |= 2;
777 info->fmt.dwMask |= PFM_BORDER;
778 break;
779 case rtfBorderTop:
780 info->borderType = RTFBorderParaTop;
781 info->fmt.wBorders |= 4;
782 info->fmt.dwMask |= PFM_BORDER;
783 break;
784 case rtfBorderBottom:
785 info->borderType = RTFBorderParaBottom;
786 info->fmt.wBorders |= 8;
787 info->fmt.dwMask |= PFM_BORDER;
788 break;
789 case rtfBorderSingle:
790 info->fmt.wBorders &= ~0x700;
791 info->fmt.wBorders |= 1 << 8;
792 info->fmt.dwMask |= PFM_BORDER;
793 break;
794 case rtfBorderThick:
795 info->fmt.wBorders &= ~0x700;
796 info->fmt.wBorders |= 2 << 8;
797 info->fmt.dwMask |= PFM_BORDER;
798 break;
799 case rtfBorderShadow:
800 info->fmt.wBorders &= ~0x700;
801 info->fmt.wBorders |= 10 << 8;
802 info->fmt.dwMask |= PFM_BORDER;
803 break;
804 case rtfBorderDouble:
805 info->fmt.wBorders &= ~0x700;
806 info->fmt.wBorders |= 7 << 8;
807 info->fmt.dwMask |= PFM_BORDER;
808 break;
809 case rtfBorderDot:
810 info->fmt.wBorders &= ~0x700;
811 info->fmt.wBorders |= 11 << 8;
812 info->fmt.dwMask |= PFM_BORDER;
813 break;
814 case rtfBorderWidth:
815 {
816 int borderSide = info->borderType & RTFBorderSideMask;
817 RTFTable *tableDef = info->tableDef;
818 if ((info->borderType & RTFBorderTypeMask) == RTFBorderTypeCell)
819 {
820 RTFBorder *border;
821 if (!tableDef || tableDef->numCellsDefined >= MAX_TABLE_CELLS)
822 break;
823 border = &tableDef->cells[tableDef->numCellsDefined].border[borderSide];
824 border->width = info->rtfParam;
825 break;
826 }
827 info->fmt.wBorderWidth = info->rtfParam;
828 info->fmt.dwMask |= PFM_BORDER;
829 break;
830 }
831 case rtfBorderSpace:
832 info->fmt.wBorderSpace = info->rtfParam;
833 info->fmt.dwMask |= PFM_BORDER;
834 break;
835 case rtfBorderColor:
836 {
837 RTFTable *tableDef = info->tableDef;
838 int borderSide = info->borderType & RTFBorderSideMask;
839 int borderType = info->borderType & RTFBorderTypeMask;
840 switch(borderType)
841 {
842 case RTFBorderTypePara:
843 if (!info->editor->bEmulateVersion10) /* v4.1 */
844 break;
845 /* v1.0 - 3.0 treat paragraph and row borders the same. */
846 case RTFBorderTypeRow:
847 if (tableDef) {
848 tableDef->border[borderSide].color = info->rtfParam;
849 }
850 break;
851 case RTFBorderTypeCell:
852 if (tableDef && tableDef->numCellsDefined < MAX_TABLE_CELLS) {
853 tableDef->cells[tableDef->numCellsDefined].border[borderSide].color = info->rtfParam;
854 }
855 break;
856 }
857 break;
858 }
859 case rtfRTLPar:
860 info->fmt.dwMask |= PFM_RTLPARA;
861 info->fmt.wEffects |= PFE_RTLPARA;
862 break;
863 case rtfLTRPar:
864 info->fmt.dwMask |= PFM_RTLPARA;
865 info->fmt.wEffects &= ~PFE_RTLPARA;
866 break;
867 }
868 }
869
870 void ME_RTFTblAttrHook(RTF_Info *info)
871 {
872 switch (info->rtfMinor)
873 {
874 case rtfRowDef:
875 {
876 if (!info->editor->bEmulateVersion10) /* v4.1 */
877 info->borderType = 0; /* Not sure */
878 else /* v1.0 - 3.0 */
879 info->borderType = RTFBorderRowTop;
880 if (!info->tableDef) {
881 info->tableDef = ME_MakeTableDef(info->editor);
882 } else {
883 ME_InitTableDef(info->editor, info->tableDef);
884 }
885 break;
886 }
887 case rtfCellPos:
888 {
889 int cellNum;
890 if (!info->tableDef)
891 {
892 info->tableDef = ME_MakeTableDef(info->editor);
893 }
894 cellNum = info->tableDef->numCellsDefined;
895 if (cellNum >= MAX_TABLE_CELLS)
896 break;
897 info->tableDef->cells[cellNum].rightBoundary = info->rtfParam;
898 if (cellNum < MAX_TAB_STOPS) {
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 ME_DisplayItem *para = info->editor->pCursors[0].pPara;
902 PARAFORMAT2 *pFmt = &para->member.para.fmt;
903 pFmt->rgxTabs[cellNum] &= ~0x00FFFFFF;
904 pFmt->rgxTabs[cellNum] |= 0x00FFFFFF & info->rtfParam;
905 }
906 info->tableDef->numCellsDefined++;
907 break;
908 }
909 case rtfRowBordTop:
910 info->borderType = RTFBorderRowTop;
911 break;
912 case rtfRowBordLeft:
913 info->borderType = RTFBorderRowLeft;
914 break;
915 case rtfRowBordBottom:
916 info->borderType = RTFBorderRowBottom;
917 break;
918 case rtfRowBordRight:
919 info->borderType = RTFBorderRowRight;
920 break;
921 case rtfCellBordTop:
922 info->borderType = RTFBorderCellTop;
923 break;
924 case rtfCellBordLeft:
925 info->borderType = RTFBorderCellLeft;
926 break;
927 case rtfCellBordBottom:
928 info->borderType = RTFBorderCellBottom;
929 break;
930 case rtfCellBordRight:
931 info->borderType = RTFBorderCellRight;
932 break;
933 case rtfRowGapH:
934 if (info->tableDef)
935 info->tableDef->gapH = info->rtfParam;
936 break;
937 case rtfRowLeftEdge:
938 if (info->tableDef)
939 info->tableDef->leftEdge = info->rtfParam;
940 break;
941 }
942 }
943
944 void ME_RTFSpecialCharHook(RTF_Info *info)
945 {
946 RTFTable *tableDef = info->tableDef;
947 switch (info->rtfMinor)
948 {
949 case rtfNestCell:
950 if (info->editor->bEmulateVersion10) /* v1.0 - v3.0 */
951 break;
952 /* else fall through since v4.1 treats rtfNestCell and rtfCell the same */
953 case rtfCell:
954 if (!tableDef)
955 break;
956 RTFFlushOutputBuffer(info);
957 if (!info->editor->bEmulateVersion10) { /* v4.1 */
958 if (tableDef->tableRowStart)
959 {
960 if (!info->nestingLevel &&
961 tableDef->tableRowStart->member.para.nFlags & MEPF_ROWEND)
962 {
963 ME_DisplayItem *para = tableDef->tableRowStart;
964 para = para->member.para.next_para;
965 para = ME_InsertTableRowStartAtParagraph(info->editor, para);
966 tableDef->tableRowStart = para;
967 info->nestingLevel = 1;
968 }
969 ME_InsertTableCellFromCursor(info->editor);
970 }
971 } else { /* v1.0 - v3.0 */
972 ME_DisplayItem *para = info->editor->pCursors[0].pPara;
973 PARAFORMAT2 *pFmt = &para->member.para.fmt;
974 if (pFmt->dwMask & PFM_TABLE && pFmt->wEffects & PFE_TABLE &&
975 tableDef->numCellsInserted < tableDef->numCellsDefined)
976 {
977 WCHAR tab = '\t';
978 ME_InsertTextFromCursor(info->editor, 0, &tab, 1, info->style);
979 tableDef->numCellsInserted++;
980 }
981 }
982 break;
983 case rtfNestRow:
984 if (info->editor->bEmulateVersion10) /* v1.0 - v3.0 */
985 break;
986 /* else fall through since v4.1 treats rtfNestRow and rtfRow the same */
987 case rtfRow:
988 {
989 ME_DisplayItem *para, *cell, *run;
990 int i;
991
992 if (!tableDef)
993 break;
994 RTFFlushOutputBuffer(info);
995 if (!info->editor->bEmulateVersion10) { /* v4.1 */
996 if (!tableDef->tableRowStart)
997 break;
998 if (!info->nestingLevel &&
999 tableDef->tableRowStart->member.para.nFlags & MEPF_ROWEND)
1000 {
1001 para = tableDef->tableRowStart;
1002 para = para->member.para.next_para;
1003 para = ME_InsertTableRowStartAtParagraph(info->editor, para);
1004 tableDef->tableRowStart = para;
1005 info->nestingLevel++;
1006 }
1007 para = tableDef->tableRowStart;
1008 cell = ME_FindItemFwd(para, diCell);
1009 assert(cell && !cell->member.cell.prev_cell);
1010 if (tableDef->numCellsDefined < 1)
1011 {
1012 /* 2000 twips appears to be the cell size that native richedit uses
1013 * when no cell sizes are specified. */
1014 const int defaultCellSize = 2000;
1015 int nRightBoundary = defaultCellSize;
1016 cell->member.cell.nRightBoundary = nRightBoundary;
1017 while (cell->member.cell.next_cell) {
1018 cell = cell->member.cell.next_cell;
1019 nRightBoundary += defaultCellSize;
1020 cell->member.cell.nRightBoundary = nRightBoundary;
1021 }
1022 para = ME_InsertTableCellFromCursor(info->editor);
1023 cell = para->member.para.pCell;
1024 cell->member.cell.nRightBoundary = nRightBoundary;
1025 } else {
1026 for (i = 0; i < tableDef->numCellsDefined; i++)
1027 {
1028 RTFCell *cellDef = &tableDef->cells[i];
1029 cell->member.cell.nRightBoundary = cellDef->rightBoundary;
1030 ME_ApplyBorderProperties(info, &cell->member.cell.border,
1031 cellDef->border);
1032 cell = cell->member.cell.next_cell;
1033 if (!cell)
1034 {
1035 para = ME_InsertTableCellFromCursor(info->editor);
1036 cell = para->member.para.pCell;
1037 }
1038 }
1039 /* Cell for table row delimiter is empty */
1040 cell->member.cell.nRightBoundary = tableDef->cells[i-1].rightBoundary;
1041 }
1042
1043 run = ME_FindItemFwd(cell, diRun);
1044 if (info->editor->pCursors[0].pRun != run ||
1045 info->editor->pCursors[0].nOffset)
1046 {
1047 int nOfs, nChars;
1048 /* Delete inserted cells that aren't defined. */
1049 info->editor->pCursors[1].pRun = run;
1050 info->editor->pCursors[1].pPara = ME_GetParagraph(run);
1051 info->editor->pCursors[1].nOffset = 0;
1052 nOfs = ME_GetCursorOfs(&info->editor->pCursors[1]);
1053 nChars = ME_GetCursorOfs(&info->editor->pCursors[0]) - nOfs;
1054 ME_InternalDeleteText(info->editor, &info->editor->pCursors[1],
1055 nChars, TRUE);
1056 }
1057
1058 para = ME_InsertTableRowEndFromCursor(info->editor);
1059 para->member.para.fmt.dxOffset = abs(info->tableDef->gapH);
1060 para->member.para.fmt.dxStartIndent = info->tableDef->leftEdge;
1061 ME_ApplyBorderProperties(info, &para->member.para.border,
1062 tableDef->border);
1063 info->nestingLevel--;
1064 if (!info->nestingLevel)
1065 {
1066 if (info->canInheritInTbl) {
1067 tableDef->tableRowStart = para;
1068 } else {
1069 while (info->tableDef) {
1070 tableDef = info->tableDef;
1071 info->tableDef = tableDef->parent;
1072 heap_free(tableDef);
1073 }
1074 }
1075 } else {
1076 info->tableDef = tableDef->parent;
1077 heap_free(tableDef);
1078 }
1079 } else { /* v1.0 - v3.0 */
1080 WCHAR endl = '\r';
1081 ME_DisplayItem *para = info->editor->pCursors[0].pPara;
1082 PARAFORMAT2 *pFmt = &para->member.para.fmt;
1083 pFmt->dxOffset = info->tableDef->gapH;
1084 pFmt->dxStartIndent = info->tableDef->leftEdge;
1085
1086 ME_ApplyBorderProperties(info, &para->member.para.border,
1087 tableDef->border);
1088 while (tableDef->numCellsInserted < tableDef->numCellsDefined)
1089 {
1090 WCHAR tab = '\t';
1091 ME_InsertTextFromCursor(info->editor, 0, &tab, 1, info->style);
1092 tableDef->numCellsInserted++;
1093 }
1094 pFmt->cTabCount = min(tableDef->numCellsDefined, MAX_TAB_STOPS);
1095 if (!tableDef->numCellsDefined)
1096 pFmt->wEffects &= ~PFE_TABLE;
1097 ME_InsertTextFromCursor(info->editor, 0, &endl, 1, info->style);
1098 tableDef->numCellsInserted = 0;
1099 }
1100 break;
1101 }
1102 case rtfTab:
1103 case rtfPar:
1104 if (info->editor->bEmulateVersion10) { /* v1.0 - 3.0 */
1105 ME_DisplayItem *para;
1106 PARAFORMAT2 *pFmt;
1107 RTFFlushOutputBuffer(info);
1108 para = info->editor->pCursors[0].pPara;
1109 pFmt = &para->member.para.fmt;
1110 if (pFmt->dwMask & PFM_TABLE && pFmt->wEffects & PFE_TABLE)
1111 {
1112 /* rtfPar is treated like a space within a table. */
1113 info->rtfClass = rtfText;
1114 info->rtfMajor = ' ';
1115 }
1116 else if (info->rtfMinor == rtfPar && tableDef)
1117 tableDef->numCellsInserted = 0;
1118 }
1119 break;
1120 }
1121 }
1122
1123 static HRESULT insert_static_object(ME_TextEditor *editor, HENHMETAFILE hemf, HBITMAP hbmp,
1124 const SIZEL* sz)
1125 {
1126 LPOLEOBJECT lpObject = NULL;
1127 LPSTORAGE lpStorage = NULL;
1128 LPOLECLIENTSITE lpClientSite = NULL;
1129 LPDATAOBJECT lpDataObject = NULL;
1130 LPOLECACHE lpOleCache = NULL;
1131 STGMEDIUM stgm;
1132 FORMATETC fm;
1133 CLSID clsid;
1134 HRESULT hr = E_FAIL;
1135 DWORD conn;
1136
1137 if (hemf)
1138 {
1139 stgm.tymed = TYMED_ENHMF;
1140 stgm.u.hEnhMetaFile = hemf;
1141 fm.cfFormat = CF_ENHMETAFILE;
1142 }
1143 else if (hbmp)
1144 {
1145 stgm.tymed = TYMED_GDI;
1146 stgm.u.hBitmap = hbmp;
1147 fm.cfFormat = CF_BITMAP;
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 (!editor->reOle)
1157 {
1158 if (!CreateIRichEditOle(NULL, editor, (LPVOID *)&editor->reOle))
1159 return hr;
1160 }
1161
1162 if (OleCreateDefaultHandler(&CLSID_NULL, NULL, &IID_IOleObject, (void**)&lpObject) == S_OK &&
1163 IRichEditOle_GetClientSite(editor->reOle, &lpClientSite) == S_OK &&
1164 IOleObject_SetClientSite(lpObject, lpClientSite) == S_OK &&
1165 IOleObject_GetUserClassID(lpObject, &clsid) == S_OK &&
1166 IOleObject_QueryInterface(lpObject, &IID_IOleCache, (void**)&lpOleCache) == S_OK &&
1167 IOleCache_Cache(lpOleCache, &fm, 0, &conn) == S_OK &&
1168 IOleObject_QueryInterface(lpObject, &IID_IDataObject, (void**)&lpDataObject) == S_OK &&
1169 IDataObject_SetData(lpDataObject, &fm, &stgm, TRUE) == S_OK)
1170 {
1171 REOBJECT reobject;
1172
1173 reobject.cbStruct = sizeof(reobject);
1174 reobject.cp = REO_CP_SELECTION;
1175 reobject.clsid = clsid;
1176 reobject.poleobj = lpObject;
1177 reobject.pstg = lpStorage;
1178 reobject.polesite = lpClientSite;
1179 /* convert from twips to .01 mm */
1180 reobject.sizel.cx = MulDiv(sz->cx, 254, 144);
1181 reobject.sizel.cy = MulDiv(sz->cy, 254, 144);
1182 reobject.dvaspect = DVASPECT_CONTENT;
1183 reobject.dwFlags = 0; /* FIXME */
1184 reobject.dwUser = 0;
1185
1186 ME_InsertOLEFromCursor(editor, &reobject, 0);
1187 hr = S_OK;
1188 }
1189
1190 if (lpObject) IOleObject_Release(lpObject);
1191 if (lpClientSite) IOleClientSite_Release(lpClientSite);
1192 if (lpStorage) IStorage_Release(lpStorage);
1193 if (lpDataObject) IDataObject_Release(lpDataObject);
1194 if (lpOleCache) IOleCache_Release(lpOleCache);
1195
1196 return hr;
1197 }
1198
1199 static void ME_RTFReadShpPictGroup( RTF_Info *info )
1200 {
1201 int level = 1;
1202
1203 for (;;)
1204 {
1205 RTFGetToken (info);
1206
1207 if (info->rtfClass == rtfEOF) return;
1208 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1209 {
1210 if (--level == 0) break;
1211 }
1212 else if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1213 {
1214 level++;
1215 }
1216 else
1217 {
1218 RTFRouteToken( info );
1219 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1220 level--;
1221 }
1222 }
1223
1224 RTFRouteToken( info ); /* feed "}" back to router */
1225 return;
1226 }
1227
1228 static DWORD read_hex_data( RTF_Info *info, BYTE **out )
1229 {
1230 DWORD read = 0, size = 1024;
1231 BYTE *buf, val;
1232 BOOL flip;
1233
1234 *out = NULL;
1235
1236 if (info->rtfClass != rtfText)
1237 {
1238 ERR("Called with incorrect token\n");
1239 return 0;
1240 }
1241
1242 buf = HeapAlloc( GetProcessHeap(), 0, size );
1243 if (!buf) return 0;
1244
1245 val = info->rtfMajor;
1246 for (flip = TRUE;; flip = !flip)
1247 {
1248 RTFGetToken( info );
1249 if (info->rtfClass == rtfEOF)
1250 {
1251 HeapFree( GetProcessHeap(), 0, buf );
1252 return 0;
1253 }
1254 if (info->rtfClass != rtfText) break;
1255 if (flip)
1256 {
1257 if (read >= size)
1258 {
1259 size *= 2;
1260 buf = HeapReAlloc( GetProcessHeap(), 0, buf, size );
1261 if (!buf) return 0;
1262 }
1263 buf[read++] = RTFCharToHex(val) * 16 + RTFCharToHex(info->rtfMajor);
1264 }
1265 else
1266 val = info->rtfMajor;
1267 }
1268 if (flip) FIXME("wrong hex string\n");
1269
1270 *out = buf;
1271 return read;
1272 }
1273
1274 static void ME_RTFReadPictGroup(RTF_Info *info)
1275 {
1276 SIZEL sz;
1277 BYTE *buffer = NULL;
1278 DWORD size = 0;
1279 METAFILEPICT mfp;
1280 HENHMETAFILE hemf;
1281 HBITMAP hbmp;
1282 enum gfxkind {gfx_unknown = 0, gfx_enhmetafile, gfx_metafile, gfx_dib} gfx = gfx_unknown;
1283 int level = 1;
1284
1285 mfp.mm = MM_TEXT;
1286 sz.cx = sz.cy = 0;
1287
1288 for (;;)
1289 {
1290 RTFGetToken( info );
1291
1292 if (info->rtfClass == rtfText)
1293 {
1294 if (level == 1)
1295 {
1296 if (!buffer)
1297 size = read_hex_data( info, &buffer );
1298 }
1299 else
1300 {
1301 RTFSkipGroup( info );
1302 }
1303 } /* We potentially have a new token so fall through. */
1304
1305 if (info->rtfClass == rtfEOF) return;
1306
1307 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1308 {
1309 if (--level == 0) break;
1310 continue;
1311 }
1312 if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1313 {
1314 level++;
1315 continue;
1316 }
1317 if (!RTFCheckCM( info, rtfControl, rtfPictAttr ))
1318 {
1319 RTFRouteToken( info );
1320 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1321 level--;
1322 continue;
1323 }
1324
1325 if (RTFCheckMM( info, rtfPictAttr, rtfWinMetafile ))
1326 {
1327 mfp.mm = info->rtfParam;
1328 gfx = gfx_metafile;
1329 }
1330 else if (RTFCheckMM( info, rtfPictAttr, rtfDevIndBitmap ))
1331 {
1332 if (info->rtfParam != 0) FIXME("dibitmap should be 0 (%d)\n", info->rtfParam);
1333 gfx = gfx_dib;
1334 }
1335 else if (RTFCheckMM( info, rtfPictAttr, rtfEmfBlip ))
1336 gfx = gfx_enhmetafile;
1337 else if (RTFCheckMM( info, rtfPictAttr, rtfPicWid ))
1338 mfp.xExt = info->rtfParam;
1339 else if (RTFCheckMM( info, rtfPictAttr, rtfPicHt ))
1340 mfp.yExt = info->rtfParam;
1341 else if (RTFCheckMM( info, rtfPictAttr, rtfPicGoalWid ))
1342 sz.cx = info->rtfParam;
1343 else if (RTFCheckMM( info, rtfPictAttr, rtfPicGoalHt ))
1344 sz.cy = info->rtfParam;
1345 else
1346 FIXME("Non supported attribute: %d %d %d\n", info->rtfClass, info->rtfMajor, info->rtfMinor);
1347 }
1348
1349 if (buffer)
1350 {
1351 switch (gfx)
1352 {
1353 case gfx_enhmetafile:
1354 if ((hemf = SetEnhMetaFileBits( size, buffer )))
1355 insert_static_object( info->editor, hemf, NULL, &sz );
1356 break;
1357 case gfx_metafile:
1358 if ((hemf = SetWinMetaFileBits( size, buffer, NULL, &mfp )))
1359 insert_static_object( info->editor, hemf, NULL, &sz );
1360 break;
1361 case gfx_dib:
1362 {
1363 BITMAPINFO *bi = (BITMAPINFO*)buffer;
1364 HDC hdc = GetDC(0);
1365 unsigned nc = bi->bmiHeader.biClrUsed;
1366
1367 /* not quite right, especially for bitfields type of compression */
1368 if (!nc && bi->bmiHeader.biBitCount <= 8)
1369 nc = 1 << bi->bmiHeader.biBitCount;
1370 if ((hbmp = CreateDIBitmap( hdc, &bi->bmiHeader,
1371 CBM_INIT, (char*)(bi + 1) + nc * sizeof(RGBQUAD),
1372 bi, DIB_RGB_COLORS)) )
1373 insert_static_object( info->editor, NULL, hbmp, &sz );
1374 ReleaseDC( 0, hdc );
1375 break;
1376 }
1377 default:
1378 break;
1379 }
1380 }
1381 HeapFree( GetProcessHeap(), 0, buffer );
1382 RTFRouteToken( info ); /* feed "}" back to router */
1383 return;
1384 }
1385
1386 /* for now, lookup the \result part and use it, whatever the object */
1387 static void ME_RTFReadObjectGroup(RTF_Info *info)
1388 {
1389 for (;;)
1390 {
1391 RTFGetToken (info);
1392 if (info->rtfClass == rtfEOF)
1393 return;
1394 if (RTFCheckCM(info, rtfGroup, rtfEndGroup))
1395 break;
1396 if (RTFCheckCM(info, rtfGroup, rtfBeginGroup))
1397 {
1398 RTFGetToken (info);
1399 if (info->rtfClass == rtfEOF)
1400 return;
1401 if (RTFCheckCMM(info, rtfControl, rtfDestination, rtfObjResult))
1402 {
1403 int level = 1;
1404
1405 while (RTFGetToken (info) != rtfEOF)
1406 {
1407 if (info->rtfClass == rtfGroup)
1408 {
1409 if (info->rtfMajor == rtfBeginGroup) level++;
1410 else if (info->rtfMajor == rtfEndGroup && --level < 0) break;
1411 }
1412 RTFRouteToken(info);
1413 }
1414 }
1415 else RTFSkipGroup(info);
1416 continue;
1417 }
1418 if (!RTFCheckCM (info, rtfControl, rtfObjAttr))
1419 {
1420 FIXME("Non supported attribute: %d %d %d\n", info->rtfClass, info->rtfMajor, info->rtfMinor);
1421 return;
1422 }
1423 }
1424 RTFRouteToken(info); /* feed "}" back to router */
1425 }
1426
1427 static void ME_RTFReadParnumGroup( RTF_Info *info )
1428 {
1429 int level = 1, type = -1;
1430 WORD indent = 0, start = 1;
1431 WCHAR txt_before = 0, txt_after = 0;
1432
1433 for (;;)
1434 {
1435 RTFGetToken( info );
1436
1437 if (RTFCheckCMM( info, rtfControl, rtfDestination, rtfParNumTextBefore ) ||
1438 RTFCheckCMM( info, rtfControl, rtfDestination, rtfParNumTextAfter ))
1439 {
1440 int loc = info->rtfMinor;
1441
1442 RTFGetToken( info );
1443 if (info->rtfClass == rtfText)
1444 {
1445 if (loc == rtfParNumTextBefore)
1446 txt_before = info->rtfMajor;
1447 else
1448 txt_after = info->rtfMajor;
1449 continue;
1450 }
1451 /* falling through to catch EOFs and group level changes */
1452 }
1453
1454 if (info->rtfClass == rtfEOF)
1455 return;
1456
1457 if (RTFCheckCM( info, rtfGroup, rtfEndGroup ))
1458 {
1459 if (--level == 0) break;
1460 continue;
1461 }
1462
1463 if (RTFCheckCM( info, rtfGroup, rtfBeginGroup ))
1464 {
1465 level++;
1466 continue;
1467 }
1468
1469 /* Ignore non para-attr */
1470 if (!RTFCheckCM( info, rtfControl, rtfParAttr ))
1471 continue;
1472
1473 switch (info->rtfMinor)
1474 {
1475 case rtfParLevel: /* Para level is ignored */
1476 case rtfParSimple:
1477 break;
1478 case rtfParBullet:
1479 type = PFN_BULLET;
1480 break;
1481
1482 case rtfParNumDecimal:
1483 type = PFN_ARABIC;
1484 break;
1485 case rtfParNumULetter:
1486 type = PFN_UCLETTER;
1487 break;
1488 case rtfParNumURoman:
1489 type = PFN_UCROMAN;
1490 break;
1491 case rtfParNumLLetter:
1492 type = PFN_LCLETTER;
1493 break;
1494 case rtfParNumLRoman:
1495 type = PFN_LCROMAN;
1496 break;
1497
1498 case rtfParNumIndent:
1499 indent = info->rtfParam;
1500 break;
1501 case rtfParNumStartAt:
1502 start = info->rtfParam;
1503 break;
1504 }
1505 }
1506
1507 if (type != -1)
1508 {
1509 info->fmt.dwMask |= (PFM_NUMBERING | PFM_NUMBERINGSTART | PFM_NUMBERINGSTYLE | PFM_NUMBERINGTAB);
1510 info->fmt.wNumbering = type;
1511 info->fmt.wNumberingStart = start;
1512 info->fmt.wNumberingStyle = PFNS_PAREN;
1513 if (type != PFN_BULLET)
1514 {
1515 if (txt_before == 0 && txt_after == 0)
1516 info->fmt.wNumberingStyle = PFNS_PLAIN;
1517 else if (txt_after == '.')
1518 info->fmt.wNumberingStyle = PFNS_PERIOD;
1519 else if (txt_before == '(' && txt_after == ')')
1520 info->fmt.wNumberingStyle = PFNS_PARENS;
1521 }
1522 info->fmt.wNumberingTab = indent;
1523 }
1524
1525 TRACE("type %d indent %d start %d txt before %04x txt after %04x\n",
1526 type, indent, start, txt_before, txt_after);
1527
1528 RTFRouteToken( info ); /* feed "}" back to router */
1529 }
1530
1531 static void ME_RTFReadHook(RTF_Info *info)
1532 {
1533 switch(info->rtfClass)
1534 {
1535 case rtfGroup:
1536 switch(info->rtfMajor)
1537 {
1538 case rtfBeginGroup:
1539 if (info->stackTop < maxStack) {
1540 info->stack[info->stackTop].style = info->style;
1541 ME_AddRefStyle(info->style);
1542 info->stack[info->stackTop].codePage = info->codePage;
1543 info->stack[info->stackTop].unicodeLength = info->unicodeLength;
1544 }
1545 info->stackTop++;
1546 info->styleChanged = FALSE;
1547 break;
1548 case rtfEndGroup:
1549 {
1550 RTFFlushOutputBuffer(info);
1551 info->stackTop--;
1552 if (info->stackTop <= 0)
1553 info->rtfClass = rtfEOF;
1554 if (info->stackTop < 0)
1555 return;
1556
1557 ME_ReleaseStyle(info->style);
1558 info->style = info->stack[info->stackTop].style;
1559 info->codePage = info->stack[info->stackTop].codePage;
1560 info->unicodeLength = info->stack[info->stackTop].unicodeLength;
1561 break;
1562 }
1563 }
1564 break;
1565 }
1566 }
1567
1568 void
1569 ME_StreamInFill(ME_InStream *stream)
1570 {
1571 stream->editstream->dwError = stream->editstream->pfnCallback(stream->editstream->dwCookie,
1572 (BYTE *)stream->buffer,
1573 sizeof(stream->buffer),
1574 (LONG *)&stream->dwSize);
1575 stream->dwUsed = 0;
1576 }
1577
1578 static LRESULT ME_StreamIn(ME_TextEditor *editor, DWORD format, EDITSTREAM *stream, BOOL stripLastCR)
1579 {
1580 RTF_Info parser;
1581 ME_Style *style;
1582 int from, to, nUndoMode;
1583 int nEventMask = editor->nEventMask;
1584 ME_InStream inStream;
1585 BOOL invalidRTF = FALSE;
1586 ME_Cursor *selStart, *selEnd;
1587 LRESULT num_read = 0; /* bytes read for SF_TEXT, non-control chars inserted for SF_RTF */
1588
1589 TRACE("stream==%p editor==%p format==0x%X\n", stream, editor, format);
1590 editor->nEventMask = 0;
1591
1592 ME_GetSelectionOfs(editor, &from, &to);
1593 if (format & SFF_SELECTION && editor->mode & TM_RICHTEXT)
1594 {
1595 ME_GetSelection(editor, &selStart, &selEnd);
1596 style = ME_GetSelectionInsertStyle(editor);
1597
1598 ME_InternalDeleteText(editor, selStart, to - from, FALSE);
1599
1600 /* Don't insert text at the end of the table row */
1601 if (!editor->bEmulateVersion10) { /* v4.1 */
1602 ME_DisplayItem *para = editor->pCursors->pPara;
1603 if (para->member.para.nFlags & MEPF_ROWEND)
1604 {
1605 para = para->member.para.next_para;
1606 editor->pCursors[0].pPara = para;
1607 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
1608 editor->pCursors[0].nOffset = 0;
1609 }
1610 if (para->member.para.nFlags & MEPF_ROWSTART)
1611 {
1612 para = para->member.para.next_para;
1613 editor->pCursors[0].pPara = para;
1614 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
1615 editor->pCursors[0].nOffset = 0;
1616 }
1617 editor->pCursors[1] = editor->pCursors[0];
1618 } else { /* v1.0 - 3.0 */
1619 if (editor->pCursors[0].pRun->member.run.nFlags & MERF_ENDPARA &&
1620 ME_IsInTable(editor->pCursors[0].pRun))
1621 return 0;
1622 }
1623 } else {
1624 style = editor->pBuffer->pDefaultStyle;
1625 ME_AddRefStyle(style);
1626 ME_SetSelection(editor, 0, 0);
1627 ME_InternalDeleteText(editor, &editor->pCursors[1],
1628 ME_GetTextLength(editor), FALSE);
1629 from = to = 0;
1630 ME_ClearTempStyle(editor);
1631 ME_SetDefaultParaFormat(editor, &editor->pCursors[0].pPara->member.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 if (parser.tableDef && parser.tableDef->tableRowStart &&
1693 (parser.nestingLevel > 0 || parser.canInheritInTbl))
1694 {
1695 /* Delete any incomplete table row at the end of the rich text. */
1696 int nOfs, nChars;
1697 ME_DisplayItem *para;
1698
1699 parser.rtfMinor = rtfRow;
1700 /* Complete the table row before deleting it.
1701 * By doing it this way we will have the current paragraph format set
1702 * properly to reflect that is not in the complete table, and undo items
1703 * will be added for this change to the current paragraph format. */
1704 if (parser.nestingLevel > 0)
1705 {
1706 while (parser.nestingLevel > 1)
1707 ME_RTFSpecialCharHook(&parser); /* Decrements nestingLevel */
1708 para = parser.tableDef->tableRowStart;
1709 ME_RTFSpecialCharHook(&parser);
1710 } else {
1711 para = parser.tableDef->tableRowStart;
1712 ME_RTFSpecialCharHook(&parser);
1713 assert(para->member.para.nFlags & MEPF_ROWEND);
1714 para = para->member.para.next_para;
1715 }
1716
1717 editor->pCursors[1].pPara = para;
1718 editor->pCursors[1].pRun = ME_FindItemFwd(para, diRun);
1719 editor->pCursors[1].nOffset = 0;
1720 nOfs = ME_GetCursorOfs(&editor->pCursors[1]);
1721 nChars = ME_GetCursorOfs(&editor->pCursors[0]) - nOfs;
1722 ME_InternalDeleteText(editor, &editor->pCursors[1], nChars, TRUE);
1723 if (parser.tableDef)
1724 parser.tableDef->tableRowStart = NULL;
1725 }
1726 }
1727 ME_CheckTablesForCorruption(editor);
1728 RTFDestroy(&parser);
1729
1730 if (parser.stackTop > 0)
1731 {
1732 while (--parser.stackTop >= 0)
1733 {
1734 ME_ReleaseStyle(parser.style);
1735 parser.style = parser.stack[parser.stackTop].style;
1736 }
1737 if (!inStream.editstream->dwError)
1738 inStream.editstream->dwError = HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
1739 }
1740
1741 /* Remove last line break, as mandated by tests. This is not affected by
1742 CR/LF counters, since RTF streaming presents only \para tokens, which
1743 are converted according to the standard rules: \r for 2.0, \r\n for 1.0
1744 */
1745 if (stripLastCR && !(format & SFF_SELECTION)) {
1746 int newto;
1747 ME_GetSelection(editor, &selStart, &selEnd);
1748 newto = ME_GetCursorOfs(selEnd);
1749 if (newto > to + (editor->bEmulateVersion10 ? 1 : 0)) {
1750 WCHAR lastchar[3] = {'\0', '\0'};
1751 int linebreakSize = editor->bEmulateVersion10 ? 2 : 1;
1752 ME_Cursor linebreakCursor = *selEnd, lastcharCursor = *selEnd;
1753 CHARFORMAT2W cf;
1754
1755 /* Set the final eop to the char fmt of the last char */
1756 cf.cbSize = sizeof(cf);
1757 cf.dwMask = CFM_ALL2;
1758 ME_MoveCursorChars(editor, &lastcharCursor, -1, FALSE);
1759 ME_GetCharFormat(editor, &lastcharCursor, &linebreakCursor, &cf);
1760 ME_SetSelection(editor, newto, -1);
1761 ME_SetSelectionCharFormat(editor, &cf);
1762 ME_SetSelection(editor, newto, newto);
1763
1764 ME_MoveCursorChars(editor, &linebreakCursor, -linebreakSize, FALSE);
1765 ME_GetTextW(editor, lastchar, 2, &linebreakCursor, linebreakSize, FALSE, FALSE);
1766 if (lastchar[0] == '\r' && (lastchar[1] == '\n' || lastchar[1] == '\0')) {
1767 ME_InternalDeleteText(editor, &linebreakCursor, linebreakSize, FALSE);
1768 }
1769 }
1770 }
1771 to = ME_GetCursorOfs(&editor->pCursors[0]);
1772 num_read = to - from;
1773
1774 style = parser.style;
1775 }
1776 else if (format & SF_TEXT)
1777 {
1778 num_read = ME_StreamInText(editor, format, &inStream, style);
1779 to = ME_GetCursorOfs(&editor->pCursors[0]);
1780 }
1781 else
1782 ERR("EM_STREAMIN without SF_TEXT or SF_RTF\n");
1783 /* put the cursor at the top */
1784 if (!(format & SFF_SELECTION))
1785 ME_SetSelection(editor, 0, 0);
1786 ME_CursorFromCharOfs(editor, from, &start);
1787 ME_UpdateLinkAttribute(editor, &start, to - from);
1788 }
1789
1790 /* Restore saved undo mode */
1791 editor->nUndoMode = nUndoMode;
1792
1793 /* even if we didn't add an undo, we need to commit anything on the stack */
1794 ME_CommitUndo(editor);
1795
1796 /* If SFF_SELECTION isn't set, delete any undos from before we started too */
1797 if (!(format & SFF_SELECTION))
1798 ME_EmptyUndoStack(editor);
1799
1800 ME_ReleaseStyle(style);
1801 editor->nEventMask = nEventMask;
1802 ME_UpdateRepaint(editor, FALSE);
1803 if (!(format & SFF_SELECTION)) {
1804 ME_ClearTempStyle(editor);
1805 }
1806 ITextHost_TxShowCaret(editor->texthost, FALSE);
1807 ME_MoveCaret(editor);
1808 ITextHost_TxShowCaret(editor->texthost, TRUE);
1809 ME_SendSelChange(editor);
1810 ME_SendRequestResize(editor, FALSE);
1811
1812 return num_read;
1813 }
1814
1815
1816 typedef struct tagME_RTFStringStreamStruct
1817 {
1818 char *string;
1819 int pos;
1820 int length;
1821 } ME_RTFStringStreamStruct;
1822
1823 static DWORD CALLBACK ME_ReadFromRTFString(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
1824 {
1825 ME_RTFStringStreamStruct *pStruct = (ME_RTFStringStreamStruct *)dwCookie;
1826 int count;
1827
1828 count = min(cb, pStruct->length - pStruct->pos);
1829 memmove(lpBuff, pStruct->string + pStruct->pos, count);
1830 pStruct->pos += count;
1831 *pcb = count;
1832 return 0;
1833 }
1834
1835 static void
1836 ME_StreamInRTFString(ME_TextEditor *editor, BOOL selection, char *string)
1837 {
1838 EDITSTREAM es;
1839 ME_RTFStringStreamStruct data;
1840
1841 data.string = string;
1842 data.length = strlen(string);
1843 data.pos = 0;
1844 es.dwCookie = (DWORD_PTR)&data;
1845 es.pfnCallback = ME_ReadFromRTFString;
1846 ME_StreamIn(editor, SF_RTF | (selection ? SFF_SELECTION : 0), &es, TRUE);
1847 }
1848
1849
1850 static int
1851 ME_FindText(ME_TextEditor *editor, DWORD flags, const CHARRANGE *chrg, const WCHAR *text, CHARRANGE *chrgText)
1852 {
1853 const int nLen = lstrlenW(text);
1854 const int nTextLen = ME_GetTextLength(editor);
1855 int nMin, nMax;
1856 ME_Cursor cursor;
1857 WCHAR wLastChar = ' ';
1858
1859 TRACE("flags==0x%08x, chrg->cpMin==%d, chrg->cpMax==%d text==%s\n",
1860 flags, chrg->cpMin, chrg->cpMax, debugstr_w(text));
1861
1862 if (flags & ~(FR_DOWN | FR_MATCHCASE | FR_WHOLEWORD))
1863 FIXME("Flags 0x%08x not implemented\n",
1864 flags & ~(FR_DOWN | FR_MATCHCASE | FR_WHOLEWORD));
1865
1866 nMin = chrg->cpMin;
1867 if (chrg->cpMax == -1)
1868 nMax = nTextLen;
1869 else
1870 nMax = chrg->cpMax > nTextLen ? nTextLen : chrg->cpMax;
1871
1872 /* In 1.0 emulation, if cpMax reaches end of text, add the FR_DOWN flag */
1873 if (editor->bEmulateVersion10 && nMax == nTextLen)
1874 {
1875 flags |= FR_DOWN;
1876 }
1877
1878 /* In 1.0 emulation, cpMin must always be no greater than cpMax */
1879 if (editor->bEmulateVersion10 && nMax < nMin)
1880 {
1881 if (chrgText)
1882 {
1883 chrgText->cpMin = -1;
1884 chrgText->cpMax = -1;
1885 }
1886 return -1;
1887 }
1888
1889 /* when searching up, if cpMin < cpMax, then instead of searching
1890 * on [cpMin,cpMax], we search on [0,cpMin], otherwise, search on
1891 * [cpMax, cpMin]. The exception is when cpMax is -1, in which
1892 * case, it is always bigger than cpMin.
1893 */
1894 if (!editor->bEmulateVersion10 && !(flags & FR_DOWN))
1895 {
1896 int nSwap = nMax;
1897
1898 nMax = nMin > nTextLen ? nTextLen : nMin;
1899 if (nMin < nSwap || chrg->cpMax == -1)
1900 nMin = 0;
1901 else
1902 nMin = nSwap;
1903 }
1904
1905 if (!nLen || nMin < 0 || nMax < 0 || nMax < nMin)
1906 {
1907 if (chrgText)
1908 chrgText->cpMin = chrgText->cpMax = -1;
1909 return -1;
1910 }
1911
1912 if (flags & FR_DOWN) /* Forward search */
1913 {
1914 /* If possible, find the character before where the search starts */
1915 if ((flags & FR_WHOLEWORD) && nMin)
1916 {
1917 ME_CursorFromCharOfs(editor, nMin - 1, &cursor);
1918 wLastChar = *get_text( &cursor.pRun->member.run, cursor.nOffset );
1919 ME_MoveCursorChars(editor, &cursor, 1, FALSE);
1920 } else {
1921 ME_CursorFromCharOfs(editor, nMin, &cursor);
1922 }
1923
1924 while (cursor.pRun && ME_GetCursorOfs(&cursor) + nLen <= nMax)
1925 {
1926 ME_DisplayItem *pCurItem = cursor.pRun;
1927 int nCurStart = cursor.nOffset;
1928 int nMatched = 0;
1929
1930 while (pCurItem && ME_CharCompare( *get_text( &pCurItem->member.run, nCurStart + nMatched ), text[nMatched], (flags & FR_MATCHCASE)))
1931 {
1932 if ((flags & FR_WHOLEWORD) && isalnumW(wLastChar))
1933 break;
1934
1935 nMatched++;
1936 if (nMatched == nLen)
1937 {
1938 ME_DisplayItem *pNextItem = pCurItem;
1939 int nNextStart = nCurStart;
1940 WCHAR wNextChar;
1941
1942 /* Check to see if next character is a whitespace */
1943 if (flags & FR_WHOLEWORD)
1944 {
1945 if (nCurStart + nMatched == pCurItem->member.run.len)
1946 {
1947 pNextItem = ME_FindItemFwd(pCurItem, diRun);
1948 nNextStart = -nMatched;
1949 }
1950
1951 if (pNextItem)
1952 wNextChar = *get_text( &pNextItem->member.run, nNextStart + nMatched );
1953 else
1954 wNextChar = ' ';
1955
1956 if (isalnumW(wNextChar))
1957 break;
1958 }
1959
1960 cursor.nOffset += cursor.pPara->member.para.nCharOfs + cursor.pRun->member.run.nCharOfs;
1961 if (chrgText)
1962 {
1963 chrgText->cpMin = cursor.nOffset;
1964 chrgText->cpMax = cursor.nOffset + nLen;
1965 }
1966 TRACE("found at %d-%d\n", cursor.nOffset, cursor.nOffset + nLen);
1967 return cursor.nOffset;
1968 }
1969 if (nCurStart + nMatched == pCurItem->member.run.len)
1970 {
1971 pCurItem = ME_FindItemFwd(pCurItem, diRun);
1972 nCurStart = -nMatched;
1973 }
1974 }
1975 if (pCurItem)
1976 wLastChar = *get_text( &pCurItem->member.run, nCurStart + nMatched );
1977 else
1978 wLastChar = ' ';
1979
1980 cursor.nOffset++;
1981 if (cursor.nOffset == cursor.pRun->member.run.len)
1982 {
1983 ME_NextRun(&cursor.pPara, &cursor.pRun, TRUE);
1984 cursor.nOffset = 0;
1985 }
1986 }
1987 }
1988 else /* Backward search */
1989 {
1990 /* If possible, find the character after where the search ends */
1991 if ((flags & FR_WHOLEWORD) && nMax < nTextLen - 1)
1992 {
1993 ME_CursorFromCharOfs(editor, nMax + 1, &cursor);
1994 wLastChar = *get_text( &cursor.pRun->member.run, cursor.nOffset );
1995 ME_MoveCursorChars(editor, &cursor, -1, FALSE);
1996 } else {
1997 ME_CursorFromCharOfs(editor, nMax, &cursor);
1998 }
1999
2000 while (cursor.pRun && ME_GetCursorOfs(&cursor) - nLen >= nMin)
2001 {
2002 ME_DisplayItem *pCurItem = cursor.pRun;
2003 ME_DisplayItem *pCurPara = cursor.pPara;
2004 int nCurEnd = cursor.nOffset;
2005 int nMatched = 0;
2006
2007 if (nCurEnd == 0)
2008 {
2009 ME_PrevRun(&pCurPara, &pCurItem, TRUE);
2010 nCurEnd = pCurItem->member.run.len;
2011 }
2012
2013 while (pCurItem && ME_CharCompare( *get_text( &pCurItem->member.run, nCurEnd - nMatched - 1 ),
2014 text[nLen - nMatched - 1], (flags & FR_MATCHCASE) ))
2015 {
2016 if ((flags & FR_WHOLEWORD) && isalnumW(wLastChar))
2017 break;
2018
2019 nMatched++;
2020 if (nMatched == nLen)
2021 {
2022 ME_DisplayItem *pPrevItem = pCurItem;
2023 int nPrevEnd = nCurEnd;
2024 WCHAR wPrevChar;
2025 int nStart;
2026
2027 /* Check to see if previous character is a whitespace */
2028 if (flags & FR_WHOLEWORD)
2029 {
2030 if (nPrevEnd - nMatched == 0)
2031 {
2032 pPrevItem = ME_FindItemBack(pCurItem, diRun);
2033 if (pPrevItem)
2034 nPrevEnd = pPrevItem->member.run.len + nMatched;
2035 }
2036
2037 if (pPrevItem)
2038 wPrevChar = *get_text( &pPrevItem->member.run, nPrevEnd - nMatched - 1 );
2039 else
2040 wPrevChar = ' ';
2041
2042 if (isalnumW(wPrevChar))
2043 break;
2044 }
2045
2046 nStart = pCurPara->member.para.nCharOfs
2047 + pCurItem->member.run.nCharOfs + nCurEnd - nMatched;
2048 if (chrgText)
2049 {
2050 chrgText->cpMin = nStart;
2051 chrgText->cpMax = nStart + nLen;
2052 }
2053 TRACE("found at %d-%d\n", nStart, nStart + nLen);
2054 return nStart;
2055 }
2056 if (nCurEnd - nMatched == 0)
2057 {
2058 ME_PrevRun(&pCurPara, &pCurItem, TRUE);
2059 /* Don't care about pCurItem becoming NULL here; it's already taken
2060 * care of in the exterior loop condition */
2061 nCurEnd = pCurItem->member.run.len + nMatched;
2062 }
2063 }
2064 if (pCurItem)
2065 wLastChar = *get_text( &pCurItem->member.run, nCurEnd - nMatched - 1 );
2066 else
2067 wLastChar = ' ';
2068
2069 cursor.nOffset--;
2070 if (cursor.nOffset < 0)
2071 {
2072 ME_PrevRun(&cursor.pPara, &cursor.pRun, TRUE);
2073 cursor.nOffset = cursor.pRun->member.run.len;
2074 }
2075 }
2076 }
2077 TRACE("not found\n");
2078 if (chrgText)
2079 chrgText->cpMin = chrgText->cpMax = -1;
2080 return -1;
2081 }
2082
2083 static int ME_GetTextEx(ME_TextEditor *editor, GETTEXTEX *ex, LPARAM pText)
2084 {
2085 int nChars;
2086 ME_Cursor start;
2087
2088 if (!ex->cb || !pText) return 0;
2089
2090 if (ex->flags & ~(GT_SELECTION | GT_USECRLF))
2091 FIXME("GETTEXTEX flags 0x%08x not supported\n", ex->flags & ~(GT_SELECTION | GT_USECRLF));
2092
2093 if (ex->flags & GT_SELECTION)
2094 {
2095 int from, to;
2096 int nStartCur = ME_GetSelectionOfs(editor, &from, &to);
2097 start = editor->pCursors[nStartCur];
2098 nChars = to - from;
2099 }
2100 else
2101 {
2102 ME_SetCursorToStart(editor, &start);
2103 nChars = INT_MAX;
2104 }
2105 if (ex->codepage == CP_UNICODE)
2106 {
2107 return ME_GetTextW(editor, (LPWSTR)pText, ex->cb / sizeof(WCHAR) - 1,
2108 &start, nChars, ex->flags & GT_USECRLF, FALSE);
2109 }
2110 else
2111 {
2112 /* potentially each char may be a CR, why calculate the exact value with O(N) when
2113 we can just take a bigger buffer? :)
2114 The above assumption still holds with CR/LF counters, since CR->CRLF expansion
2115 occurs only in richedit 2.0 mode, in which line breaks have only one CR
2116 */
2117 int crlfmul = (ex->flags & GT_USECRLF) ? 2 : 1;
2118 DWORD buflen;
2119 LPWSTR buffer;
2120 LRESULT rc;
2121
2122 buflen = min(crlfmul * nChars, ex->cb - 1);
2123 buffer = heap_alloc((buflen + 1) * sizeof(WCHAR));
2124
2125 nChars = ME_GetTextW(editor, buffer, buflen, &start, nChars, ex->flags & GT_USECRLF, FALSE);
2126 rc = WideCharToMultiByte(ex->codepage, 0, buffer, nChars + 1,
2127 (LPSTR)pText, ex->cb, ex->lpDefaultChar, ex->lpUsedDefChar);
2128 if (rc) rc--; /* do not count 0 terminator */
2129
2130 heap_free(buffer);
2131 return rc;
2132 }
2133 }
2134
2135 static int ME_GetTextRange(ME_TextEditor *editor, WCHAR *strText,
2136 const ME_Cursor *start, int nLen, BOOL unicode)
2137 {
2138 if (!strText) return 0;
2139 if (unicode) {
2140 return ME_GetTextW(editor, strText, INT_MAX, start, nLen, FALSE, FALSE);
2141 } else {
2142 int nChars;
2143 WCHAR *p = heap_alloc((nLen+1) * sizeof(*p));
2144 if (!p) return 0;
2145 nChars = ME_GetTextW(editor, p, nLen, start, nLen, FALSE, FALSE);
2146 WideCharToMultiByte(CP_ACP, 0, p, nChars+1, (char *)strText,
2147 nLen+1, NULL, NULL);
2148 heap_free(p);
2149 return nChars;
2150 }
2151 }
2152
2153 static int handle_EM_EXSETSEL( ME_TextEditor *editor, int to, int from )
2154 {
2155 int end;
2156
2157 TRACE("%d - %d\n", to, from );
2158
2159 ME_InvalidateSelection( editor );
2160 end = ME_SetSelection( editor, to, from );
2161 ME_InvalidateSelection( editor );
2162 ITextHost_TxShowCaret( editor->texthost, FALSE );
2163 ME_ShowCaret( editor );
2164 ME_SendSelChange( editor );
2165
2166 return end;
2167 }
2168
2169 typedef struct tagME_GlobalDestStruct
2170 {
2171 HGLOBAL hData;
2172 int nLength;
2173 } ME_GlobalDestStruct;
2174
2175 static DWORD CALLBACK ME_ReadFromHGLOBALUnicode(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
2176 {
2177 ME_GlobalDestStruct *pData = (ME_GlobalDestStruct *)dwCookie;
2178 int i;
2179 WORD *pSrc, *pDest;
2180
2181 cb = cb >> 1;
2182 pDest = (WORD *)lpBuff;
2183 pSrc = GlobalLock(pData->hData);
2184 for (i = 0; i<cb && pSrc[pData->nLength+i]; i++) {
2185 pDest[i] = pSrc[pData->nLength+i];
2186 }
2187 pData->nLength += i;
2188 *pcb = 2*i;
2189 GlobalUnlock(pData->hData);
2190 return 0;
2191 }
2192
2193 static DWORD CALLBACK ME_ReadFromHGLOBALRTF(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb)
2194 {
2195 ME_GlobalDestStruct *pData = (ME_GlobalDestStruct *)dwCookie;
2196 int i;
2197 BYTE *pSrc, *pDest;
2198
2199 pDest = lpBuff;
2200 pSrc = GlobalLock(pData->hData);
2201 for (i = 0; i<cb && pSrc[pData->nLength+i]; i++) {
2202 pDest[i] = pSrc[pData->nLength+i];
2203 }
2204 pData->nLength += i;
2205 *pcb = i;
2206 GlobalUnlock(pData->hData);
2207 return 0;
2208 }
2209
2210 static const WCHAR rtfW[] = {'R','i','c','h',' ','T','e','x','t',' ','F','o','r','m','a','t',0};
2211
2212 static HRESULT paste_rtf(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2213 {
2214 EDITSTREAM es;
2215 ME_GlobalDestStruct gds;
2216 HRESULT hr;
2217
2218 gds.hData = med->u.hGlobal;
2219 gds.nLength = 0;
2220 es.dwCookie = (DWORD_PTR)&gds;
2221 es.pfnCallback = ME_ReadFromHGLOBALRTF;
2222 hr = ME_StreamIn( editor, SF_RTF | SFF_SELECTION, &es, FALSE ) == 0 ? E_FAIL : S_OK;
2223 ReleaseStgMedium( med );
2224 return hr;
2225 }
2226
2227 static HRESULT paste_text(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2228 {
2229 EDITSTREAM es;
2230 ME_GlobalDestStruct gds;
2231 HRESULT hr;
2232
2233 gds.hData = med->u.hGlobal;
2234 gds.nLength = 0;
2235 es.dwCookie = (DWORD_PTR)&gds;
2236 es.pfnCallback = ME_ReadFromHGLOBALUnicode;
2237 hr = ME_StreamIn( editor, SF_TEXT | SF_UNICODE | SFF_SELECTION, &es, FALSE ) == 0 ? E_FAIL : S_OK;
2238 ReleaseStgMedium( med );
2239 return hr;
2240 }
2241
2242 static HRESULT paste_emf(ME_TextEditor *editor, FORMATETC *fmt, STGMEDIUM *med)
2243 {
2244 HRESULT hr;
2245 SIZEL sz = {0, 0};
2246
2247 hr = insert_static_object( editor, med->u.hEnhMetaFile, NULL, &sz );
2248 if (SUCCEEDED(hr))
2249 {
2250 ME_CommitUndo( editor );
2251 ME_UpdateRepaint( editor, FALSE );
2252 }
2253 else
2254 ReleaseStgMedium( med );
2255
2256 return hr;
2257 }
2258
2259 static struct paste_format
2260 {
2261 FORMATETC fmt;
2262 HRESULT (*paste)(ME_TextEditor *, FORMATETC *, STGMEDIUM *);
2263 const WCHAR *name;
2264 } paste_formats[] =
2265 {
2266 {{ -1, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, paste_rtf, rtfW },
2267 {{ CF_UNICODETEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }, paste_text },
2268 {{ CF_ENHMETAFILE, NULL, DVASPECT_CONTENT, -1, TYMED_ENHMF }, paste_emf },
2269 {{ 0 }}
2270 };
2271
2272 static void init_paste_formats(void)
2273 {
2274 struct paste_format *format;
2275 static int done;
2276
2277 if (!done)
2278 {
2279 for (format = paste_formats; format->fmt.cfFormat; format++)
2280 {
2281 if (format->name)
2282 format->fmt.cfFormat = RegisterClipboardFormatW( format->name );
2283 }
2284 done = 1;
2285 }
2286 }
2287
2288 static BOOL paste_special(ME_TextEditor *editor, UINT cf, REPASTESPECIAL *ps, BOOL check_only)
2289 {
2290 HRESULT hr;
2291 STGMEDIUM med;
2292 struct paste_format *format;
2293 IDataObject *data;
2294
2295 /* Protect read-only edit control from modification */
2296 if (editor->styleFlags & ES_READONLY)
2297 {
2298 if (!check_only)
2299 MessageBeep(MB_ICONERROR);
2300 return FALSE;
2301 }
2302
2303 init_paste_formats();
2304
2305 if (ps && ps->dwAspect != DVASPECT_CONTENT)
2306 FIXME("Ignoring aspect %x\n", ps->dwAspect);
2307
2308 hr = OleGetClipboard( &data );
2309 if (hr != S_OK) return FALSE;
2310
2311 if (cf == CF_TEXT) cf = CF_UNICODETEXT;
2312
2313 hr = S_FALSE;
2314 for (format = paste_formats; format->fmt.cfFormat; format++)
2315 {
2316 if (cf && cf != format->fmt.cfFormat) continue;
2317 hr = IDataObject_QueryGetData( data, &format->fmt );
2318 if (hr == S_OK)
2319 {
2320 if (!check_only)
2321 {
2322 hr = IDataObject_GetData( data, &format->fmt, &med );
2323 if (hr != S_OK) goto done;
2324 hr = format->paste( editor, &format->fmt, &med );
2325 }
2326 break;
2327 }
2328 }
2329
2330 done:
2331 IDataObject_Release( data );
2332
2333 return hr == S_OK;
2334 }
2335
2336 static BOOL ME_Copy(ME_TextEditor *editor, const ME_Cursor *start, int nChars)
2337 {
2338 LPDATAOBJECT dataObj = NULL;
2339 HRESULT hr = S_OK;
2340
2341 if (editor->cPasswordMask)
2342 return FALSE; /* Copying or Cutting masked text isn't allowed */
2343
2344 if(editor->lpOleCallback)
2345 {
2346 CHARRANGE range;
2347 range.cpMin = ME_GetCursorOfs(start);
2348 range.cpMax = range.cpMin + nChars;
2349 hr = IRichEditOleCallback_GetClipboardData(editor->lpOleCallback, &range, RECO_COPY, &dataObj);
2350 }
2351 if(FAILED(hr) || !dataObj)
2352 hr = ME_GetDataObject(editor, start, nChars, &dataObj);
2353 if(SUCCEEDED(hr)) {
2354 hr = OleSetClipboard(dataObj);
2355 IDataObject_Release(dataObj);
2356 }
2357 return SUCCEEDED(hr);
2358 }
2359
2360 static BOOL copy_or_cut(ME_TextEditor *editor, BOOL cut)
2361 {
2362 BOOL result;
2363 int offs, num_chars;
2364 int start_cursor = ME_GetSelectionOfs(editor, &offs, &num_chars);
2365 ME_Cursor *sel_start = &editor->pCursors[start_cursor];
2366
2367 if (cut && (editor->styleFlags & ES_READONLY))
2368 {
2369 MessageBeep(MB_ICONERROR);
2370 return FALSE;
2371 }
2372
2373 num_chars -= offs;
2374 result = ME_Copy(editor, sel_start, num_chars);
2375 if (result && cut)
2376 {
2377 ME_InternalDeleteText(editor, sel_start, num_chars, FALSE);
2378 ME_CommitUndo(editor);
2379 ME_UpdateRepaint(editor, TRUE);
2380 }
2381 return result;
2382 }
2383
2384 /* helper to send a msg filter notification */
2385 static BOOL
2386 ME_FilterEvent(ME_TextEditor *editor, UINT msg, WPARAM* wParam, LPARAM* lParam)
2387 {
2388 MSGFILTER msgf;
2389
2390 if (!editor->hWnd || !editor->hwndParent) return FALSE;
2391 msgf.nmhdr.hwndFrom = editor->hWnd;
2392 msgf.nmhdr.idFrom = GetWindowLongW(editor->hWnd, GWLP_ID);
2393 msgf.nmhdr.code = EN_MSGFILTER;
2394 msgf.msg = msg;
2395 msgf.wParam = *wParam;
2396 msgf.lParam = *lParam;
2397 if (SendMessageW(editor->hwndParent, WM_NOTIFY, msgf.nmhdr.idFrom, (LPARAM)&msgf))
2398 return FALSE;
2399 *wParam = msgf.wParam;
2400 *lParam = msgf.lParam;
2401 msgf.wParam = *wParam;
2402
2403 return TRUE;
2404 }
2405
2406 static void ME_UpdateSelectionLinkAttribute(ME_TextEditor *editor)
2407 {
2408 ME_DisplayItem *startPara, *endPara;
2409 ME_DisplayItem *prev_para;
2410 ME_Cursor *from, *to;
2411 ME_Cursor start;
2412 int nChars;
2413
2414 if (!editor->AutoURLDetect_bEnable) return;
2415
2416 ME_GetSelection(editor, &from, &to);
2417
2418 /* Find paragraph previous to the one that contains start cursor */
2419 startPara = from->pPara;
2420 prev_para = startPara->member.para.prev_para;
2421 if (prev_para->type == diParagraph) startPara = prev_para;
2422
2423 /* Find paragraph that contains end cursor */
2424 endPara = to->pPara->member.para.next_para;
2425
2426 start.pPara = startPara;
2427 start.pRun = ME_FindItemFwd(startPara, diRun);
2428 start.nOffset = 0;
2429 nChars = endPara->member.para.nCharOfs - startPara->member.para.nCharOfs;
2430
2431 ME_UpdateLinkAttribute(editor, &start, nChars);
2432 }
2433
2434 static BOOL
2435 ME_KeyDown(ME_TextEditor *editor, WORD nKey)
2436 {
2437 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2438 BOOL shift_is_down = GetKeyState(VK_SHIFT) & 0x8000;
2439
2440 if (editor->bMouseCaptured)
2441 return FALSE;
2442 if (nKey != VK_SHIFT && nKey != VK_CONTROL && nKey != VK_MENU)
2443 editor->nSelectionType = stPosition;
2444
2445 switch (nKey)
2446 {
2447 case VK_LEFT:
2448 case VK_RIGHT:
2449 case VK_HOME:
2450 case VK_END:
2451 editor->nUDArrowX = -1;
2452 /* fall through */
2453 case VK_UP:
2454 case VK_DOWN:
2455 case VK_PRIOR:
2456 case VK_NEXT:
2457 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
2458 ME_ArrowKey(editor, nKey, shift_is_down, ctrl_is_down);
2459 return TRUE;
2460 case VK_BACK:
2461 case VK_DELETE:
2462 editor->nUDArrowX = -1;
2463 /* FIXME backspace and delete aren't the same, they act different wrt paragraph style of the merged paragraph */
2464 if (editor->styleFlags & ES_READONLY)
2465 return FALSE;
2466 if (ME_IsSelection(editor))
2467 {
2468 ME_DeleteSelection(editor);
2469 ME_CommitUndo(editor);
2470 }
2471 else if (nKey == VK_DELETE)
2472 {
2473 /* Delete stops group typing.
2474 * (See MSDN remarks on EM_STOPGROUPTYPING message) */
2475 ME_DeleteTextAtCursor(editor, 1, 1);
2476 ME_CommitUndo(editor);
2477 }
2478 else if (ME_ArrowKey(editor, VK_LEFT, FALSE, FALSE))
2479 {
2480 BOOL bDeletionSucceeded;
2481 /* Backspace can be grouped for a single undo */
2482 ME_ContinueCoalescingTransaction(editor);
2483 bDeletionSucceeded = ME_DeleteTextAtCursor(editor, 1, 1);
2484 if (!bDeletionSucceeded && !editor->bEmulateVersion10) { /* v4.1 */
2485 /* Deletion was prevented so the cursor is moved back to where it was.
2486 * (e.g. this happens when trying to delete cell boundaries)
2487 */
2488 ME_ArrowKey(editor, VK_RIGHT, FALSE, FALSE);
2489 }
2490 ME_CommitCoalescingUndo(editor);
2491 }
2492 else
2493 return TRUE;
2494 ME_MoveCursorFromTableRowStartParagraph(editor);
2495 ME_UpdateSelectionLinkAttribute(editor);
2496 ME_UpdateRepaint(editor, FALSE);
2497 ME_SendRequestResize(editor, FALSE);
2498 return TRUE;
2499 case VK_RETURN:
2500 if (editor->bDialogMode)
2501 {
2502 if (ctrl_is_down)
2503 return TRUE;
2504
2505 if (!(editor->styleFlags & ES_WANTRETURN))
2506 {
2507 if (editor->hwndParent)
2508 {
2509 DWORD dw;
2510 dw = SendMessageW(editor->hwndParent, DM_GETDEFID, 0, 0);
2511 if (HIWORD(dw) == DC_HASDEFID)
2512 {
2513 HWND hwDefCtrl = GetDlgItem(editor->hwndParent, LOWORD(dw));
2514 if (hwDefCtrl)
2515 {
2516 SendMessageW(editor->hwndParent, WM_NEXTDLGCTL, (WPARAM)hwDefCtrl, TRUE);
2517 PostMessageW(hwDefCtrl, WM_KEYDOWN, VK_RETURN, 0);
2518 }
2519 }
2520 }
2521 return TRUE;
2522 }
2523 }
2524
2525 if (editor->styleFlags & ES_MULTILINE)
2526 {
2527 ME_Cursor cursor = editor->pCursors[0];
2528 ME_DisplayItem *para = cursor.pPara;
2529 int from, to;
2530 const WCHAR endl = '\r';
2531 const WCHAR endlv10[] = {'\r','\n'};
2532 ME_Style *style, *eop_style;
2533
2534 if (editor->styleFlags & ES_READONLY) {
2535 MessageBeep(MB_ICONERROR);
2536 return TRUE;
2537 }
2538
2539 ME_GetSelectionOfs(editor, &from, &to);
2540 if (editor->nTextLimit > ME_GetTextLength(editor) - (to-from))
2541 {
2542 if (!editor->bEmulateVersion10) { /* v4.1 */
2543 if (para->member.para.nFlags & MEPF_ROWEND) {
2544 /* Add a new table row after this row. */
2545 para = ME_AppendTableRow(editor, para);
2546 para = para->member.para.next_para;
2547 editor->pCursors[0].pPara = para;
2548 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2549 editor->pCursors[0].nOffset = 0;
2550 editor->pCursors[1] = editor->pCursors[0];
2551 ME_CommitUndo(editor);
2552 ME_CheckTablesForCorruption(editor);
2553 ME_UpdateRepaint(editor, FALSE);
2554 return TRUE;
2555 }
2556 else if (para == editor->pCursors[1].pPara &&
2557 cursor.nOffset + cursor.pRun->member.run.nCharOfs == 0 &&
2558 para->member.para.prev_para->member.para.nFlags & MEPF_ROWSTART &&
2559 !para->member.para.prev_para->member.para.nCharOfs)
2560 {
2561 /* Insert a newline before the table. */
2562 para = para->member.para.prev_para;
2563 para->member.para.nFlags &= ~MEPF_ROWSTART;
2564 editor->pCursors[0].pPara = para;
2565 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2566 editor->pCursors[1] = editor->pCursors[0];
2567 ME_InsertTextFromCursor(editor, 0, &endl, 1,
2568 editor->pCursors[0].pRun->member.run.style);
2569 para = editor->pBuffer->pFirst->member.para.next_para;
2570 ME_SetDefaultParaFormat(editor, &para->member.para.fmt);
2571 para->member.para.nFlags = MEPF_REWRAP;
2572 editor->pCursors[0].pPara = para;
2573 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2574 editor->pCursors[1] = editor->pCursors[0];
2575 para->member.para.next_para->member.para.nFlags |= MEPF_ROWSTART;
2576 ME_CommitCoalescingUndo(editor);
2577 ME_CheckTablesForCorruption(editor);
2578 ME_UpdateRepaint(editor, FALSE);
2579 return TRUE;
2580 }
2581 } else { /* v1.0 - 3.0 */
2582 ME_DisplayItem *para = cursor.pPara;
2583 if (ME_IsInTable(para))
2584 {
2585 if (cursor.pRun->member.run.nFlags & MERF_ENDPARA)
2586 {
2587 if (from == to) {
2588 ME_ContinueCoalescingTransaction(editor);
2589 para = ME_AppendTableRow(editor, para);
2590 editor->pCursors[0].pPara = para;
2591 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2592 editor->pCursors[0].nOffset = 0;
2593 editor->pCursors[1] = editor->pCursors[0];
2594 ME_CommitCoalescingUndo(editor);
2595 ME_UpdateRepaint(editor, FALSE);
2596 return TRUE;
2597 }
2598 } else {
2599 ME_ContinueCoalescingTransaction(editor);
2600 if (cursor.pRun->member.run.nCharOfs + cursor.nOffset == 0 &&
2601 !ME_IsInTable(para->member.para.prev_para))
2602 {
2603 /* Insert newline before table */
2604 cursor.pRun = ME_FindItemBack(para, diRun);
2605 if (cursor.pRun) {
2606 editor->pCursors[0].pRun = cursor.pRun;
2607 editor->pCursors[0].pPara = para->member.para.prev_para;
2608 }
2609 editor->pCursors[0].nOffset = 0;
2610 editor->pCursors[1] = editor->pCursors[0];
2611 ME_InsertTextFromCursor(editor, 0, &endl, 1,
2612 editor->pCursors[0].pRun->member.run.style);
2613 } else {
2614 editor->pCursors[1] = editor->pCursors[0];
2615 para = ME_AppendTableRow(editor, para);
2616 editor->pCursors[0].pPara = para;
2617 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2618 editor->pCursors[0].nOffset = 0;
2619 editor->pCursors[1] = editor->pCursors[0];
2620 }
2621 ME_CommitCoalescingUndo(editor);
2622 ME_UpdateRepaint(editor, FALSE);
2623 return TRUE;
2624 }
2625 }
2626 }
2627
2628 style = ME_GetInsertStyle(editor, 0);
2629
2630 /* Normally the new eop style is the insert style, however in a list it is copied from the existing
2631 eop style (this prevents the list label style changing when the new eop is inserted).
2632 No extra ref is taken here on eop_style. */
2633 if (para->member.para.fmt.wNumbering)
2634 eop_style = para->member.para.eop_run->style;
2635 else
2636 eop_style = style;
2637 ME_ContinueCoalescingTransaction(editor);
2638 if (shift_is_down)
2639 ME_InsertEndRowFromCursor(editor, 0);
2640 else
2641 if (!editor->bEmulateVersion10)
2642 ME_InsertTextFromCursor(editor, 0, &endl, 1, eop_style);
2643 else
2644 ME_InsertTextFromCursor(editor, 0, endlv10, 2, eop_style);
2645 ME_CommitCoalescingUndo(editor);
2646 SetCursor(NULL);
2647
2648 ME_UpdateSelectionLinkAttribute(editor);
2649 ME_UpdateRepaint(editor, FALSE);
2650 ME_SaveTempStyle(editor, style); /* set the temp insert style for the new para */
2651 ME_ReleaseStyle(style);
2652 }
2653 return TRUE;
2654 }
2655 break;
2656 case VK_ESCAPE:
2657 if (editor->bDialogMode && editor->hwndParent)
2658 PostMessageW(editor->hwndParent, WM_CLOSE, 0, 0);
2659 return TRUE;
2660 case VK_TAB:
2661 if (editor->bDialogMode && editor->hwndParent)
2662 SendMessageW(editor->hwndParent, WM_NEXTDLGCTL, shift_is_down, 0);
2663 return TRUE;
2664 case 'A':
2665 if (ctrl_is_down)
2666 {
2667 handle_EM_EXSETSEL( editor, 0, -1 );
2668 return TRUE;
2669 }
2670 break;
2671 case 'V':
2672 if (ctrl_is_down)
2673 return paste_special( editor, 0, NULL, FALSE );
2674 break;
2675 case 'C':
2676 case 'X':
2677 if (ctrl_is_down)
2678 return copy_or_cut(editor, nKey == 'X');
2679 break;
2680 case 'Z':
2681 if (ctrl_is_down)
2682 {
2683 ME_Undo(editor);
2684 return TRUE;
2685 }
2686 break;
2687 case 'Y':
2688 if (ctrl_is_down)
2689 {
2690 ME_Redo(editor);
2691 return TRUE;
2692 }
2693 break;
2694
2695 default:
2696 if (nKey != VK_SHIFT && nKey != VK_CONTROL && nKey && nKey != VK_MENU)
2697 editor->nUDArrowX = -1;
2698 if (ctrl_is_down)
2699 {
2700 if (nKey == 'W')
2701 {
2702 CHARFORMAT2W chf;
2703 char buf[2048];
2704 chf.cbSize = sizeof(chf);
2705
2706 ME_GetSelectionCharFormat(editor, &chf);
2707 ME_DumpStyleToBuf(&chf, buf);
2708 MessageBoxA(NULL, buf, "Style dump", MB_OK);
2709 }
2710 if (nKey == 'Q')
2711 {
2712 ME_CheckCharOffsets(editor);
2713 }
2714 }
2715 }
2716 return FALSE;
2717 }
2718
2719 static LRESULT ME_Char(ME_TextEditor *editor, WPARAM charCode,
2720 LPARAM flags, BOOL unicode)
2721 {
2722 WCHAR wstr;
2723
2724 if (editor->bMouseCaptured)
2725 return 0;
2726
2727 if (unicode)
2728 wstr = (WCHAR)charCode;
2729 else
2730 {
2731 CHAR charA = charCode;
2732 MultiByteToWideChar(CP_ACP, 0, &charA, 1, &wstr, 1);
2733 }
2734
2735 if (editor->styleFlags & ES_READONLY) {
2736 MessageBeep(MB_ICONERROR);
2737 return 0; /* FIXME really 0 ? */
2738 }
2739
2740 if ((unsigned)wstr >= ' ' || wstr == '\t')
2741 {
2742 ME_Cursor cursor = editor->pCursors[0];
2743 ME_DisplayItem *para = cursor.pPara;
2744 int from, to;
2745 BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
2746 ME_GetSelectionOfs(editor, &from, &to);
2747 if (wstr == '\t' &&
2748 /* v4.1 allows tabs to be inserted with ctrl key down */
2749 !(ctrl_is_down && !editor->bEmulateVersion10))
2750 {
2751 ME_DisplayItem *para;
2752 BOOL bSelectedRow = FALSE;
2753
2754 para = cursor.pPara;
2755 if (ME_IsSelection(editor) &&
2756 cursor.pRun->member.run.nCharOfs + cursor.nOffset == 0 &&
2757 to == ME_GetCursorOfs(&editor->pCursors[0]) &&
2758 para->member.para.prev_para->type == diParagraph)
2759 {
2760 para = para->member.para.prev_para;
2761 bSelectedRow = TRUE;
2762 }
2763 if (ME_IsInTable(para))
2764 {
2765 ME_TabPressedInTable(editor, bSelectedRow);
2766 ME_CommitUndo(editor);
2767 return 0;
2768 }
2769 } else if (!editor->bEmulateVersion10) { /* v4.1 */
2770 if (para->member.para.nFlags & MEPF_ROWEND) {
2771 if (from == to) {
2772 para = para->member.para.next_para;
2773 if (para->member.para.nFlags & MEPF_ROWSTART)
2774 para = para->member.para.next_para;
2775 editor->pCursors[0].pPara = para;
2776 editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun);
2777 editor->pCursors[0].nOffset = 0;
2778 editor->pCursors[1] = editor->pCursors[0];
2779 }
2780 }
2781 } else { /* v1.0 - 3.0 */
2782 if (ME_IsInTable(cursor.pRun) &&
2783 cursor.pRun->member.run.nFlags & MERF_ENDPARA &&
2784 from == to)
2785 {
2786 /* Text should not be inserted at the end of the table. */
2787 MessageBeep(-1);
2788 return 0;
2789 }
2790 }
2791 /* FIXME maybe it would make sense to call EM_REPLACESEL instead ? */
2792 /* WM_CHAR is restricted to nTextLimit */
2793 if(editor->nTextLimit > ME_GetTextLength(editor) - (to-from))
2794 {
2795 ME_Style *style = ME_GetInsertStyle(editor, 0);
2796 ME_ContinueCoalescingTransaction(editor);
2797 ME_InsertTextFromCursor(editor, 0, &wstr, 1, style);
2798 ME_ReleaseStyle(style);
2799 ME_CommitCoalescingUndo(editor);
2800 ITextHost_TxSetCursor(editor->texthost, NULL, FALSE);
2801 }
2802
2803 ME_UpdateSelectionLinkAttribute(editor);
2804 ME_UpdateRepaint(editor, FALSE);
2805 }
2806 return 0;
2807 }
2808
2809 /* Process the message and calculate the new click count.
2810 *
2811 * returns: The click count if it is mouse down event, else returns 0. */
2812 static int ME_CalculateClickCount(ME_TextEditor *editor, UINT msg, WPARAM wParam,
2813 LPARAM lParam)
2814 {
2815 static int clickNum = 0;
2816 if (msg < WM_MOUSEFIRST || msg > WM_MOUSELAST)
2817 return 0;
2818
2819 if ((msg == WM_LBUTTONDBLCLK) ||
2820 (msg == WM_RBUTTONDBLCLK) ||
2821 (msg == WM_MBUTTONDBLCLK) ||
2822 (msg == WM_XBUTTONDBLCLK))
2823 {
2824 msg -= (WM_LBUTTONDBLCLK - WM_LBUTTONDOWN);
2825 }
2826
2827 if ((msg == WM_LBUTTONDOWN) ||
2828 (msg == WM_RBUTTONDOWN) ||
2829 (msg == WM_MBUTTONDOWN) ||
2830 (msg == WM_XBUTTONDOWN))
2831 {
2832 static MSG prevClickMsg;
2833 MSG clickMsg;
2834 /* Compare the editor instead of the hwnd so that the this
2835 * can still be done for windowless richedit controls. */
2836 clickMsg.hwnd = (HWND)editor;
2837 clickMsg.message = msg;
2838 clickMsg.wParam = wParam;
2839 clickMsg.lParam = lParam;
2840 clickMsg.time = GetMessageTime();
2841 clickMsg.pt.x = (short)LOWORD(lParam);
2842 clickMsg.pt.y = (short)HIWORD(lParam);
2843 if ((clickNum != 0) &&
2844 (clickMsg.message == prevClickMsg.message) &&
2845 (clickMsg.hwnd == prevClickMsg.hwnd) &&
2846 (clickMsg.wParam == prevClickMsg.wParam) &&
2847 (clickMsg.time - prevClickMsg.time < GetDoubleClickTime()) &&
2848 (abs(clickMsg.pt.x - prevClickMsg.pt.x) < GetSystemMetrics(SM_CXDOUBLECLK)/2) &&
2849 (abs(clickMsg.pt.y - prevClickMsg.pt.y) < GetSystemMetrics(SM_CYDOUBLECLK)/2))
2850 {
2851 clickNum++;
2852 } else {
2853 clickNum = 1;
2854 }
2855 prevClickMsg = clickMsg;
2856 } else {
2857 return 0;
2858 }
2859 return clickNum;
2860 }
2861
2862 static BOOL is_link( ME_Run *run )
2863 {
2864 return (run->style->fmt.dwMask & CFM_LINK) && (run->style->fmt.dwEffects & CFE_LINK);
2865 }
2866
2867 static BOOL ME_SetCursor(ME_TextEditor *editor)
2868 {
2869 ME_Cursor cursor;
2870 POINT pt;
2871 BOOL isExact;
2872 SCROLLBARINFO sbi;
2873 DWORD messagePos = GetMessagePos();
2874 pt.x = (short)LOWORD(messagePos);
2875 pt.y = (short)HIWORD(messagePos);
2876
2877 if (editor->hWnd)
2878 {
2879 sbi.cbSize = sizeof(sbi);
2880 GetScrollBarInfo(editor->hWnd, OBJID_HSCROLL, &sbi);
2881 if (!(sbi.rgstate[0] & (STATE_SYSTEM_INVISIBLE|STATE_SYSTEM_OFFSCREEN)) &&
2882 PtInRect(&sbi.rcScrollBar, pt))
2883 {
2884 ITextHost_TxSetCursor(editor->texthost,
2885 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2886 return TRUE;
2887 }
2888 sbi.cbSize = sizeof(sbi);
2889 GetScrollBarInfo(editor->hWnd, OBJID_VSCROLL, &sbi);
2890 if (!(sbi.rgstate[0] & (STATE_SYSTEM_INVISIBLE|STATE_SYSTEM_OFFSCREEN)) &&
2891 PtInRect(&sbi.rcScrollBar, pt))
2892 {
2893 ITextHost_TxSetCursor(editor->texthost,
2894 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2895 return TRUE;
2896 }
2897 }
2898 ITextHost_TxScreenToClient(editor->texthost, &pt);
2899
2900 if (editor->nSelectionType == stLine && editor->bMouseCaptured) {
2901 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2902 return TRUE;
2903 }
2904 if (!editor->bEmulateVersion10 /* v4.1 */ &&
2905 pt.y < editor->rcFormat.top &&
2906 pt.x < editor->rcFormat.left)
2907 {
2908 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2909 return TRUE;
2910 }
2911 if (pt.y < editor->rcFormat.top || pt.y > editor->rcFormat.bottom)
2912 {
2913 if (editor->bEmulateVersion10) /* v1.0 - 3.0 */
2914 ITextHost_TxSetCursor(editor->texthost,
2915 LoadCursorW(NULL, (WCHAR*)IDC_ARROW), FALSE);
2916 else /* v4.1 */
2917 ITextHost_TxSetCursor(editor->texthost,
2918 LoadCursorW(NULL, (WCHAR*)IDC_IBEAM), TRUE);
2919 return TRUE;
2920 }
2921 if (pt.x < editor->rcFormat.left)
2922 {
2923 ITextHost_TxSetCursor(editor->texthost, hLeft, FALSE);
2924 return TRUE;
2925 }
2926 ME_CharFromPos(editor, pt.x, pt.y, &cursor, &isExact);
2927 if (isExact)
2928 {
2929 ME_Run *run;
2930
2931 run = &cursor.pRun->member.run;
2932 if (is_link( run ))
2933 {
2934 ITextHost_TxSetCursor(editor->texthost,
2935 LoadCursorW(NULL, (WCHAR*)IDC_HAND),
2936 FALSE);
2937 return TRUE;
2938 }
2939
2940 if (ME_IsSelection(editor))
2941 {
2942 int selStart, selEnd;
2943 int offset = ME_GetCursorOfs(&cursor);
2944
2945 ME_GetSelectionOfs(editor, &selStart, &selEnd);
2946 if (selStart <= offset && selEnd >= offset) {
2947 ITextHost_TxSetCursor(editor->texthost,
2948 LoadCursorW(NULL, (WCHAR*)IDC_ARROW),
2949 FALSE);
2950 return TRUE;
2951 }
2952 }
2953 }
2954 ITextHost_TxSetCursor(editor->texthost,
2955 LoadCursorW(NULL, (WCHAR*)IDC_IBEAM), TRUE);
2956 return TRUE;
2957 }
2958
2959 static void ME_SetDefaultFormatRect(ME_TextEditor *editor)
2960 {
2961 ITextHost_TxGetClientRect(editor->texthost, &editor->rcFormat);
2962 editor->rcFormat.top += editor->exStyleFlags & WS_EX_CLIENTEDGE ? 1 : 0;
2963 editor->rcFormat.left += 1 + editor->selofs;
2964 editor->rcFormat.right -= 1;
2965 }
2966
2967 static LONG ME_GetSelectionType(ME_TextEditor *editor)
2968 {
2969 LONG sel_type = SEL_EMPTY;
2970 LONG start, end;
2971
2972 ME_GetSelectionOfs(editor, &start, &end);
2973 if (start == end)
2974 sel_type = SEL_EMPTY;
2975 else
2976 {
2977 LONG object_count = 0, character_count = 0;
2978 int i;
2979
2980 for (i = 0; i < end - start; i++)
2981 {
2982 ME_Cursor cursor;
2983
2984 ME_CursorFromCharOfs(editor, start + i, &cursor);
2985 if (cursor.pRun->member.run.reobj)
2986 object_count++;
2987 else
2988 character_count++;
2989 if (character_count >= 2 && object_count >= 2)
2990 return (SEL_TEXT | SEL_MULTICHAR | SEL_OBJECT | SEL_MULTIOBJECT);
2991 }
2992 if (character_count)
2993 {
2994 sel_type |= SEL_TEXT;
2995 if (character_count >= 2)
2996 sel_type |= SEL_MULTICHAR;
2997 }
2998 if (object_count)
2999 {
3000 sel_type |= SEL_OBJECT;
3001 if (object_count >= 2)
3002 sel_type |= SEL_MULTIOBJECT;
3003 }
3004 }
3005 return sel_type;
3006 }
3007
3008 static BOOL ME_ShowContextMenu(ME_TextEditor *editor, int x, int y)
3009 {
3010 CHARRANGE selrange;
3011 HMENU menu;
3012 int seltype;
3013
3014 if(!editor->lpOleCallback || !editor->hWnd)
3015 return FALSE;
3016 ME_GetSelectionOfs(editor, &selrange.cpMin, &selrange.cpMax);
3017 seltype = ME_GetSelectionType(editor);
3018 if(SUCCEEDED(IRichEditOleCallback_GetContextMenu(editor->lpOleCallback, seltype, NULL, &selrange, &menu)))
3019 {
3020 TrackPopupMenu(menu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, x, y, 0, editor->hwndParent, NULL);
3021 DestroyMenu(menu);
3022 }
3023 return TRUE;
3024 }
3025
3026 ME_TextEditor *ME_MakeEditor(ITextHost *texthost, BOOL bEmulateVersion10)
3027 {
3028 ME_TextEditor *ed = heap_alloc(sizeof(*ed));
3029 int i;
3030 DWORD props;
3031 LONG selbarwidth;
3032
3033 ed->hWnd = NULL;
3034 ed->hwndParent = NULL;
3035 ed->sizeWindow.cx = ed->sizeWindow.cy = 0;
3036 ed->texthost = texthost;
3037 ed->reOle = NULL;
3038 ed->bEmulateVersion10 = bEmulateVersion10;
3039 ed->styleFlags = 0;
3040 ed->exStyleFlags = 0;
3041 ITextHost_TxGetPropertyBits(texthost,
3042 (TXTBIT_RICHTEXT|TXTBIT_MULTILINE|
3043 TXTBIT_READONLY|TXTBIT_USEPASSWORD|
3044 TXTBIT_HIDESELECTION|TXTBIT_SAVESELECTION|
3045 TXTBIT_AUTOWORDSEL|TXTBIT_VERTICAL|
3046 TXTBIT_WORDWRAP|TXTBIT_DISABLEDRAG),
3047 &props);
3048 ITextHost_TxGetScrollBars(texthost, &ed->styleFlags);
3049 ed->styleFlags &= (WS_VSCROLL|WS_HSCROLL|ES_AUTOVSCROLL|
3050 ES_AUTOHSCROLL|ES_DISABLENOSCROLL);
3051 ed->pBuffer = ME_MakeText();
3052 ed->nZoomNumerator = ed->nZoomDenominator = 0;
3053 ed->nAvailWidth = 0; /* wrap to client area */
3054 ME_MakeFirstParagraph(ed);
3055 /* The four cursors are for:
3056 * 0 - The position where the caret is shown
3057 * 1 - The anchored end of the selection (for normal selection)
3058 * 2 & 3 - The anchored start and end respectively for word, line,
3059 * or paragraph selection.
3060 */
3061 ed->nCursors = 4;
3062 ed->pCursors = heap_alloc(ed->nCursors * sizeof(*ed->pCursors));
3063 ME_SetCursorToStart(ed, &ed->pCursors[0]);
3064 ed->pCursors[1] = ed->pCursors[0];
3065 ed->pCursors[2] = ed->pCursors[0];
3066 ed->pCursors[3] = ed->pCursors[1];
3067 ed->nLastTotalLength = ed->nTotalLength = 0;
3068 ed->nLastTotalWidth = ed->nTotalWidth = 0;
3069 ed->nUDArrowX = -1;
3070 ed->rgbBackColor = -1;
3071 ed->hbrBackground = GetSysColorBrush(COLOR_WINDOW);
3072 ed->bCaretAtEnd = FALSE;
3073 ed->nEventMask = 0;
3074 ed->nModifyStep = 0;
3075 ed->nTextLimit = TEXT_LIMIT_DEFAULT;
3076 list_init( &ed->undo_stack );
3077 list_init( &ed->redo_stack );
3078 ed->nUndoStackSize = 0;
3079 ed->nUndoLimit = STACK_SIZE_DEFAULT;
3080 ed->nUndoMode = umAddToUndo;
3081 ed->nParagraphs = 1;
3082 ed->nLastSelStart = ed->nLastSelEnd = 0;
3083 ed->pLastSelStartPara = ed->pLastSelEndPara = ed->pCursors[0].pPara;
3084 ed->bHideSelection = FALSE;
3085 ed->pfnWordBreak = NULL;
3086 ed->lpOleCallback = NULL;
3087 ed->mode = TM_MULTILEVELUNDO | TM_MULTICODEPAGE;
3088 ed->mode |= (props & TXTBIT_RICHTEXT) ? TM_RICHTEXT : TM_PLAINTEXT;
3089 ed->AutoURLDetect_bEnable = FALSE;
3090 ed->bHaveFocus = FALSE;
3091 ed->bDialogMode = FALSE;
3092 ed->bMouseCaptured = FALSE;
3093 for (i=0; i<HFONT_CACHE_SIZE; i++)
3094 {
3095 ed->pFontCache[i].nRefs = 0;
3096 ed->pFontCache[i].nAge = 0;
3097 ed->pFontCache[i].hFont = NULL;
3098 }
3099
3100 ME_CheckCharOffsets(ed);
3101 SetRectEmpty(&ed->rcFormat);
3102 ed->bDefaultFormatRect = TRUE;
3103 ITextHost_TxGetSelectionBarWidth(ed->texthost, &selbarwidth);
3104 if (selbarwidth) {
3105 /* FIXME: Convert selbarwidth from HIMETRIC to pixels */
3106 ed->selofs = SELECTIONBAR_WIDTH;
3107 ed->styleFlags |= ES_SELECTIONBAR;
3108 } else {
3109 ed->selofs = 0;
3110 }
3111 ed->nSelectionType = stPosition;
3112
3113 ed->cPasswordMask = 0;
3114 if (props & TXTBIT_USEPASSWORD)
3115 ITextHost_TxGetPasswordChar(texthost, &ed->cPasswordMask);
3116
3117 if (props & TXTBIT_AUTOWORDSEL)
3118 ed->styleFlags |= ECO_AUTOWORDSELECTION;
3119 if (props & TXTBIT_MULTILINE) {
3120 ed->styleFlags |= ES_MULTILINE;
3121 ed->bWordWrap = (props & TXTBIT_WORDWRAP) != 0;
3122 } else {
3123 ed->bWordWrap = FALSE;
3124 }
3125 if (props & TXTBIT_READONLY)
3126 ed->styleFlags |= ES_READONLY;
3127 if (!(props & TXTBIT_HIDESELECTION))
3128 ed->styleFlags |= ES_NOHIDESEL;
3129 if (props & TXTBIT_SAVESELECTION)
3130 ed->styleFlags |= ES_SAVESEL;
3131 if (props & TXTBIT_VERTICAL)
3132 ed->styleFlags |= ES_VERTICAL;
3133 if (props & TXTBIT_DISABLEDRAG)
3134 ed->styleFlags |= ES_NOOLEDRAGDROP;
3135
3136 ed->notified_cr.cpMin = ed->notified_cr.cpMax = 0;
3137
3138 /* Default scrollbar information */
3139 ed->vert_si.cbSize = sizeof(SCROLLINFO);
3140 ed->vert_si.nMin = 0;
3141 ed->vert_si.nMax = 0;
3142 ed->vert_si.nPage = 0;
3143 ed->vert_si.nPos = 0;
3144
3145 ed->horz_si.cbSize = sizeof(SCROLLINFO);
3146 ed->horz_si.nMin = 0;
3147 ed->horz_si.nMax = 0;
3148 ed->horz_si.nPage = 0;
3149 ed->horz_si.nPos = 0;
3150
3151 ed->wheel_remain = 0;
3152
3153 list_init( &ed->style_list );
3154 list_init( &ed->reobj_list );
3155 OleInitialize(NULL);
3156
3157 return ed;
3158 }
3159
3160 void ME_DestroyEditor(ME_TextEditor *editor)
3161 {
3162 ME_DisplayItem *pFirst = editor->pBuffer->pFirst;
3163 ME_DisplayItem *p = pFirst, *pNext = NULL;
3164 ME_Style *s, *cursor2;
3165 int i;
3166
3167 ME_ClearTempStyle(editor);
3168 ME_EmptyUndoStack(editor);
3169 while(p) {
3170 pNext = p->next;
3171 ME_DestroyDisplayItem(p);
3172 p = pNext;
3173 }
3174
3175 LIST_FOR_EACH_ENTRY_SAFE( s, cursor2, &editor->style_list, ME_Style, entry )
3176 ME_DestroyStyle( s );
3177
3178 ME_ReleaseStyle(editor->pBuffer->pDefaultStyle);
3179 for (i=0; i<HFONT_CACHE_SIZE; i++)
3180 {
3181 if (editor->pFontCache[i].hFont)
3182 DeleteObject(editor->pFontCache[i].hFont);
3183 }
3184 if (editor->rgbBackColor != -1)
3185 DeleteObject(editor->hbrBackground);
3186 if(editor->lpOleCallback)
3187 IRichEditOleCallback_Release(editor->lpOleCallback);
3188 ITextHost_Release(editor->texthost);
3189 if (editor->reOle)
3190 {
3191 IRichEditOle_Release(editor->reOle);
3192 editor->reOle = NULL;
3193 }
3194 OleUninitialize();
3195
3196 heap_free(editor->pBuffer);
3197 heap_free(editor->pCursors);
3198 heap_free(editor);
3199 }
3200
3201 BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
3202 {
3203 TRACE("\n");
3204 switch (fdwReason)
3205 {
3206 case DLL_PROCESS_ATTACH:
3207 DisableThreadLibraryCalls(hinstDLL);
3208 me_heap = HeapCreate (0, 0x10000, 0);
3209 if (!ME_RegisterEditorClass(hinstDLL)) return FALSE;
3210 hLeft = LoadCursorW(hinstDLL, MAKEINTRESOURCEW(OCR_REVERSE));
3211 LookupInit();
3212 break;
3213
3214 case DLL_PROCESS_DETACH:
3215 if (lpvReserved) break;
3216 UnregisterClassW(RICHEDIT_CLASS20W, 0);
3217 UnregisterClassW(MSFTEDIT_CLASS, 0);
3218 UnregisterClassA(RICHEDIT_CLASS20A, 0);
3219 UnregisterClassA("RichEdit50A", 0);
3220 if (ME_ListBoxRegistered)
3221 UnregisterClassW(REListBox20W, 0);
3222 if (ME_ComboBoxRegistered)
3223 UnregisterClassW(REComboBox20W, 0);
3224 LookupCleanup();
3225 HeapDestroy (me_heap);
3226 release_typelib();
3227 break;
3228 }
3229 return TRUE;
3230 }
3231
3232 static inline int get_default_line_height( ME_TextEditor *editor )
3233 {
3234 int height = 0;
3235
3236 if (editor->pBuffer && editor->pBuffer->pDefaultStyle)
3237 height = editor->pBuffer->pDefaultStyle->tm.tmHeight;
3238 if (height <= 0) height = 24;
3239
3240 return height;
3241 }
3242
3243 static inline int calc_wheel_change( int *remain, int amount_per_click )
3244 {
3245 int change = amount_per_click * (float)*remain / WHEEL_DELTA;
3246 *remain -= WHEEL_DELTA * change / amount_per_click;
3247 return change;
3248 }
3249
3250 static const char * const edit_messages[] = {
3251 "EM_GETSEL",
3252 "EM_SETSEL",
3253 "EM_GETRECT",
3254 "EM_SETRECT",
3255 "EM_SETRECTNP",
3256 "EM_SCROLL",
3257 "EM_LINESCROLL",
3258 "EM_SCROLLCARET",
3259 "EM_GETMODIFY",
3260 "EM_SETMODIFY",
3261 "EM_GETLINECOUNT",
3262 "EM_LINEINDEX",
3263 "EM_SETHANDLE",
3264 "EM_GETHANDLE",
3265 "EM_GETTHUMB",
3266 "EM_UNKNOWN_BF",
3267 "EM_UNKNOWN_C0",
3268 "EM_LINELENGTH",
3269 "EM_REPLACESEL",
3270 "EM_UNKNOWN_C3",
3271 "EM_GETLINE",
3272 "EM_LIMITTEXT",
3273 "EM_CANUNDO",
3274 "EM_UNDO",
3275 "EM_FMTLINES",
3276 "EM_LINEFROMCHAR",
3277 "EM_UNKNOWN_CA",
3278 "EM_SETTABSTOPS",
3279 "EM_SETPASSWORDCHAR",
3280 "EM_EMPTYUNDOBUFFER",
3281 "EM_GETFIRSTVISIBLELINE",
3282 "EM_SETREADONLY",
3283 "EM_SETWORDBREAKPROC",
3284 "EM_GETWORDBREAKPROC",
3285 "EM_GETPASSWORDCHAR",
3286 "EM_SETMARGINS",
3287 "EM_GETMARGINS",
3288 "EM_GETLIMITTEXT",
3289 "EM_POSFROMCHAR",
3290 "EM_CHARFROMPOS",
3291 "EM_SETIMESTATUS",
3292 "EM_GETIMESTATUS"
3293 };
3294
3295 static const char * const richedit_messages[] = {
3296 "EM_CANPASTE",
3297 "EM_DISPLAYBAND",
3298 "EM_EXGETSEL",
3299 "EM_EXLIMITTEXT",
3300 "EM_EXLINEFROMCHAR",
3301 "EM_EXSETSEL",
3302 "EM_FINDTEXT",
3303 "EM_FORMATRANGE",
3304 "EM_GETCHARFORMAT",
3305 "EM_GETEVENTMASK",
3306 "EM_GETOLEINTERFACE",
3307 "EM_GETPARAFORMAT",
3308 "EM_GETSELTEXT",
3309 "EM_HIDESELECTION",
3310 "EM_PASTESPECIAL",
3311 "EM_REQUESTRESIZE",
3312 "EM_SELECTIONTYPE",
3313 "EM_SETBKGNDCOLOR",
3314 "EM_SETCHARFORMAT",
3315 "EM_SETEVENTMASK",
3316 "EM_SETOLECALLBACK",
3317 "EM_SETPARAFORMAT",
3318 "EM_SETTARGETDEVICE",
3319 "EM_STREAMIN",
3320 "EM_STREAMOUT",
3321 "EM_GETTEXTRANGE",
3322 "EM_FINDWORDBREAK",
3323 "EM_SETOPTIONS",
3324 "EM_GETOPTIONS",
3325 "EM_FINDTEXTEX",
3326 "EM_GETWORDBREAKPROCEX",
3327 "EM_SETWORDBREAKPROCEX",
3328 "EM_SETUNDOLIMIT",
3329 "EM_UNKNOWN_USER_83",
3330 "EM_REDO",
3331 "EM_CANREDO",
3332 "EM_GETUNDONAME",
3333 "EM_GETREDONAME",
3334 "EM_STOPGROUPTYPING",
3335 "EM_SETTEXTMODE",
3336 "EM_GETTEXTMODE",
3337 "EM_AUTOURLDETECT",
3338 "EM_GETAUTOURLDETECT",
3339 "EM_SETPALETTE",
3340 "EM_GETTEXTEX",
3341 "EM_GETTEXTLENGTHEX",
3342 "EM_SHOWSCROLLBAR",
3343 "EM_SETTEXTEX",
3344 "EM_UNKNOWN_USER_98",
3345 "EM_UNKNOWN_USER_99",
3346 "EM_SETPUNCTUATION",
3347 "EM_GETPUNCTUATION",
3348 "EM_SETWORDWRAPMODE",
3349 "EM_GETWORDWRAPMODE",
3350 "EM_SETIMECOLOR",
3351 "EM_GETIMECOLOR",
3352 "EM_SETIMEOPTIONS",
3353 "EM_GETIMEOPTIONS",
3354 "EM_CONVPOSITION",
3355 "EM_UNKNOWN_USER_109",
3356 "EM_UNKNOWN_USER_110",
3357 "EM_UNKNOWN_USER_111",
3358 "EM_UNKNOWN_USER_112",
3359 "EM_UNKNOWN_USER_113",
3360 "EM_UNKNOWN_USER_114",
3361 "EM_UNKNOWN_USER_115",
3362 "EM_UNKNOWN_USER_116",
3363 "EM_UNKNOWN_USER_117",
3364 "EM_UNKNOWN_USER_118",
3365 "EM_UNKNOWN_USER_119",
3366 "EM_SETLANGOPTIONS",
3367 "EM_GETLANGOPTIONS",
3368 "EM_GETIMECOMPMODE",
3369 "EM_FINDTEXTW",
3370 "EM_FINDTEXTEXW",
3371 "EM_RECONVERSION",
3372 "EM_SETIMEMODEBIAS",
3373 "EM_GETIMEMODEBIAS"
3374 };
3375
3376 static const char *
3377 get_msg_name(UINT msg)
3378 {
3379 if (msg >= EM_GETSEL && msg <= EM_CHARFROMPOS)
3380 return edit_messages[msg - EM_GETSEL];
3381 if (msg >= EM_CANPASTE && msg <= EM_GETIMEMODEBIAS)
3382 return richedit_messages[msg - EM_CANPASTE];
3383 return "";
3384 }
3385
3386 static void ME_LinkNotify(ME_TextEditor *editor, UINT msg, WPARAM wParam, LPARAM lParam)
3387 {
3388 int x,y;
3389 BOOL isExact;
3390 ME_Cursor cursor; /* The start of the clicked text. */
3391
3392 ENLINK info;
3393 x = (short)LOWORD(lParam);
3394 y = (short)HIWORD(lParam);
3395 ME_CharFromPos(editor, x, y, &cursor, &isExact);
3396 if (!isExact) return;
3397
3398 if (is_link( &cursor.pRun->member.run ))
3399 { /* The clicked run has CFE_LINK set */
3400 ME_DisplayItem *di;
3401
3402 info.nmhdr.hwndFrom = NULL;
3403 info.nmhdr.idFrom = 0;
3404 info.nmhdr.code = EN_LINK;
3405 info.msg = msg;
3406 info.wParam = wParam;
3407 info.lParam = lParam;
3408 cursor.nOffset = 0;
3409
3410 /* find the first contiguous run with CFE_LINK set */
3411 info.chrg.cpMin = ME_GetCursorOfs(&cursor);
3412 di = cursor.pRun;
3413 while (ME_PrevRun( NULL, &di, FALSE ) && is_link( &di->member.run ))
3414 info.chrg.cpMin -= di->member.run.len;
3415
3416 /* find the last contiguous run with CFE_LINK set */
3417 info.chrg.cpMax = ME_GetCursorOfs(&cursor) + cursor.pRun->member.run.len;
3418 di = cursor.pRun;
3419 while (ME_NextRun( NULL, &di, FALSE ) && is_link( &di->member.run ))
3420 info.chrg.cpMax += di->member.run.len;
3421
3422 ITextHost_TxNotify(editor->texthost, info.nmhdr.code, &info);
3423 }
3424 }
3425
3426 void ME_ReplaceSel(ME_TextEditor *editor, BOOL can_undo, const WCHAR *str, int len)
3427 {
3428 int from, to, nStartCursor;
3429 ME_Style *style;
3430
3431 nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3432 style = ME_GetSelectionInsertStyle(editor);
3433 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to-from, FALSE);
3434 ME_InsertTextFromCursor(editor, 0, str, len, style);
3435 ME_ReleaseStyle(style);
3436 /* drop temporary style if line end */
3437 /*
3438 * FIXME question: does abc\n mean: put abc,
3439 * clear temp style, put \n? (would require a change)
3440 */
3441 if (len>0 && str[len-1] == '\n')
3442 ME_ClearTempStyle(editor);
3443 ME_CommitUndo(editor);
3444 ME_UpdateSelectionLinkAttribute(editor);
3445 if (!can_undo)
3446 ME_EmptyUndoStack(editor);
3447 ME_UpdateRepaint(editor, FALSE);
3448 }
3449
3450 static void ME_SetText(ME_TextEditor *editor, void *text, BOOL unicode)
3451 {
3452 LONG codepage = unicode ? CP_UNICODE : CP_ACP;
3453 int textLen;
3454
3455 LPWSTR wszText = ME_ToUnicode(codepage, text, &textLen);
3456 ME_InsertTextFromCursor(editor, 0, wszText, textLen, editor->pBuffer->pDefaultStyle);
3457 ME_EndToUnicode(codepage, wszText);
3458 }
3459
3460 static LRESULT ME_WmCreate(ME_TextEditor *editor, LPARAM lParam, BOOL unicode)
3461 {
3462 CREATESTRUCTW *createW = (CREATESTRUCTW*)lParam;
3463 CREATESTRUCTA *createA = (CREATESTRUCTA*)lParam;
3464 void *text = NULL;
3465 INT max;
3466
3467 if (lParam)
3468 text = unicode ? (void*)createW->lpszName : (void*)createA->lpszName;
3469
3470 ME_SetDefaultFormatRect(editor);
3471
3472 max = (editor->styleFlags & ES_DISABLENOSCROLL) ? 1 : 0;
3473 if (~editor->styleFlags & ES_DISABLENOSCROLL || editor->styleFlags & WS_VSCROLL)
3474 ITextHost_TxSetScrollRange(editor->texthost, SB_VERT, 0, max, TRUE);
3475
3476 if (~editor->styleFlags & ES_DISABLENOSCROLL || editor->styleFlags & WS_HSCROLL)
3477 ITextHost_TxSetScrollRange(editor->texthost, SB_HORZ, 0, max, TRUE);
3478
3479 if (editor->styleFlags & ES_DISABLENOSCROLL)
3480 {
3481 if (editor->styleFlags & WS_VSCROLL)
3482 {
3483 ITextHost_TxEnableScrollBar(editor->texthost, SB_VERT, ESB_DISABLE_BOTH);
3484 ITextHost_TxShowScrollBar(editor->texthost, SB_VERT, TRUE);
3485 }
3486 if (editor->styleFlags & WS_HSCROLL)
3487 {
3488 ITextHost_TxEnableScrollBar(editor->texthost, SB_HORZ, ESB_DISABLE_BOTH);
3489 ITextHost_TxShowScrollBar(editor->texthost, SB_HORZ, TRUE);
3490 }
3491 }
3492
3493 if (text)
3494 {
3495 ME_SetText(editor, text, unicode);
3496 ME_SetCursorToStart(editor, &editor->pCursors[0]);
3497 ME_SetCursorToStart(editor, &editor->pCursors[1]);
3498 }
3499
3500 ME_CommitUndo(editor);
3501 ME_WrapMarkedParagraphs(editor);
3502 ME_MoveCaret(editor);
3503 return 0;
3504 }
3505
3506
3507 #define UNSUPPORTED_MSG(e) \
3508 case e: \
3509 FIXME(#e ": stub\n"); \
3510 *phresult = S_FALSE; \
3511 return 0;
3512
3513 /* Handle messages for windowless and windowed richedit controls.
3514 *
3515 * The LRESULT that is returned is a return value for window procs,
3516 * and the phresult parameter is the COM return code needed by the
3517 * text services interface. */
3518 LRESULT ME_HandleMessage(ME_TextEditor *editor, UINT msg, WPARAM wParam,
3519 LPARAM lParam, BOOL unicode, HRESULT* phresult)
3520 {
3521 *phresult = S_OK;
3522
3523 switch(msg) {
3524
3525 UNSUPPORTED_MSG(EM_DISPLAYBAND)
3526 UNSUPPORTED_MSG(EM_FINDWORDBREAK)
3527 UNSUPPORTED_MSG(EM_FMTLINES)
3528 UNSUPPORTED_MSG(EM_FORMATRANGE)
3529 UNSUPPORTED_MSG(EM_GETBIDIOPTIONS)
3530 UNSUPPORTED_MSG(EM_GETEDITSTYLE)
3531 UNSUPPORTED_MSG(EM_GETIMECOMPMODE)
3532 UNSUPPORTED_MSG(EM_GETIMESTATUS)
3533 UNSUPPORTED_MSG(EM_SETIMESTATUS)
3534 UNSUPPORTED_MSG(EM_GETLANGOPTIONS)
3535 UNSUPPORTED_MSG(EM_GETREDONAME)
3536 UNSUPPORTED_MSG(EM_GETTYPOGRAPHYOPTIONS)
3537 UNSUPPORTED_MSG(EM_GETUNDONAME)
3538 UNSUPPORTED_MSG(EM_GETWORDBREAKPROCEX)
3539 UNSUPPORTED_MSG(EM_SETBIDIOPTIONS)
3540 UNSUPPORTED_MSG(EM_SETEDITSTYLE)
3541 UNSUPPORTED_MSG(EM_SETLANGOPTIONS)
3542 UNSUPPORTED_MSG(EM_SETMARGINS)
3543 UNSUPPORTED_MSG(EM_SETPALETTE)
3544 UNSUPPORTED_MSG(EM_SETTABSTOPS)
3545 UNSUPPORTED_MSG(EM_SETTYPOGRAPHYOPTIONS)
3546 UNSUPPORTED_MSG(EM_SETWORDBREAKPROCEX)
3547
3548 /* Messages specific to Richedit controls */
3549
3550 case EM_STREAMIN:
3551 return ME_StreamIn(editor, wParam, (EDITSTREAM*)lParam, TRUE);
3552 case EM_STREAMOUT:
3553 return ME_StreamOut(editor, wParam, (EDITSTREAM *)lParam);
3554 case WM_GETDLGCODE:
3555 {
3556 UINT code = DLGC_WANTCHARS|DLGC_WANTTAB|DLGC_WANTARROWS;
3557
3558 if (lParam)
3559 editor->bDialogMode = TRUE;
3560 if (editor->styleFlags & ES_MULTILINE)
3561 code |= DLGC_WANTMESSAGE;
3562 if (!(editor->styleFlags & ES_SAVESEL))
3563 code |= DLGC_HASSETSEL;
3564 return code;
3565 }
3566 case EM_EMPTYUNDOBUFFER:
3567 ME_EmptyUndoStack(editor);
3568 return 0;
3569 case EM_GETSEL:
3570 {
3571 /* Note: wParam/lParam can be NULL */
3572 UINT from, to;
3573 PUINT pfrom = wParam ? (PUINT)wParam : &from;
3574 PUINT pto = lParam ? (PUINT)lParam : &to;
3575 ME_GetSelectionOfs(editor, (int *)pfrom, (int *)pto);
3576 if ((*pfrom|*pto) & 0xFFFF0000)
3577 return -1;
3578 return MAKELONG(*pfrom,*pto);
3579 }
3580 case EM_EXGETSEL:
3581 {
3582 CHARRANGE *pRange = (CHARRANGE *)lParam;
3583 ME_GetSelectionOfs(editor, &pRange->cpMin, &pRange->cpMax);
3584 TRACE("EM_EXGETSEL = (%d,%d)\n", pRange->cpMin, pRange->cpMax);
3585 return 0;
3586 }
3587 case EM_SETUNDOLIMIT:
3588 {
3589 if ((int)wParam < 0)
3590 editor->nUndoLimit = STACK_SIZE_DEFAULT;
3591 else
3592 editor->nUndoLimit = min(wParam, STACK_SIZE_MAX);
3593 /* Setting a max stack size keeps wine from getting killed
3594 for hogging memory. Windows allocates all this memory at once, so
3595 no program would realistically set a value above our maximum. */
3596 return editor->nUndoLimit;
3597 }
3598 case EM_CANUNDO:
3599 return !list_empty( &editor->undo_stack );
3600 case EM_CANREDO:
3601 return !list_empty( &editor->redo_stack );
3602 case WM_UNDO: /* FIXME: actually not the same */
3603 case EM_UNDO:
3604 return ME_Undo(editor);
3605 case EM_REDO:
3606 return ME_Redo(editor);
3607 case EM_GETOPTIONS:
3608 {
3609 /* these flags are equivalent to the ES_* counterparts */
3610 DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
3611 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN | ECO_SELECTIONBAR;
3612 DWORD settings = editor->styleFlags & mask;
3613
3614 return settings;
3615 }
3616 case EM_SETFONTSIZE:
3617 {
3618 CHARFORMAT2W cf;
3619 LONG tmp_size, size;
3620 BOOL is_increase = ((LONG)wParam > 0);
3621
3622 if (editor->mode & TM_PLAINTEXT)
3623 return FALSE;
3624
3625 cf.cbSize = sizeof(cf);
3626 cf.dwMask = CFM_SIZE;
3627 ME_GetSelectionCharFormat(editor, &cf);
3628 tmp_size = (cf.yHeight / 20) + wParam;
3629
3630 if (tmp_size <= 1)
3631 size = 1;
3632 else if (tmp_size > 12 && tmp_size < 28 && tmp_size % 2)
3633 size = tmp_size + (is_increase ? 1 : -1);
3634 else if (tmp_size > 28 && tmp_size < 36)
3635 size = is_increase ? 36 : 28;
3636 else if (tmp_size > 36 && tmp_size < 48)
3637 size = is_increase ? 48 : 36;
3638 else if (tmp_size > 48 && tmp_size < 72)
3639 size = is_increase ? 72 : 48;
3640 else if (tmp_size > 72 && tmp_size < 80)
3641 size = is_increase ? 80 : 72;
3642 else if (tmp_size > 80 && tmp_size < 1638)
3643 size = 10 * (is_increase ? (tmp_size / 10 + 1) : (tmp_size / 10));
3644 else if (tmp_size >= 1638)
3645 size = 1638;
3646 else
3647 size = tmp_size;
3648
3649 cf.yHeight = size * 20; /* convert twips to points */
3650 ME_SetSelectionCharFormat(editor, &cf);
3651 ME_CommitUndo(editor);
3652 ME_WrapMarkedParagraphs(editor);
3653 ME_UpdateScrollBar(editor);
3654 ME_Repaint(editor);
3655
3656 return TRUE;
3657 }
3658 case EM_SETOPTIONS:
3659 {
3660 /* these flags are equivalent to ES_* counterparts, except for
3661 * ECO_AUTOWORDSELECTION that doesn't have an ES_* counterpart,
3662 * but is still stored in editor->styleFlags. */
3663 const DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
3664 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN |
3665 ECO_SELECTIONBAR | ECO_AUTOWORDSELECTION;
3666 DWORD settings = mask & editor->styleFlags;
3667 DWORD oldSettings = settings;
3668 DWORD changedSettings;
3669
3670 switch(wParam)
3671 {
3672 case ECOOP_SET:
3673 settings = lParam;
3674 break;
3675 case ECOOP_OR:
3676 settings |= lParam;
3677 break;
3678 case ECOOP_AND:
3679 settings &= lParam;
3680 break;
3681 case ECOOP_XOR:
3682 settings ^= lParam;
3683 }
3684 changedSettings = oldSettings ^ settings;
3685
3686 if (changedSettings) {
3687 editor->styleFlags = (editor->styleFlags & ~mask) | (settings & mask);
3688
3689 if (changedSettings & ECO_SELECTIONBAR)
3690 {
3691 ITextHost_TxInvalidateRect(editor->texthost, &editor->rcFormat, TRUE);
3692 if (settings & ECO_SELECTIONBAR) {
3693 assert(!editor->selofs);
3694 editor->selofs = SELECTIONBAR_WIDTH;
3695 editor->rcFormat.left += editor->selofs;
3696 } else {
3697 editor->rcFormat.left -= editor->selofs;
3698 editor->selofs = 0;
3699 }
3700 ME_RewrapRepaint(editor);
3701 }
3702
3703 if ((changedSettings & settings & ES_NOHIDESEL) && !editor->bHaveFocus)
3704 ME_InvalidateSelection( editor );
3705
3706 if (changedSettings & settings & ECO_VERTICAL)
3707 FIXME("ECO_VERTICAL not implemented yet!\n");
3708 if (changedSettings & settings & ECO_AUTOHSCROLL)
3709 FIXME("ECO_AUTOHSCROLL not implemented yet!\n");
3710 if (changedSettings & settings & ECO_AUTOVSCROLL)
3711 FIXME("ECO_AUTOVSCROLL not implemented yet!\n");
3712 if (changedSettings & settings & ECO_WANTRETURN)
3713 FIXME("ECO_WANTRETURN not implemented yet!\n");
3714 if (changedSettings & settings & ECO_AUTOWORDSELECTION)
3715 FIXME("ECO_AUTOWORDSELECTION not implemented yet!\n");
3716 }
3717
3718 return settings;
3719 }
3720 case EM_SETSEL:
3721 {
3722 return handle_EM_EXSETSEL( editor, wParam, lParam );
3723 }
3724 case EM_SETSCROLLPOS:
3725 {
3726 POINT *point = (POINT *)lParam;
3727 ME_ScrollAbs(editor, point->x, point->y);
3728 return 0;
3729 }
3730 case EM_AUTOURLDETECT:
3731 {
3732 if (wParam==1 || wParam ==0)
3733 {
3734 editor->AutoURLDetect_bEnable = (BOOL)wParam;
3735 return 0;
3736 }
3737 return E_INVALIDARG;
3738 }
3739 case EM_GETAUTOURLDETECT:
3740 {
3741 return editor->AutoURLDetect_bEnable;
3742 }
3743 case EM_EXSETSEL:
3744 {
3745 CHARRANGE range = *(CHARRANGE *)lParam;
3746
3747 return handle_EM_EXSETSEL( editor, range.cpMin, range.cpMax );
3748 }
3749 case EM_SHOWSCROLLBAR:
3750 {
3751 DWORD flags;
3752
3753 switch (wParam)
3754 {
3755 case SB_HORZ:
3756 flags = WS_HSCROLL;
3757 break;
3758 case SB_VERT:
3759 flags = WS_VSCROLL;
3760 break;
3761 case SB_BOTH:
3762 flags = WS_HSCROLL|WS_VSCROLL;
3763 break;
3764 default:
3765 return 0;
3766 }
3767
3768 if (lParam) {
3769 editor->styleFlags |= flags;
3770 if (flags & WS_HSCROLL)
3771 ITextHost_TxShowScrollBar(editor->texthost, SB_HORZ,
3772 editor->nTotalWidth > editor->sizeWindow.cx);
3773 if (flags & WS_VSCROLL)
3774 ITextHost_TxShowScrollBar(editor->texthost, SB_VERT,
3775 editor->nTotalLength > editor->sizeWindow.cy);
3776 } else {
3777 editor->styleFlags &= ~flags;
3778 ITextHost_TxShowScrollBar(editor->texthost, wParam, FALSE);
3779 }
3780 return 0;
3781 }
3782 case EM_SETTEXTEX:
3783 {
3784 LPWSTR wszText;
3785 SETTEXTEX *pStruct = (SETTEXTEX *)wParam;
3786 int from, to, len;
3787 ME_Style *style;
3788 BOOL bRtf, bUnicode, bSelection, bUTF8;
3789 int oldModify = editor->nModifyStep;
3790 static const char utf8_bom[] = {0xef, 0xbb, 0xbf};
3791
3792 if (!pStruct) return 0;
3793
3794 /* If we detect ascii rtf at the start of the string,
3795 * we know it isn't unicode. */
3796 bRtf = (lParam && (!strncmp((char *)lParam, "{\\rtf", 5) ||
3797 !strncmp((char *)lParam, "{\\urtf", 6)));
3798 bUnicode = !bRtf && pStruct->codepage == CP_UNICODE;
3799 bUTF8 = (lParam && (!strncmp((char *)lParam, utf8_bom, 3)));
3800
3801 TRACE("EM_SETTEXTEX - %s, flags %d, cp %d\n",
3802 bUnicode ? debugstr_w((LPCWSTR)lParam) : debugstr_a((LPCSTR)lParam),
3803 pStruct->flags, pStruct->codepage);
3804
3805 bSelection = (pStruct->flags & ST_SELECTION) != 0;
3806 if (bSelection) {
3807 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
3808 style = ME_GetSelectionInsertStyle(editor);
3809 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to - from, FALSE);
3810 } else {
3811 ME_Cursor start;
3812 ME_SetCursorToStart(editor, &start);
3813 ME_InternalDeleteText(editor, &start, ME_GetTextLength(editor), FALSE);
3814 style = editor->pBuffer->pDefaultStyle;
3815 }
3816
3817 if (bRtf) {
3818 ME_StreamInRTFString(editor, bSelection, (char *)lParam);
3819 if (bSelection) {
3820 /* FIXME: The length returned doesn't include the rtf control
3821 * characters, only the actual text. */
3822 len = lParam ? strlen((char *)lParam) : 0;
3823 }
3824 } else {
3825 if (bUTF8 && !bUnicode) {
3826 wszText = ME_ToUnicode(CP_UTF8, (void *)(lParam+3), &len);
3827 ME_InsertTextFromCursor(editor, 0, wszText, len, style);
3828 ME_EndToUnicode(CP_UTF8, wszText);
3829 } else {
3830 wszText = ME_ToUnicode(pStruct->codepage, (void *)lParam, &len);
3831 ME_InsertTextFromCursor(editor, 0, wszText, len, style);
3832 ME_EndToUnicode(pStruct->codepage, wszText);
3833 }
3834 }
3835
3836 if (bSelection) {
3837 ME_ReleaseStyle(style);
3838 ME_UpdateSelectionLinkAttribute(editor);
3839 } else {
3840 ME_Cursor cursor;
3841 len = 1;
3842 ME_SetCursorToStart(editor, &cursor);
3843 ME_UpdateLinkAttribute(editor, &cursor, INT_MAX);
3844 }
3845 ME_CommitUndo(editor);
3846 if (!(pStruct->flags & ST_KEEPUNDO))
3847 {
3848 editor->nModifyStep = oldModify;
3849 ME_EmptyUndoStack(editor);
3850 }
3851 ME_UpdateRepaint(editor, FALSE);
3852 return len;
3853 }
3854 case EM_SELECTIONTYPE:
3855 return ME_GetSelectionType(editor);
3856 case EM_SETBKGNDCOLOR:
3857 {
3858 LRESULT lColor;
3859 if (editor->rgbBackColor != -1) {
3860 DeleteObject(editor->hbrBackground);
3861 lColor = editor->rgbBackColor;
3862 }
3863 else lColor = ITextHost_TxGetSysColor(editor->texthost, COLOR_WINDOW);
3864
3865 if (wParam)
3866 {
3867 editor->rgbBackColor = -1;
3868 editor->hbrBackground = GetSysColorBrush(COLOR_WINDOW);
3869 }
3870 else
3871 {
3872 editor->rgbBackColor = lParam;
3873 editor->hbrBackground = CreateSolidBrush(editor->rgbBackColor);
3874 }
3875 ITextHost_TxInvalidateRect(editor->texthost, NULL, TRUE);
3876 return lColor;
3877 }
3878 case EM_GETMODIFY:
3879 return editor->nModifyStep == 0 ? 0 : -1;
3880 case EM_SETMODIFY:
3881 {
3882 if (wParam)
3883 editor->nModifyStep = 1;
3884 else
3885 editor->nModifyStep = 0;
3886
3887 return 0;
3888 }
3889 case EM_SETREADONLY:
3890 {
3891 if (wParam)
3892 editor->styleFlags |= ES_READONLY;
3893 else
3894 editor->styleFlags &= ~ES_READONLY;
3895 return 1;
3896 }
3897 case EM_SETEVENTMASK:
3898 {
3899 DWORD nOldMask = editor->nEventMask;
3900
3901 editor->nEventMask = lParam;
3902 return nOldMask;
3903 }
3904 case EM_GETEVENTMASK:
3905 return editor->nEventMask;
3906 case EM_SETCHARFORMAT:
3907 {
3908 CHARFORMAT2W buf, *p;
3909 BOOL bRepaint = TRUE;
3910 p = ME_ToCF2W(&buf, (CHARFORMAT2W *)lParam);
3911 if (p == NULL) return 0;
3912 if (wParam & SCF_ALL) {
3913 if (editor->mode & TM_PLAINTEXT) {
3914 ME_SetDefaultCharFormat(editor, p);
3915 } else {
3916 ME_Cursor start;
3917 ME_SetCursorToStart(editor, &start);
3918 ME_SetCharFormat(editor, &start, NULL, p);
3919 editor->nModifyStep = 1;
3920 }
3921 } else if (wParam & SCF_SELECTION) {
3922 if (editor->mode & TM_PLAINTEXT)
3923 return 0;
3924 if (wParam & SCF_WORD) {
3925 ME_Cursor start;
3926 ME_Cursor end = editor->pCursors[0];
3927 ME_MoveCursorWords(editor, &end, +1);
3928 start = end;
3929 ME_MoveCursorWords(editor, &start, -1);
3930 ME_SetCharFormat(editor, &start, &end, p);
3931 }
3932 bRepaint = ME_IsSelection(editor);
3933 ME_SetSelectionCharFormat(editor, p);
3934 if (bRepaint) editor->nModifyStep = 1;
3935 } else { /* SCF_DEFAULT */
3936 ME_SetDefaultCharFormat(editor, p);
3937 }
3938 ME_CommitUndo(editor);
3939 if (bRepaint)
3940 {
3941 ME_WrapMarkedParagraphs(editor);
3942 ME_UpdateScrollBar(editor);
3943 ME_Repaint(editor);
3944 }
3945 return 1;
3946 }
3947 case EM_GETCHARFORMAT:
3948 {
3949 CHARFORMAT2W tmp, *dst = (CHARFORMAT2W *)lParam;
3950 if (dst->cbSize != sizeof(CHARFORMATA) &&
3951 dst->cbSize != sizeof(CHARFORMATW) &&
3952 dst->cbSize != sizeof(CHARFORMAT2A) &&
3953 dst->cbSize != sizeof(CHARFORMAT2W))
3954 return 0;
3955 tmp.cbSize = sizeof(tmp);
3956 if (!wParam)
3957 ME_GetDefaultCharFormat(editor, &tmp);
3958 else
3959 ME_GetSelectionCharFormat(editor, &tmp);
3960 ME_CopyToCFAny(dst, &tmp);
3961 return tmp.dwMask;
3962 }
3963 case EM_SETPARAFORMAT:
3964 {
3965 BOOL result = ME_SetSelectionParaFormat(editor, (PARAFORMAT2 *)lParam);
3966 ME_WrapMarkedParagraphs(editor);
3967 ME_UpdateScrollBar(editor);
3968 ME_Repaint(editor);
3969 ME_CommitUndo(editor);
3970 return result;
3971 }
3972 case EM_GETPARAFORMAT:
3973 ME_GetSelectionParaFormat(editor, (PARAFORMAT2 *)lParam);
3974 return ((PARAFORMAT2 *)lParam)->dwMask;
3975 case EM_GETFIRSTVISIBLELINE:
3976 {
3977 ME_DisplayItem *p = editor->pBuffer->pFirst;
3978 int y = editor->vert_si.nPos;
3979 int ypara = 0;
3980 int count = 0;
3981 int ystart, yend;
3982 while(p) {
3983 p = ME_FindItemFwd(p, diStartRowOrParagraphOrEnd);
3984 if (p->type == diTextEnd)
3985 break;
3986 if (p->type == diParagraph) {
3987 ypara = p->member.para.pt.y;
3988 continue;
3989 }
3990 ystart = ypara + p->member.row.pt.y;
3991 yend = ystart + p->member.row.nHeight;
3992 if (y < yend) {
3993 break;
3994 }
3995 count++;
3996 }
3997 return count;
3998 }
3999 case EM_HIDESELECTION:
4000 {
4001 editor->bHideSelection = (wParam != 0);
4002 ME_InvalidateSelection(editor);
4003 return 0;
4004 }
4005 case EM_LINESCROLL:
4006 {
4007 if (!(editor->styleFlags & ES_MULTILINE))
4008 return FALSE;
4009 ME_ScrollDown( editor, lParam * get_default_line_height( editor ) );
4010 return TRUE;
4011 }
4012 case WM_CLEAR:
4013 {
4014 int from, to;
4015 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
4016 ME_InternalDeleteText(editor, &editor->pCursors[nStartCursor], to-from, FALSE);
4017 ME_CommitUndo(editor);
4018 ME_UpdateRepaint(editor, TRUE);
4019 return 0;
4020 }
4021 case EM_REPLACESEL:
4022 {
4023 int len = 0;
4024 LONG codepage = unicode ? CP_UNICODE : CP_ACP;
4025 LPWSTR wszText = ME_ToUnicode(codepage, (void *)lParam, &len);
4026
4027 TRACE("EM_REPLACESEL - %s\n", debugstr_w(wszText));
4028
4029 ME_ReplaceSel(editor, !!wParam, wszText, len);
4030 ME_EndToUnicode(codepage, wszText);
4031 return len;
4032 }
4033 case EM_SCROLLCARET:
4034 ME_EnsureVisible(editor, &editor->pCursors[0]);
4035 return 0;
4036 case WM_SETFONT:
4037 {
4038 LOGFONTW lf;
4039 CHARFORMAT2W fmt;
4040 HDC hDC;
4041 BOOL bRepaint = LOWORD(lParam);
4042
4043 if (!wParam)
4044 wParam = (WPARAM)GetStockObject(SYSTEM_FONT);
4045
4046 if (!GetObjectW((HGDIOBJ)wParam, sizeof(LOGFONTW), &lf))
4047 return 0;
4048
4049 hDC = ITextHost_TxGetDC(editor->texthost);
4050 ME_CharFormatFromLogFont(hDC, &lf, &fmt);
4051 ITextHost_TxReleaseDC(editor->texthost, hDC);
4052 if (editor->mode & TM_RICHTEXT) {
4053 ME_Cursor start;
4054 ME_SetCursorToStart(editor, &start);
4055 ME_SetCharFormat(editor, &start, NULL, &fmt);
4056 }
4057 ME_SetDefaultCharFormat(editor, &fmt);
4058
4059 ME_CommitUndo(editor);
4060 ME_MarkAllForWrapping(editor);
4061 ME_WrapMarkedParagraphs(editor);
4062 ME_UpdateScrollBar(editor);
4063 if (bRepaint)
4064 ME_Repaint(editor);
4065 return 0;
4066 }
4067 case WM_SETTEXT:
4068 {
4069 ME_Cursor cursor;
4070 ME_SetCursorToStart(editor, &cursor);
4071 ME_InternalDeleteText(editor, &cursor, ME_GetTextLength(editor), FALSE);
4072 if (lParam)
4073 {
4074 TRACE("WM_SETTEXT lParam==%lx\n",lParam);
4075 if (!strncmp((char *)lParam, "{\\rtf", 5) ||
4076 !strncmp((char *)lParam, "{\\urtf", 6))
4077 {
4078 /* Undocumented: WM_SETTEXT supports RTF text */
4079 ME_StreamInRTFString(editor, 0, (char *)lParam);
4080 }
4081 else
4082 ME_SetText(editor, (void*)lParam, unicode);
4083 }
4084 else
4085 TRACE("WM_SETTEXT - NULL\n");
4086 ME_SetCursorToStart(editor, &cursor);
4087 ME_UpdateLinkAttribute(editor, &cursor, INT_MAX);
4088 ME_SetSelection(editor, 0, 0);
4089 editor->nModifyStep = 0;
4090 ME_CommitUndo(editor);
4091 ME_EmptyUndoStack(editor);
4092 ME_UpdateRepaint(editor, FALSE);
4093 return 1;
4094 }
4095 case EM_CANPASTE:
4096 return paste_special( editor, 0, NULL, TRUE );
4097 case WM_PASTE:
4098 case WM_MBUTTONDOWN:
4099 wParam = 0;
4100 lParam = 0;
4101 /* fall through */
4102 case EM_PASTESPECIAL:
4103 paste_special( editor, wParam, (REPASTESPECIAL *)lParam, FALSE );
4104 return 0;
4105 case WM_CUT:
4106 case WM_COPY:
4107 copy_or_cut(editor, msg == WM_CUT);
4108 return 0;
4109 case WM_GETTEXTLENGTH:
4110 {
4111 GETTEXTLENGTHEX how;
4112
4113 /* CR/LF conversion required in 2.0 mode, verbatim in 1.0 mode */
4114 how.flags = GTL_CLOSE | (editor->bEmulateVersion10 ? 0 : GTL_USECRLF) | GTL_NUMCHARS;
4115 how.codepage = unicode ? CP_UNICODE : CP_ACP;
4116 return ME_GetTextLengthEx(editor, &how);
4117 }
4118 case EM_GETTEXTLENGTHEX:
4119 return ME_GetTextLengthEx(editor, (GETTEXTLENGTHEX *)wParam);
4120 case WM_GETTEXT:
4121 {
4122 GETTEXTEX ex;
4123 ex.cb = wParam * (unicode ? sizeof(WCHAR) : sizeof(CHAR));
4124 ex.flags = GT_USECRLF;
4125 ex.codepage = unicode ? CP_UNICODE : CP_ACP;
4126 ex.lpDefaultChar = NULL;
4127 ex.lpUsedDefChar = NULL;
4128 return ME_GetTextEx(editor, &ex, lParam);
4129 }
4130 case EM_GETTEXTEX:
4131 return ME_GetTextEx(editor, (GETTEXTEX*)wParam, lParam);
4132 case EM_GETSELTEXT:
4133 {
4134 int nFrom, nTo, nStartCur = ME_GetSelectionOfs(editor, &nFrom, &nTo);
4135 ME_Cursor *from = &editor->pCursors[nStartCur];
4136 return ME_GetTextRange(editor, (WCHAR *)lParam, from,
4137 nTo - nFrom, unicode);
4138 }
4139 case EM_GETSCROLLPOS:
4140 {
4141 POINT *point = (POINT *)lParam;
4142 point->x = editor->horz_si.nPos;
4143 point->y = editor->vert_si.nPos;
4144 /* 16-bit scaled value is returned as stored in scrollinfo */
4145 if (editor->horz_si.nMax > 0xffff)
4146 point->x = MulDiv(point->x, 0xffff, editor->horz_si.nMax);
4147 if (editor->vert_si.nMax > 0xffff)
4148 point->y = MulDiv(point->y, 0xffff, editor->vert_si.nMax);
4149 return 1;
4150 }
4151 case EM_GETTEXTRANGE:
4152 {
4153 TEXTRANGEW *rng = (TEXTRANGEW *)lParam;
4154 ME_Cursor start;
4155 int nStart = rng->chrg.cpMin;
4156 int nEnd = rng->chrg.cpMax;
4157 int textlength = ME_GetTextLength(editor);
4158
4159 TRACE("EM_GETTEXTRANGE min=%d max=%d unicode=%d textlength=%d\n",
4160 rng->chrg.cpMin, rng->chrg.cpMax, unicode, textlength);
4161 if (nStart < 0) return 0;
4162 if ((nStart == 0 && nEnd == -1) || nEnd > textlength)
4163 nEnd = textlength;
4164 if (nStart >= nEnd) return 0;
4165
4166 ME_CursorFromCharOfs(editor, nStart, &start);
4167 return ME_GetTextRange(editor, rng->lpstrText, &start, nEnd - nStart, unicode);
4168 }
4169 case EM_GETLINE:
4170 {
4171 ME_DisplayItem *run;
4172 const unsigned int nMaxChars = *(WORD *) lParam;
4173 unsigned int nCharsLeft = nMaxChars;
4174 char *dest = (char *) lParam;
4175 BOOL wroteNull = FALSE;
4176
4177 TRACE("EM_GETLINE: row=%d, nMaxChars=%d (%s)\n", (int) wParam, nMaxChars,
4178 unicode ? "Unicode" : "Ansi");
4179
4180 run = ME_FindRowWithNumber(editor, wParam);
4181 if (run == NULL)
4182 return 0;
4183
4184 while (nCharsLeft && (run = ME_FindItemFwd(run, diRunOrStartRow))
4185 && run->type == diRun)
4186 {
4187 WCHAR *str = get_text( &run->member.run, 0 );
4188 unsigned int nCopy;
4189
4190 nCopy = min(nCharsLeft, run->member.run.len);
4191
4192 if (unicode)
4193 memcpy(dest, str, nCopy * sizeof(WCHAR));
4194 else
4195 nCopy = WideCharToMultiByte(CP_ACP, 0, str, nCopy, dest,
4196 nCharsLeft, NULL, NULL);
4197 dest += nCopy * (unicode ? sizeof(WCHAR) : 1);
4198 nCharsLeft -= nCopy;
4199 }
4200
4201 /* append line termination, space allowing */
4202 if (nCharsLeft > 0)
4203 {
4204 if (unicode)
4205 *((WCHAR *)dest) = '\0';
4206 else
4207 *dest = '\0';
4208 nCharsLeft--;
4209 wroteNull = TRUE;
4210 }
4211
4212 TRACE("EM_GETLINE: got %u characters\n", nMaxChars - nCharsLeft);
4213 return nMaxChars - nCharsLeft - (wroteNull ? 1 : 0);
4214 }
4215 case EM_GETLINECOUNT:
4216 {
4217 ME_DisplayItem *item = editor->pBuffer->pFirst->next;
4218 int nRows = 0;
4219
4220 ME_DisplayItem *prev_para = NULL, *last_para = NULL;
4221
4222 while (item != editor->pBuffer->pLast)
4223 {
4224 assert(item->type == diParagraph);
4225 prev_para = ME_FindItemBack(item, diRun);
4226 if (prev_para) {
4227 assert(prev_para->member.run.nFlags & MERF_ENDPARA);
4228 }
4229 nRows += item->member.para.nRows;
4230 item = item->member.para.next_para;
4231 }
4232 last_para = ME_FindItemBack(item, diRun);
4233 assert(last_para);
4234 assert(last_para->member.run.nFlags & MERF_ENDPARA);
4235 if (editor->bEmulateVersion10 && prev_para &&
4236 last_para->member.run.nCharOfs == 0 &&
4237 prev_para->member.run.len == 1 &&
4238 *get_text( &prev_para->member.run, 0 ) == '\r')
4239 {
4240 /* In 1.0 emulation, the last solitary \r at the very end of the text
4241 (if one exists) is NOT a line break.
4242 FIXME: this is an ugly hack. This should have a more regular model. */
4243 nRows--;
4244 }
4245
4246 TRACE("EM_GETLINECOUNT: nRows==%d\n", nRows);
4247 return max(1, nRows);
4248 }
4249 case EM_LINEFROMCHAR:
4250 {
4251 if (wParam == -1)
4252 return ME_RowNumberFromCharOfs(editor, ME_GetCursorOfs(&editor->pCursors[1]));
4253 else
4254 return ME_RowNumberFromCharOfs(editor, wParam);
4255 }
4256 case EM_EXLINEFROMCHAR:
4257 {
4258 if (lParam == -1)
4259 return ME_RowNumberFromCharOfs(editor, ME_GetCursorOfs(&editor->pCursors[1]));
4260 else
4261 return ME_RowNumberFromCharOfs(editor, lParam);
4262 }
4263 case EM_LINEINDEX:
4264 {
4265 ME_DisplayItem *item, *para;
4266 int nCharOfs;
4267
4268 if (wParam == -1)
4269 item = ME_FindItemBack(editor->pCursors[0].pRun, diStartRow);
4270 else
4271 item = ME_FindRowWithNumber(editor, wParam);
4272 if (!item)
4273 return -1;
4274 para = ME_GetParagraph(item);
4275 item = ME_FindItemFwd(item, diRun);
4276 nCharOfs = para->member.para.nCharOfs + item->member.run.nCharOfs;
4277 TRACE("EM_LINEINDEX: nCharOfs==%d\n", nCharOfs);
4278 return nCharOfs;
4279 }
4280 case EM_LINELENGTH:
4281 {
4282 ME_DisplayItem *item, *item_end;
4283 int nChars = 0, nThisLineOfs = 0, nNextLineOfs = 0;
4284 ME_DisplayItem *para, *run;
4285
4286 if (wParam > ME_GetTextLength(editor))
4287 return 0;
4288 if (wParam == -1)
4289 {
4290 FIXME("EM_LINELENGTH: returning number of unselected characters on lines with selection unsupported.\n");
4291 return 0;
4292 }
4293 ME_RunOfsFromCharOfs(editor, wParam, &para, &run, NULL);
4294 item = ME_RowStart(run);
4295 nThisLineOfs = ME_CharOfsFromRunOfs(editor, para, ME_FindItemFwd(item, diRun), 0);
4296 item_end = ME_FindItemFwd(item, diStartRowOrParagraphOrEnd);
4297 if (item_end->type == diStartRow) {
4298 nNextLineOfs = ME_CharOfsFromRunOfs(editor, para, ME_FindItemFwd(item_end, diRun), 0);
4299 } else {
4300 ME_DisplayItem *endRun = ME_FindItemBack(item_end, diRun);
4301 assert(endRun && endRun->member.run.nFlags & MERF_ENDPARA);
4302 nNextLineOfs = item_end->member.para.nCharOfs - endRun->member.run.len;
4303 }
4304 nChars = nNextLineOfs - nThisLineOfs;
4305 TRACE("EM_LINELENGTH(%ld)==%d\n",wParam, nChars);
4306 return nChars;
4307 }
4308 case EM_EXLIMITTEXT:
4309 {
4310 if ((int)lParam < 0)
4311 return 0;
4312 if (lParam == 0)
4313 editor->nTextLimit = 65536;
4314 else
4315 editor->nTextLimit = (int) lParam;
4316 return 0;
4317 }
4318 case EM_LIMITTEXT:
4319 {
4320 if (wParam == 0)
4321 editor->nTextLimit = 65536;
4322 else
4323 editor->nTextLimit = (int) wParam;
4324 return 0;
4325 }
4326 case EM_GETLIMITTEXT:
4327 {
4328 return editor->nTextLimit;
4329 }
4330 case EM_FINDTEXT:
4331 {
4332 LRESULT r;
4333 if(!unicode){
4334 FINDTEXTA *ft = (FINDTEXTA *)lParam;
4335 int nChars = MultiByteToWideChar(CP_ACP, 0, ft->lpstrText, -1, NULL, 0);
4336 WCHAR *tmp;
4337
4338 if ((tmp = heap_alloc(nChars * sizeof(*tmp))) != NULL)
4339 MultiByteToWideChar(CP_ACP, 0, ft->lpstrText, -1, tmp, nChars);
4340 r = ME_FindText(editor, wParam, &ft->chrg, tmp, NULL);
4341 heap_free(tmp);
4342 }else{
4343 FINDTEXTW *ft = (FINDTEXTW *)lParam;
4344 r = ME_FindText(editor, wParam, &ft->chrg, ft->lpstrText, NULL);
4345 }
4346 return r;
4347 }
4348 case EM_FINDTEXTEX:
4349 {
4350 LRESULT r;
4351 if(!unicode){
4352 FINDTEXTEXA *ex = (FINDTEXTEXA *)lParam;
4353 int nChars = MultiByteToWideChar(CP_ACP, 0, ex->lpstrText, -1, NULL, 0);
4354 WCHAR *tmp;
4355
4356 if ((tmp = heap_alloc(nChars * sizeof(*tmp))) != NULL)
4357 MultiByteToWideChar(CP_ACP, 0, ex->lpstrText, -1, tmp, nChars);
4358 r = ME_FindText(editor, wParam, &ex->chrg, tmp, &ex->chrgText);
4359 heap_free(tmp);
4360 }else{
4361 FINDTEXTEXW *ex = (FINDTEXTEXW *)lParam;
4362 r = ME_FindText(editor, wParam, &ex->chrg, ex->lpstrText, &ex->chrgText);
4363 }
4364 return r;
4365 }
4366 case EM_FINDTEXTW:
4367 {
4368 FINDTEXTW *ft = (FINDTEXTW *)lParam;
4369 return ME_FindText(editor, wParam, &ft->chrg, ft->lpstrText, NULL);
4370 }
4371 case EM_FINDTEXTEXW:
4372 {
4373 FINDTEXTEXW *ex = (FINDTEXTEXW *)lParam;
4374 return ME_FindText(editor, wParam, &ex->chrg, ex->lpstrText, &ex->chrgText);
4375 }
4376 case EM_GETZOOM:
4377 if (!wParam || !lParam)
4378 return FALSE;
4379 *(int *)wParam = editor->nZoomNumerator;
4380 *(int *)lParam = editor->nZoomDenominator;
4381 return TRUE;
4382 case EM_SETZOOM:
4383 return ME_SetZoom(editor, wParam, lParam);
4384 case EM_CHARFROMPOS:
4385 {
4386 ME_Cursor cursor;
4387 if (ME_CharFromPos(editor, ((POINTL *)lParam)->x, ((POINTL *)lParam)->y,
4388 &cursor, NULL))
4389 return ME_GetCursorOfs(&cursor);
4390 else
4391 return -1;
4392 }
4393 case EM_POSFROMCHAR:
4394 {
4395 ME_DisplayItem *pPara, *pRun;
4396 int nCharOfs, nOffset, nLength;
4397 POINTL pt = {0,0};
4398
4399 nCharOfs = wParam;
4400 /* detect which API version we're dealing with */
4401 if (wParam >= 0x40000)
4402 nCharOfs = lParam;
4403 nLength = ME_GetTextLength(editor);
4404 nCharOfs = min(nCharOfs, nLength);
4405 nCharOfs = max(nCharOfs, 0);
4406
4407 ME_RunOfsFromCharOfs(editor, nCharOfs, &pPara, &pRun, &nOffset);
4408 assert(pRun->type == diRun);
4409 pt.y = pRun->member.run.pt.y;
4410 pt.x = pRun->member.run.pt.x + ME_PointFromChar(editor, &pRun->member.run, nOffset, TRUE);
4411 pt.y += pPara->member.para.pt.y + editor->rcFormat.top;
4412 pt.x += editor->rcFormat.left;
4413
4414 pt.x -= editor->horz_si.nPos;
4415 pt.y -= editor->vert_si.nPos;
4416
4417 if (wParam >= 0x40000) {
4418 *(POINTL *)wParam = pt;
4419 }
4420 return (wParam >= 0x40000) ? 0 : MAKELONG( pt.x, pt.y );
4421 }
4422 case WM_CREATE:
4423 return ME_WmCreate(editor, lParam, unicode);
4424 case WM_DESTROY:
4425 ME_DestroyEditor(editor);
4426 return 0;
4427 case WM_SETCURSOR:
4428 {
4429 POINT cursor_pos;
4430 if (wParam == (WPARAM)editor->hWnd && GetCursorPos(&cursor_pos) &&
4431 ScreenToClient(editor->hWnd, &cursor_pos))
4432 ME_LinkNotify(editor, msg, 0, MAKELPARAM(cursor_pos.x, cursor_pos.y));
4433 return ME_SetCursor(editor);
4434 }
4435 case WM_LBUTTONDBLCLK:
4436 case WM_LBUTTONDOWN:
4437 {
4438 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4439 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4440 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4441 return 0;
4442 ITextHost_TxSetFocus(editor->texthost);
4443 ME_LButtonDown(editor, (short)LOWORD(lParam), (short)HIWORD(lParam),
4444 ME_CalculateClickCount(editor, msg, wParam, lParam));
4445 ITextHost_TxSetCapture(editor->texthost, TRUE);
4446 editor->bMouseCaptured = TRUE;
4447 ME_LinkNotify(editor, msg, wParam, lParam);
4448 if (!ME_SetCursor(editor)) goto do_default;
4449 break;
4450 }
4451 case WM_MOUSEMOVE:
4452 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4453 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4454 return 0;
4455 if (editor->bMouseCaptured)
4456 ME_MouseMove(editor, (short)LOWORD(lParam), (short)HIWORD(lParam));
4457 else
4458 ME_LinkNotify(editor, msg, wParam, lParam);
4459 /* Set cursor if mouse is captured, since WM_SETCURSOR won't be received. */
4460 if (editor->bMouseCaptured)
4461 ME_SetCursor(editor);
4462 break;
4463 case WM_LBUTTONUP:
4464 if (editor->bMouseCaptured) {
4465 ITextHost_TxSetCapture(editor->texthost, FALSE);
4466 editor->bMouseCaptured = FALSE;
4467 }
4468 if (editor->nSelectionType == stDocument)
4469 editor->nSelectionType = stPosition;
4470 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4471 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4472 return 0;
4473 else
4474 {
4475 ME_SetCursor(editor);
4476 ME_LinkNotify(editor, msg, wParam, lParam);
4477 }
4478 break;
4479 case WM_RBUTTONUP:
4480 case WM_RBUTTONDOWN:
4481 case WM_RBUTTONDBLCLK:
4482 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4483 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4484 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4485 return 0;
4486 ME_LinkNotify(editor, msg, wParam, lParam);
4487 goto do_default;
4488 case WM_CONTEXTMENU:
4489 if (!ME_ShowContextMenu(editor, (short)LOWORD(lParam), (short)HIWORD(lParam)))
4490 goto do_default;
4491 break;
4492 case WM_SETFOCUS:
4493 editor->bHaveFocus = TRUE;
4494 ME_ShowCaret(editor);
4495 ME_SendOldNotify(editor, EN_SETFOCUS);
4496 if (!editor->bHideSelection && !(editor->styleFlags & ES_NOHIDESEL))
4497 ME_InvalidateSelection( editor );
4498 return 0;
4499 case WM_KILLFOCUS:
4500 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4501 editor->bHaveFocus = FALSE;
4502 editor->wheel_remain = 0;
4503 ME_HideCaret(editor);
4504 ME_SendOldNotify(editor, EN_KILLFOCUS);
4505 if (!editor->bHideSelection && !(editor->styleFlags & ES_NOHIDESEL))
4506 ME_InvalidateSelection( editor );
4507 return 0;
4508 case WM_COMMAND:
4509 TRACE("editor wnd command = %d\n", LOWORD(wParam));
4510 return 0;
4511 case WM_KEYUP:
4512 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4513 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4514 return 0;
4515 goto do_default;
4516 case WM_KEYDOWN:
4517 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4518 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4519 return 0;
4520 if (ME_KeyDown(editor, LOWORD(wParam)))
4521 return 0;
4522 goto do_default;
4523 case WM_CHAR:
4524 if ((editor->nEventMask & ENM_KEYEVENTS) &&
4525 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4526 return 0;
4527 return ME_Char(editor, wParam, lParam, unicode);
4528 case WM_UNICHAR:
4529 if (unicode)
4530 {
4531 if(wParam == UNICODE_NOCHAR) return TRUE;
4532 if(wParam <= 0x000fffff)
4533 {
4534 if(wParam > 0xffff) /* convert to surrogates */
4535 {
4536 wParam -= 0x10000;
4537 ME_Char(editor, (wParam >> 10) + 0xd800, 0, TRUE);
4538 ME_Char(editor, (wParam & 0x03ff) + 0xdc00, 0, TRUE);
4539 } else {
4540 ME_Char(editor, wParam, 0, TRUE);
4541 }
4542 }
4543 return 0;
4544 }
4545 break;
4546 case EM_STOPGROUPTYPING:
4547 ME_CommitUndo(editor); /* End coalesced undos for typed characters */
4548 return 0;
4549 case WM_HSCROLL:
4550 {
4551 const int scrollUnit = 7;
4552
4553 switch(LOWORD(wParam))
4554 {
4555 case SB_LEFT:
4556 ME_ScrollAbs(editor, 0, 0);
4557 break;
4558 case SB_RIGHT:
4559 ME_ScrollAbs(editor,
4560 editor->horz_si.nMax - (int)editor->horz_si.nPage,
4561 editor->vert_si.nMax - (int)editor->vert_si.nPage);
4562 break;
4563 case SB_LINELEFT:
4564 ME_ScrollLeft(editor, scrollUnit);
4565 break;
4566 case SB_LINERIGHT:
4567 ME_ScrollRight(editor, scrollUnit);
4568 break;
4569 case SB_PAGELEFT:
4570 ME_ScrollLeft(editor, editor->sizeWindow.cx);
4571 break;
4572 case SB_PAGERIGHT:
4573 ME_ScrollRight(editor, editor->sizeWindow.cx);
4574 break;
4575 case SB_THUMBTRACK:
4576 case SB_THUMBPOSITION:
4577 {
4578 int pos = HIWORD(wParam);
4579 if (editor->horz_si.nMax > 0xffff)
4580 pos = MulDiv(pos, editor->horz_si.nMax, 0xffff);
4581 ME_HScrollAbs(editor, pos);
4582 break;
4583 }
4584 }
4585 break;
4586 }
4587 case EM_SCROLL: /* fall through */
4588 case WM_VSCROLL:
4589 {
4590 int origNPos;
4591 int lineHeight = get_default_line_height( editor );
4592
4593 origNPos = editor->vert_si.nPos;
4594
4595 switch(LOWORD(wParam))
4596 {
4597 case SB_TOP:
4598 ME_ScrollAbs(editor, 0, 0);
4599 break;
4600 case SB_BOTTOM:
4601 ME_ScrollAbs(editor,
4602 editor->horz_si.nMax - (int)editor->horz_si.nPage,
4603 editor->vert_si.nMax - (int)editor->vert_si.nPage);
4604 break;
4605 case SB_LINEUP:
4606 ME_ScrollUp(editor,lineHeight);
4607 break;
4608 case SB_LINEDOWN:
4609 ME_ScrollDown(editor,lineHeight);
4610 break;
4611 case SB_PAGEUP:
4612 ME_ScrollUp(editor,editor->sizeWindow.cy);
4613 break;
4614 case SB_PAGEDOWN:
4615 ME_ScrollDown(editor,editor->sizeWindow.cy);
4616 break;
4617 case SB_THUMBTRACK:
4618 case SB_THUMBPOSITION:
4619 {
4620 int pos = HIWORD(wParam);
4621 if (editor->vert_si.nMax > 0xffff)
4622 pos = MulDiv(pos, editor->vert_si.nMax, 0xffff);
4623 ME_VScrollAbs(editor, pos);
4624 break;
4625 }
4626 }
4627 if (msg == EM_SCROLL)
4628 return 0x00010000 | (((editor->vert_si.nPos - origNPos)/lineHeight) & 0xffff);
4629 break;
4630 }
4631 case WM_MOUSEWHEEL:
4632 {
4633 int delta;
4634 BOOL ctrl_is_down;
4635
4636 if ((editor->nEventMask & ENM_MOUSEEVENTS) &&
4637 !ME_FilterEvent(editor, msg, &wParam, &lParam))
4638 return 0;
4639
4640 ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000;
4641
4642 delta = GET_WHEEL_DELTA_WPARAM(wParam);
4643
4644 /* if scrolling changes direction, ignore left overs */
4645 if ((delta < 0 && editor->wheel_remain < 0) ||
4646 (delta > 0 && editor->wheel_remain > 0))
4647 editor->wheel_remain += delta;
4648 else
4649 editor->wheel_remain = delta;
4650
4651 if (editor->wheel_remain)
4652 {
4653 if (ctrl_is_down) {
4654 int numerator;
4655 if (!editor->nZoomNumerator || !editor->nZoomDenominator)
4656 {
4657 numerator = 100;
4658 } else {
4659 numerator = editor->nZoomNumerator * 100 / editor->nZoomDenominator;
4660 }
4661 numerator += calc_wheel_change( &editor->wheel_remain, 10 );
4662 if (numerator >= 10 && numerator <= 500)
4663 ME_SetZoom(editor, numerator, 100);
4664 } else {
4665 UINT max_lines = 3;
4666 int lines = 0;
4667
4668 SystemParametersInfoW( SPI_GETWHEELSCROLLLINES, 0, &max_lines, 0 );
4669 if (max_lines)
4670 lines = calc_wheel_change( &editor->wheel_remain, (int)max_lines );
4671 if (lines)
4672 ME_ScrollDown( editor, -lines * get_default_line_height( editor ) );
4673 }
4674 }
4675 break;
4676 }
4677 case EM_GETRECT:
4678 {
4679 *((RECT *)lParam) = editor->rcFormat;
4680 if (editor->bDefaultFormatRect)
4681 ((RECT *)lParam)->left -= editor->selofs;
4682 return 0;
4683 }
4684 case EM_SETRECT:
4685 case EM_SETRECTNP:
4686 {
4687 if (lParam)
4688 {
4689 int border = 0;
4690 RECT clientRect;
4691 RECT *rc = (RECT *)lParam;
4692
4693 border = editor->exStyleFlags & WS_EX_CLIENTEDGE ? 1 : 0;
4694 ITextHost_TxGetClientRect(editor->texthost, &clientRect);
4695 if (wParam == 0)
4696 {
4697 editor->rcFormat.top = max(0, rc->top - border);
4698 editor->rcFormat.left = max(0, rc->left - border);
4699 editor->rcFormat.bottom = min(clientRect.bottom, rc->bottom);
4700 editor->rcFormat.right = min(clientRect.right, rc->right + border);
4701 } else if (wParam == 1) {
4702 /* MSDN incorrectly says a wParam value of 1 causes the
4703 * lParam rect to be used as a relative offset,
4704 * however, the tests show it just prevents min/max bound
4705 * checking. */
4706 editor->rcFormat.top = rc->top - border;
4707 editor->rcFormat.left = rc->left - border;
4708 editor->rcFormat.bottom = rc->bottom;
4709 editor->rcFormat.right = rc->right + border;
4710 } else {
4711 return 0;
4712 }
4713 editor->bDefaultFormatRect = FALSE;
4714 }
4715 else
4716 {
4717 ME_SetDefaultFormatRect(editor);
4718 editor->bDefaultFormatRect = TRUE;
4719 }
4720 ME_MarkAllForWrapping(editor);
4721 ME_WrapMarkedParagraphs(editor);
4722 ME_UpdateScrollBar(editor);
4723 if (msg != EM_SETRECTNP)
4724 ME_Repaint(editor);
4725 return 0;
4726 }
4727 case EM_REQUESTRESIZE:
4728 ME_SendRequestResize(editor, TRUE);
4729 return 0;
4730 case WM_SETREDRAW:
4731 goto do_default;
4732 case WM_WINDOWPOSCHANGED:
4733 {
4734 RECT clientRect;
4735 WINDOWPOS *winpos = (WINDOWPOS *)lParam;
4736
4737 if (winpos->flags & SWP_NOCLIENTSIZE) goto do_default;
4738 ITextHost_TxGetClientRect(editor->texthost, &clientRect);
4739 if (editor->bDefaultFormatRect) {
4740 ME_SetDefaultFormatRect(editor);
4741 } else {
4742 editor->rcFormat.right += clientRect.right - editor->prevClientRect.right;
4743 editor->rcFormat.bottom += clientRect.bottom - editor->prevClientRect.bottom;
4744 }
4745 editor->prevClientRect = clientRect;
4746 ME_RewrapRepaint(editor);
4747 goto do_default;
4748 }
4749 /* IME messages to make richedit controls IME aware */
4750 case WM_IME_SETCONTEXT:
4751 case WM_IME_CONTROL:
4752 case WM_IME_SELECT:
4753 case WM_IME_COMPOSITIONFULL:
4754 return 0;
4755 case WM_IME_STARTCOMPOSITION:
4756 {
4757 editor->imeStartIndex=ME_GetCursorOfs(&editor->pCursors[0]);
4758 ME_DeleteSelection(editor);
4759 ME_CommitUndo(editor);
4760 ME_UpdateRepaint(editor, FALSE);
4761 return 0;
4762 }
4763 case WM_IME_COMPOSITION:
4764 {
4765 HIMC hIMC;
4766
4767 ME_Style *style = ME_GetInsertStyle(editor, 0);
4768 hIMC = ITextHost_TxImmGetContext(editor->texthost);
4769 ME_DeleteSelection(editor);
4770 ME_SaveTempStyle(editor, style);
4771 if (lParam & (GCS_RESULTSTR|GCS_COMPSTR))
4772 {
4773 LPWSTR lpCompStr = NULL;
4774 DWORD dwBufLen;
4775 DWORD dwIndex = lParam & GCS_RESULTSTR;
4776 if (!dwIndex)
4777 dwIndex = GCS_COMPSTR;
4778
4779 dwBufLen = ImmGetCompositionStringW(hIMC, dwIndex, NULL, 0);
4780 lpCompStr = HeapAlloc(GetProcessHeap(),0,dwBufLen + sizeof(WCHAR));
4781 ImmGetCompositionStringW(hIMC, dwIndex, lpCompStr, dwBufLen);
4782 lpCompStr[dwBufLen/sizeof(WCHAR)] = 0;
4783 ME_InsertTextFromCursor(editor,0,lpCompStr,dwBufLen/sizeof(WCHAR),style);
4784 HeapFree(GetProcessHeap(), 0, lpCompStr);
4785
4786 if (dwIndex == GCS_COMPSTR)
4787 ME_SetSelection(editor,editor->imeStartIndex,
4788 editor->imeStartIndex + dwBufLen/sizeof(WCHAR));
4789 }
4790 ME_ReleaseStyle(style);
4791 ME_CommitUndo(editor);
4792 ME_UpdateRepaint(editor, FALSE);
4793 return 0;
4794 }
4795 case WM_IME_ENDCOMPOSITION:
4796 {
4797 ME_DeleteSelection(editor);
4798 editor->imeStartIndex=-1;
4799 return 0;
4800 }
4801 case EM_GETOLEINTERFACE:
4802 {
4803 if (!editor->reOle)
4804 if (!CreateIRichEditOle(NULL, editor, (LPVOID *)&editor->reOle))
4805 return 0;
4806 *(LPVOID *)lParam = editor->reOle;
4807 IRichEditOle_AddRef(editor->reOle);
4808 return 1;
4809 }
4810 case EM_GETPASSWORDCHAR:
4811 {
4812 return editor->cPasswordMask;
4813 }
4814 case EM_SETOLECALLBACK:
4815 if(editor->lpOleCallback)
4816 IRichEditOleCallback_Release(editor->lpOleCallback);
4817 editor->lpOleCallback = (IRichEditOleCallback*)lParam;
4818 if(editor->lpOleCallback)
4819 IRichEditOleCallback_AddRef(editor->lpOleCallback);
4820 return TRUE;
4821 case EM_GETWORDBREAKPROC:
4822 return (LRESULT)editor->pfnWordBreak;
4823 case EM_SETWORDBREAKPROC:
4824 {
4825 EDITWORDBREAKPROCW pfnOld = editor->pfnWordBreak;
4826
4827 editor->pfnWordBreak = (EDITWORDBREAKPROCW)lParam;
4828 return (LRESULT)pfnOld;
4829 }
4830 case EM_GETTEXTMODE:
4831 return editor->mode;
4832 case EM_SETTEXTMODE:
4833 {
4834 int mask = 0;
4835 int changes = 0;
4836
4837 if (ME_GetTextLength(editor) ||
4838 !list_empty( &editor->undo_stack ) || !list_empty( &editor->redo_stack ))
4839 return E_UNEXPECTED;
4840
4841 /* Check for mutually exclusive flags in adjacent bits of wParam */
4842 if ((wParam & (TM_RICHTEXT | TM_MULTILEVELUNDO | TM_MULTICODEPAGE)) &
4843 (wParam & (TM_PLAINTEXT | TM_SINGLELEVELUNDO | TM_SINGLECODEPAGE)) << 1)
4844 return E_INVALIDARG;
4845
4846 if (wParam & (TM_RICHTEXT | TM_PLAINTEXT))
4847 {
4848 mask |= TM_RICHTEXT | TM_PLAINTEXT;
4849 changes |= wParam & (TM_RICHTEXT | TM_PLAINTEXT);
4850 if (wParam & TM_PLAINTEXT) {
4851 /* Clear selection since it should be possible to select the
4852 * end of text run for rich text */
4853 ME_InvalidateSelection(editor);
4854 ME_SetCursorToStart(editor, &editor->pCursors[0]);
4855 editor->pCursors[1] = editor->pCursors[0];
4856 /* plain text can only have the default style. */
4857 ME_ClearTempStyle(editor);
4858 ME_AddRefStyle(editor->pBuffer->pDefaultStyle);
4859 ME_ReleaseStyle(editor->pCursors[0].pRun->member.run.style);
4860 editor->pCursors[0].pRun->member.run.style = editor->pBuffer->pDefaultStyle;
4861 }
4862 }
4863 /* FIXME: Currently no support for undo level and code page options */
4864 editor->mode = (editor->mode & ~mask) | changes;
4865 return 0;
4866 }
4867 case EM_SETPASSWORDCHAR:
4868 {
4869 editor->cPasswordMask = wParam;
4870 ME_RewrapRepaint(editor);
4871 return 0;
4872 }
4873 case EM_SETTARGETDEVICE:
4874 if (wParam == 0)
4875 {
4876 BOOL new = (lParam == 0 && (editor->styleFlags & ES_MULTILINE));
4877 if (editor->nAvailWidth || editor->bWordWrap != new)
4878 {
4879 editor->bWordWrap = new;
4880 editor->nAvailWidth = 0; /* wrap to client area */
4881 ME_RewrapRepaint(editor);
4882 }
4883 } else {
4884 int width = max(0, lParam);
4885 if ((editor->styleFlags & ES_MULTILINE) &&
4886 (!editor->bWordWrap || editor->nAvailWidth != width))
4887 {
4888 editor->nAvailWidth = width;
4889 editor->bWordWrap = TRUE;
4890 ME_RewrapRepaint(editor);
4891 }
4892 FIXME("EM_SETTARGETDEVICE doesn't use non-NULL target devices\n");
4893 }
4894 return TRUE;
4895 default:
4896 do_default:
4897 *phresult = S_FALSE;
4898 break;
4899 }
4900 return 0L;
4901 }
4902
4903 static BOOL create_windowed_editor(HWND hwnd, CREATESTRUCTW *create, BOOL emulate_10)
4904 {
4905 ITextHost *host = ME_CreateTextHost( hwnd, create, emulate_10 );
4906 ME_TextEditor *editor;
4907
4908 if (!host) return FALSE;
4909
4910 editor = ME_MakeEditor( host, emulate_10 );
4911 if (!editor)
4912 {
4913 ITextHost_Release( host );
4914 return FALSE;
4915 }
4916
4917 editor->exStyleFlags = GetWindowLongW( hwnd, GWL_EXSTYLE );
4918 editor->styleFlags |= GetWindowLongW( hwnd, GWL_STYLE ) & ES_WANTRETURN;
4919 editor->hWnd = hwnd; /* FIXME: Remove editor's dependence on hWnd */
4920 editor->hwndParent = create->hwndParent;
4921
4922 SetWindowLongPtrW( hwnd, 0, (LONG_PTR)editor );
4923
4924 return TRUE;
4925 }
4926
4927 static LRESULT RichEditWndProc_common(HWND hWnd, UINT msg, WPARAM wParam,
4928 LPARAM lParam, BOOL unicode)
4929 {
4930 ME_TextEditor *editor;
4931 HRESULT hresult;
4932 LRESULT lresult = 0;
4933
4934 TRACE("enter hwnd %p msg %04x (%s) %lx %lx, unicode %d\n",
4935 hWnd, msg, get_msg_name(msg), wParam, lParam, unicode);
4936
4937 editor = (ME_TextEditor *)GetWindowLongPtrW(hWnd, 0);
4938 if (!editor)
4939 {
4940 if (msg == WM_NCCREATE)
4941 {
4942 CREATESTRUCTW *pcs = (CREATESTRUCTW *)lParam;
4943
4944 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd, pcs->style);
4945 return create_windowed_editor( hWnd, pcs, FALSE );
4946 }
4947 else
4948 {
4949 return DefWindowProcW(hWnd, msg, wParam, lParam);
4950 }
4951 }
4952
4953 switch (msg)
4954 {
4955 case WM_PAINT:
4956 {
4957 HDC hDC;
4958 RECT rc;
4959 PAINTSTRUCT ps;
4960
4961 hDC = BeginPaint(editor->hWnd, &ps);
4962 if (!editor->bEmulateVersion10 || (editor->nEventMask & ENM_UPDATE))
4963 ME_SendOldNotify(editor, EN_UPDATE);
4964 /* Erase area outside of the formatting rectangle */
4965 if (ps.rcPaint.top < editor->rcFormat.top)
4966 {
4967 rc = ps.rcPaint;
4968 rc.bottom = editor->rcFormat.top;
4969 FillRect(hDC, &rc, editor->hbrBackground);
4970 ps.rcPaint.top = editor->rcFormat.top;
4971 }
4972 if (ps.rcPaint.bottom > editor->rcFormat.bottom) {
4973 rc = ps.rcPaint;
4974 rc.top = editor->rcFormat.bottom;
4975 FillRect(hDC, &rc, editor->hbrBackground);
4976 ps.rcPaint.bottom = editor->rcFormat.bottom;
4977 }
4978 if (ps.rcPaint.left < editor->rcFormat.left) {
4979 rc = ps.rcPaint;
4980 rc.right = editor->rcFormat.left;
4981 FillRect(hDC, &rc, editor->hbrBackground);
4982 ps.rcPaint.left = editor->rcFormat.left;
4983 }
4984 if (ps.rcPaint.right > editor->rcFormat.right) {
4985 rc = ps.rcPaint;
4986 rc.left = editor->rcFormat.right;
4987 FillRect(hDC, &rc, editor->hbrBackground);
4988 ps.rcPaint.right = editor->rcFormat.right;
4989 }
4990
4991 ME_PaintContent(editor, hDC, &ps.rcPaint);
4992 EndPaint(editor->hWnd, &ps);
4993 return 0;
4994 }
4995 case WM_ERASEBKGND:
4996 {
4997 HDC hDC = (HDC)wParam;
4998 RECT rc;
4999
5000 if (GetUpdateRect(editor->hWnd, &rc, TRUE))
5001 FillRect(hDC, &rc, editor->hbrBackground);
5002 return 1;
5003 }
5004 case EM_SETOPTIONS:
5005 {
5006 DWORD dwStyle;
5007 const DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL |
5008 ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN |
5009 ECO_SELECTIONBAR;
5010 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
5011 dwStyle = GetWindowLongW(hWnd, GWL_STYLE);
5012 dwStyle = (dwStyle & ~mask) | (lresult & mask);
5013 SetWindowLongW(hWnd, GWL_STYLE, dwStyle);
5014 return lresult;
5015 }
5016 case EM_SETREADONLY:
5017 {
5018 DWORD dwStyle;
5019 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
5020 dwStyle = GetWindowLongW(hWnd, GWL_STYLE);
5021 dwStyle &= ~ES_READONLY;
5022 if (wParam)
5023 dwStyle |= ES_READONLY;
5024 SetWindowLongW(hWnd, GWL_STYLE, dwStyle);
5025 return lresult;
5026 }
5027 default:
5028 lresult = ME_HandleMessage(editor, msg, wParam, lParam, unicode, &hresult);
5029 }
5030
5031 if (hresult == S_FALSE)
5032 lresult = DefWindowProcW(hWnd, msg, wParam, lParam);
5033
5034 TRACE("exit hwnd %p msg %04x (%s) %lx %lx, unicode %d -> %lu\n",
5035 hWnd, msg, get_msg_name(msg), wParam, lParam, unicode, lresult);
5036
5037 return lresult;
5038 }
5039
5040 static LRESULT WINAPI RichEditWndProcW(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5041 {
5042 BOOL unicode = TRUE;
5043
5044 /* Under Win9x RichEdit20W returns ANSI strings, see the tests. */
5045 if (msg == WM_GETTEXT && (GetVersion() & 0x80000000))
5046 unicode = FALSE;
5047
5048 return RichEditWndProc_common(hWnd, msg, wParam, lParam, unicode);
5049 }
5050
5051 static LRESULT WINAPI RichEditWndProcA(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5052 {
5053 return RichEditWndProc_common(hWnd, msg, wParam, lParam, FALSE);
5054 }
5055
5056 /******************************************************************
5057 * RichEditANSIWndProc (RICHED20.10)
5058 */
5059 LRESULT WINAPI RichEditANSIWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5060 {
5061 return RichEditWndProcA(hWnd, msg, wParam, lParam);
5062 }
5063
5064 /******************************************************************
5065 * RichEdit10ANSIWndProc (RICHED20.9)
5066 */
5067 LRESULT WINAPI RichEdit10ANSIWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
5068 {
5069 if (msg == WM_NCCREATE && !GetWindowLongPtrW(hWnd, 0))
5070 {
5071 CREATESTRUCTW *pcs = (CREATESTRUCTW *)lParam;
5072
5073 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd, pcs->style);
5074 return create_windowed_editor( hWnd, pcs, TRUE );
5075 }
5076 return RichEditANSIWndProc(hWnd, msg, wParam, lParam);
5077 }
5078
5079 void ME_SendOldNotify(ME_TextEditor *editor, int nCode)
5080 {
5081 ITextHost_TxNotify(editor->texthost, nCode, NULL);
5082 }
5083
5084 /* Fill buffer with srcChars unicode characters from the start cursor.
5085 *
5086 * buffer: destination buffer
5087 * buflen: length of buffer in characters excluding the NULL terminator.
5088 * start: start of editor text to copy into buffer.
5089 * srcChars: Number of characters to use from the editor text.
5090 * bCRLF: if true, replaces all end of lines with \r\n pairs.
5091 *
5092 * returns the number of characters written excluding the NULL terminator.
5093 *
5094 * The written text is always NULL terminated.
5095 */
5096 int ME_GetTextW(ME_TextEditor *editor, WCHAR *buffer, int buflen,
5097 const ME_Cursor *start, int srcChars, BOOL bCRLF,
5098 BOOL bEOP)
5099 {
5100 ME_DisplayItem *pRun, *pNextRun;
5101 const WCHAR *pStart = buffer;
5102 const WCHAR cr_lf[] = {'\r', '\n', 0};
5103 const WCHAR *str;
5104 int nLen;
5105
5106 /* bCRLF flag is only honored in 2.0 and up. 1.0 must always return text verbatim */
5107 if (editor->bEmulateVersion10) bCRLF = FALSE;
5108
5109 pRun = start->pRun;
5110 assert(pRun);
5111 pNextRun = ME_FindItemFwd(pRun, diRun);
5112
5113 nLen = pRun->member.run.len - start->nOffset;
5114 str = get_text( &pRun->member.run, start->nOffset );
5115
5116 while (srcChars && buflen && pNextRun)
5117 {
5118 int nFlags = pRun->member.run.nFlags;
5119
5120 if (bCRLF && nFlags & MERF_ENDPARA && ~nFlags & MERF_ENDCELL)
5121 {
5122 if (buflen == 1) break;
5123 /* FIXME: native fails to reduce srcChars here for WM_GETTEXT or
5124 * EM_GETTEXTEX, however, this is done for copying text which
5125 * also uses this function. */
5126 srcChars -= min(nLen, srcChars);
5127 nLen = 2;
5128 str = cr_lf;
5129 } else {
5130 nLen = min(nLen, srcChars);
5131 srcChars -= nLen;
5132 }
5133
5134 nLen = min(nLen, buflen);
5135 buflen -= nLen;
5136
5137 CopyMemory(buffer, str, sizeof(WCHAR) * nLen);
5138
5139 buffer += nLen;
5140
5141 pRun = pNextRun;
5142 pNextRun = ME_FindItemFwd(pRun, diRun);
5143
5144 nLen = pRun->member.run.len;
5145 str = get_text( &pRun->member.run, 0 );
5146 }
5147 /* append '\r' to the last paragraph. */
5148 if (pRun->next->type == diTextEnd && bEOP)
5149 {
5150 *buffer = '\r';
5151 buffer ++;
5152 }
5153 *buffer = 0;
5154 return buffer - pStart;
5155 }
5156
5157 static BOOL ME_RegisterEditorClass(HINSTANCE hInstance)
5158 {
5159 WNDCLASSW wcW;
5160 WNDCLASSA wcA;
5161
5162 wcW.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS;
5163 wcW.lpfnWndProc = RichEditWndProcW;
5164 wcW.cbClsExtra = 0;
5165 wcW.cbWndExtra = sizeof(ME_TextEditor *);
5166 wcW.hInstance = NULL; /* hInstance would register DLL-local class */
5167 wcW.hIcon = NULL;
5168 wcW.hCursor = LoadCursorW(NULL, (LPWSTR)IDC_IBEAM);
5169 wcW.hbrBackground = GetStockObject(NULL_BRUSH);
5170 wcW.lpszMenuName = NULL;
5171
5172 if (is_version_nt())
5173 {
5174 wcW.lpszClassName = RICHEDIT_CLASS20W;
5175 if (!RegisterClassW(&wcW)) return FALSE;
5176 wcW.lpszClassName = MSFTEDIT_CLASS;
5177 if (!RegisterClassW(&wcW)) return FALSE;
5178 }
5179 else
5180 {
5181 /* WNDCLASSA/W have the same layout */
5182 wcW.lpszClassName = (LPCWSTR)"RichEdit20W";
5183 if (!RegisterClassA((WNDCLASSA *)&wcW)) return FALSE;
5184 wcW.lpszClassName = (LPCWSTR)"RichEdit50W";
5185 if (!RegisterClassA((WNDCLASSA *)&wcW)) return FALSE;
5186 }
5187
5188 wcA.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS;
5189 wcA.lpfnWndProc = RichEditWndProcA;
5190 wcA.cbClsExtra = 0;
5191 wcA.cbWndExtra = sizeof(ME_TextEditor *);
5192 wcA.hInstance = NULL; /* hInstance would register DLL-local class */
5193 wcA.hIcon = NULL;
5194 wcA.hCursor = LoadCursorW(NULL, (LPWSTR)IDC_IBEAM);
5195 wcA.hbrBackground = GetStockObject(NULL_BRUSH);
5196 wcA.lpszMenuName = NULL;
5197 wcA.lpszClassName = RICHEDIT_CLASS20A;
5198 if (!RegisterClassA(&wcA)) return FALSE;
5199 wcA.lpszClassName = "RichEdit50A";
5200 if (!RegisterClassA(&wcA)) return FALSE;
5201
5202 return TRUE;
5203 }
5204
5205 static LRESULT WINAPI REComboWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
5206 /* FIXME: Not implemented */
5207 TRACE("hWnd %p msg %04x (%s) %08lx %08lx\n",
5208 hWnd, msg, get_msg_name(msg), wParam, lParam);
5209 return DefWindowProcW(hWnd, msg, wParam, lParam);
5210 }
5211
5212 static LRESULT WINAPI REListWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
5213 /* FIXME: Not implemented */
5214 TRACE("hWnd %p msg %04x (%s) %08lx %08lx\n",
5215 hWnd, msg, get_msg_name(msg), wParam, lParam);
5216 return DefWindowProcW(hWnd, msg, wParam, lParam);
5217 }
5218
5219 /******************************************************************
5220 * REExtendedRegisterClass (RICHED20.8)
5221 *
5222 * FIXME undocumented
5223 * Need to check for errors and implement controls and callbacks
5224 */
5225 LRESULT WINAPI REExtendedRegisterClass(void)
5226 {
5227 WNDCLASSW wcW;
5228 UINT result;
5229
5230 FIXME("semi stub\n");
5231
5232 wcW.cbClsExtra = 0;
5233 wcW.cbWndExtra = 4;
5234 wcW.hInstance = NULL;
5235 wcW.hIcon = NULL;
5236 wcW.hCursor = NULL;
5237 wcW.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
5238 wcW.lpszMenuName = NULL;
5239
5240 if (!ME_ListBoxRegistered)
5241 {
5242 wcW.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS;
5243 wcW.lpfnWndProc = REListWndProc;
5244 wcW.lpszClassName = REListBox20W;
5245 if (RegisterClassW(&wcW)) ME_ListBoxRegistered = TRUE;
5246 }
5247
5248 if (!ME_ComboBoxRegistered)
5249 {
5250 wcW.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS | CS_VREDRAW | CS_HREDRAW;
5251 wcW.lpfnWndProc = REComboWndProc;
5252 wcW.lpszClassName = REComboBox20W;
5253 if (RegisterClassW(&wcW)) ME_ComboBoxRegistered = TRUE;
5254 }
5255
5256 result = 0;
5257 if (ME_ListBoxRegistered)
5258 result += 1;
5259 if (ME_ComboBoxRegistered)
5260 result += 2;
5261
5262 return result;
5263 }
5264
5265 static int wchar_comp( const void *key, const void *elem )
5266 {
5267 return *(const WCHAR *)key - *(const WCHAR *)elem;
5268 }
5269
5270 /* neutral characters end the url if the next non-neutral character is a space character,
5271 otherwise they are included in the url. */
5272 static BOOL isurlneutral( WCHAR c )
5273 {
5274 /* NB this list is sorted */
5275 static const WCHAR neutral_chars[] = {'!','\"','\'','(',')',',','-','.',':',';','<','>','?','[',']','{','}'};
5276
5277 /* Some shortcuts */
5278 if (isalnum( c )) return FALSE;
5279 if (c > neutral_chars[sizeof(neutral_chars) / sizeof(neutral_chars[0]) - 1]) return FALSE;
5280
5281 return !!bsearch( &c, neutral_chars, sizeof(neutral_chars) / sizeof(neutral_chars[0]),
5282 sizeof(c), wchar_comp );
5283 }
5284
5285 /**
5286 * This proc takes a selection, and scans it forward in order to select the span
5287 * of a possible URL candidate. A possible URL candidate must start with isalnum
5288 * or one of the following special characters: *|/\+%#@ and must consist entirely
5289 * of the characters allowed to start the URL, plus : (colon) which may occur
5290 * at most once, and not at either end.
5291 */
5292 static BOOL ME_FindNextURLCandidate(ME_TextEditor *editor,
5293 const ME_Cursor *start,
5294 int nChars,
5295 ME_Cursor *candidate_min,
5296 ME_Cursor *candidate_max)
5297 {
5298 ME_Cursor cursor = *start, neutral_end, space_end;
5299 BOOL candidateStarted = FALSE, quoted = FALSE;
5300 WCHAR c;
5301
5302 while (nChars > 0)
5303 {
5304 WCHAR *str = get_text( &cursor.pRun->member.run, 0 );
5305 int run_len = cursor.pRun->member.run.len;
5306
5307 nChars -= run_len - cursor.nOffset;
5308
5309 /* Find start of candidate */
5310 if (!candidateStarted)
5311 {
5312 while (cursor.nOffset < run_len)
5313 {
5314 c = str[cursor.nOffset];
5315 if (!isspaceW( c ) && !isurlneutral( c ))
5316 {
5317 *candidate_min = cursor;
5318 candidateStarted = TRUE;
5319 neutral_end.pPara = NULL;
5320 space_end.pPara = NULL;
5321 cursor.nOffset++;
5322 break;
5323 }
5324 quoted = (c == '<');
5325 cursor.nOffset++;
5326 }
5327 }
5328
5329 /* Find end of candidate */
5330 if (candidateStarted)
5331 {
5332 while (cursor.nOffset < run_len)
5333 {
5334 c = str[cursor.nOffset];
5335 if (isspaceW( c ))
5336 {
5337 if (quoted && c != '\r')
5338 {
5339 if (!space_end.pPara)
5340 {
5341 if (neutral_end.pPara)
5342 space_end = neutral_end;
5343 else
5344 space_end = cursor;
5345 }
5346 }
5347 else
5348 goto done;
5349 }
5350 else if (isurlneutral( c ))
5351 {
5352 if (quoted && c == '>')
5353 {
5354 neutral_end.pPara = NULL;
5355 space_end.pPara = NULL;
5356 goto done;
5357 }
5358 if (!neutral_end.pPara)
5359 neutral_end = cursor;
5360 }
5361 else
5362 neutral_end.pPara = NULL;
5363
5364 cursor.nOffset++;
5365 }
5366 }
5367
5368 cursor.nOffset = 0;
5369 if (!ME_NextRun(&cursor.pPara, &cursor.pRun, TRUE))
5370 goto done;
5371 }
5372
5373 done:
5374 if (candidateStarted)
5375 {
5376 if (space_end.pPara)
5377 *candidate_max = space_end;
5378 else if (neutral_end.pPara)
5379 *candidate_max = neutral_end;
5380 else
5381 *candidate_max = cursor;
5382 return TRUE;
5383 }
5384 *candidate_max = *candidate_min = cursor;
5385 return FALSE;
5386 }
5387
5388 /**
5389 * This proc evaluates the selection and returns TRUE if it can be considered an URL
5390 */
5391 static BOOL ME_IsCandidateAnURL(ME_TextEditor *editor, const ME_Cursor *start, int nChars)
5392 {
5393 #define MAX_PREFIX_LEN 9
5394 struct prefix_s {
5395 const WCHAR text[MAX_PREFIX_LEN];
5396 int length;
5397 }prefixes[] = {
5398 {{'p','r','o','s','p','e','r','o',':'}, 9},
5399 {{'t','e','l','n','e','t',':'}, 7},
5400 {{'g','o','p','h','e','r',':'}, 7},
5401 {{'m','a','i','l','t','o',':'}, 7},
5402 {{'h','t','t','p','s',':'}, 6},
5403 {{'f','i','l','e',':'}, 5},
5404 {{'n','e','w','s',':'}, 5},
5405 {{'w','a','i','s',':'}, 5},
5406 {{'n','n','t','p',':'}, 5},
5407 {{'h','t','t','p',':'}, 5},
5408 {{'w','w','w','.'}, 4},
5409 {{'f','t','p',':'}, 4},
5410 };
5411 WCHAR bufferW[MAX_PREFIX_LEN + 1];
5412 unsigned int i;
5413
5414 ME_GetTextW(editor, bufferW, MAX_PREFIX_LEN, start, nChars, FALSE, FALSE);
5415 for (i = 0; i < sizeof(prefixes) / sizeof(*prefixes); i++)
5416 {
5417 if (nChars < prefixes[i].length) continue;
5418 if (!memcmp(prefixes[i].text, bufferW, prefixes[i].length * sizeof(WCHAR)))
5419 return TRUE;
5420 }
5421 return FALSE;
5422 #undef MAX_PREFIX_LEN
5423 }
5424
5425 /**
5426 * This proc walks through the indicated selection and evaluates whether each
5427 * section identified by ME_FindNextURLCandidate and in-between sections have
5428 * their proper CFE_LINK attributes set or unset. If the CFE_LINK attribute is
5429 * not what it is supposed to be, this proc sets or unsets it as appropriate.
5430 *
5431 * Since this function can cause runs to be split, do not depend on the value
5432 * of the start cursor at the end of the function.
5433 *
5434 * nChars may be set to INT_MAX to update to the end of the text.
5435 *
5436 * Returns TRUE if at least one section was modified.
5437 */
5438 static BOOL ME_UpdateLinkAttribute(ME_TextEditor *editor, ME_Cursor *start, int nChars)
5439 {
5440 BOOL modified = FALSE;
5441 ME_Cursor startCur = *start;
5442
5443 if (!editor->AutoURLDetect_bEnable) return FALSE;
5444
5445 do
5446 {
5447 CHARFORMAT2W link;
5448 ME_Cursor candidateStart, candidateEnd;
5449
5450 if (ME_FindNextURLCandidate(editor, &startCur, nChars,
5451 &candidateStart, &candidateEnd))
5452 {
5453 /* Section before candidate is not an URL */
5454 int cMin = ME_GetCursorOfs(&candidateStart);
5455 int cMax = ME_GetCursorOfs(&candidateEnd);
5456
5457 if (!ME_IsCandidateAnURL(editor, &candidateStart, cMax - cMin))
5458 candidateStart = candidateEnd;
5459 nChars -= cMax - ME_GetCursorOfs(&startCur);
5460 }
5461 else
5462 {
5463 /* No more candidates until end of selection */
5464 nChars = 0;
5465 }
5466
5467 if (startCur.pRun != candidateStart.pRun ||
5468 startCur.nOffset != candidateStart.nOffset)
5469 {
5470 /* CFE_LINK effect should be consistently unset */
5471 link.cbSize = sizeof(link);
5472 ME_GetCharFormat(editor, &startCur, &candidateStart, &link);
5473 if (!(link.dwMask & CFM_LINK) || (link.dwEffects & CFE_LINK))
5474 {
5475 /* CFE_LINK must be unset from this range */
5476 memset(&link, 0, sizeof(CHARFORMAT2W));
5477 link.cbSize = sizeof(link);
5478 link.dwMask = CFM_LINK;
5479 link.dwEffects = 0;
5480 ME_SetCharFormat(editor, &startCur, &candidateStart, &link);
5481 /* Update candidateEnd since setting character formats may split
5482 * runs, which can cause a cursor to be at an invalid offset within
5483 * a split run. */
5484 while (candidateEnd.nOffset >= candidateEnd.pRun->member.run.len)
5485 {
5486 candidateEnd.nOffset -= candidateEnd.pRun->member.run.len;
5487 candidateEnd.pRun = ME_FindItemFwd(candidateEnd.pRun, diRun);
5488 }
5489 modified = TRUE;
5490 }
5491 }
5492 if (candidateStart.pRun != candidateEnd.pRun ||
5493 candidateStart.nOffset != candidateEnd.nOffset)
5494 {
5495 /* CFE_LINK effect should be consistently set */
5496 link.cbSize = sizeof(link);
5497 ME_GetCharFormat(editor, &candidateStart, &candidateEnd, &link);
5498 if (!(link.dwMask & CFM_LINK) || !(link.dwEffects & CFE_LINK))
5499 {
5500 /* CFE_LINK must be set on this range */
5501 memset(&link, 0, sizeof(CHARFORMAT2W));
5502 link.cbSize = sizeof(link);
5503 link.dwMask = CFM_LINK;
5504 link.dwEffects = CFE_LINK;
5505 ME_SetCharFormat(editor, &candidateStart, &candidateEnd, &link);
5506 modified = TRUE;
5507 }
5508 }
5509 startCur = candidateEnd;
5510 } while (nChars > 0);
5511 return modified;
5512 }