2 * RichEdit - functions dealing with editor object
4 * Copyright 2004 by Krzysztof Foltman
5 * Copyright 2005 by Cihan Altinay
6 * Copyright 2005 by Phil Krylov
7 * Copyright 2008 Eric Pouech
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
25 API implementation status:
27 Messages (ANSI versions not done yet)
28 + EM_AUTOURLDETECT 2.0
39 + EM_FINDTEXT (only FR_DOWN flag implemented)
40 + EM_FINDTEXTEX (only FR_DOWN flag implemented)
44 + EM_GETAUTOURLDETECT 2.0
45 - EM_GETBIDIOPTIONS 3.0
46 - EM_GETCHARFORMAT (partly done)
49 + EM_GETFIRSTVISIBLELINE (can be optimized if needed)
50 - EM_GETIMECOLOR 1.0asian
51 - EM_GETIMECOMPMODE 2.0
52 - EM_GETIMEOPTIONS 1.0asian
54 - EM_GETLANGOPTIONS 2.0
57 + EM_GETLINECOUNT returns number of rows, not of paragraphs
62 + EM_GETPASSWORDCHAR 2.0
63 - EM_GETPUNCTUATION 1.0asian
67 + EM_GETSELTEXT (ANSI&Unicode)
71 + EM_GETTEXTLENGTHEX (GTL_PRECISE unimplemented)
73 ? + EM_GETTEXTRANGE (ANSI&Unicode)
74 - EM_GETTYPOGRAPHYOPTIONS 3.0
77 - EM_GETWORDBREAKPROCEX
78 - EM_GETWORDWRAPMODE 1.0asian
81 + EM_LIMITTEXT (Also called EM_SETLIMITTEXT)
90 + EM_REPLACESEL (proper style?) ANSI&Unicode
94 - EM_SETBIDIOPTIONS 3.0
96 + EM_SETCHARFORMAT (partly done, no ANSI)
98 + EM_SETEVENTMASK (few notifications supported)
100 - EM_SETIMECOLOR 1.0asian
101 - EM_SETIMEOPTIONS 1.0asian
103 - EM_SETLANGOPTIONS 2.0
106 + EM_SETMODIFY (not sure if implementation is correct)
108 + EM_SETOPTIONS (partially implemented)
111 + EM_SETPASSWORDCHAR 2.0
112 - EM_SETPUNCTUATION 1.0asian
113 + EM_SETREADONLY no beep on modification attempt
115 + EM_SETRECTNP (EM_SETRECT without repainting)
117 + EM_SETSCROLLPOS 3.0
119 - EM_SETTARGETDEVICE (partial)
120 + EM_SETTEXTEX 3.0 (proper style?)
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
128 + EM_SHOWSCROLLBAR 2.0
129 + EM_STOPGROUPTYPING 2.0
137 + WM_GETDLGCODE (the current implementation is incomplete)
138 + WM_GETTEXT (ANSI&Unicode)
139 + WM_GETTEXTLENGTH (ANSI version sucks)
143 + WM_SETTEXT (resets undo stack !) (proper style?) ANSI&Unicode
144 + WM_STYLECHANGING (seems to do nothing)
145 + WM_STYLECHANGED (seems to do nothing)
151 * EN_CHANGE (sent from the wrong place)
168 * EN_UPDATE (sent from the wrong place)
176 + ES_DISABLENOSCROLL (scrollbar is always visible)
177 - ES_EX_NOCALLOLEINIT
181 - ES_READONLY (I'm not sure if beeping is the proper behaviour)
187 - ES_WANTRETURN (don't know how to do WM_GETDLGCODE part)
194 * RICHED20 TODO (incomplete):
196 * - messages/styles/notifications listed above
197 * - add remaining CHARFORMAT/PARAFORMAT fields
198 * - right/center align should strip spaces from the beginning
199 * - pictures/OLE objects (not just smiling faces that lack API support ;-) )
200 * - COM interface (looks like a major pain in the TODO list)
201 * - calculate heights of pictures (half-done)
202 * - hysteresis during wrapping (related to scrollbars appearing/disappearing)
204 * - how to implement EM_FORMATRANGE and EM_DISPLAYBAND ? (Mission Impossible)
205 * - italic caret with italic fonts
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
214 * - ListBox & ComboBox not implemented
216 * Bugs that are probably fixed, but not so easy to verify:
217 * - EN_UPDATE/EN_CHANGE are handled very incorrectly (should be OK now)
218 * - undo for ME_JoinParagraphs doesn't store paragraph format ? (it does)
219 * - check/fix artificial EOL logic (bCursorAtEnd, hardly logical)
220 * - caret shouldn't be displayed when selection isn't empty
221 * - check refcounting in style management functions (looks perfect now, but no bugs is suspicious)
222 * - undo for setting default format (done, might be buggy)
223 * - styles might be not released properly (looks like they work like charm, but who knows?
227 #define NONAMELESSUNION
232 #define NO_SHLWAPI_STREAM
239 #include <reactos/undocuser.h>
242 #define STACK_SIZE_DEFAULT 100
243 #define STACK_SIZE_MAX 1000
245 #define TEXT_LIMIT_DEFAULT 32767
247 WINE_DEFAULT_DEBUG_CHANNEL(richedit
);
249 static BOOL
ME_RegisterEditorClass(HINSTANCE
);
250 static BOOL
ME_UpdateLinkAttribute(ME_TextEditor
*editor
, ME_Cursor
*start
, int nChars
);
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
;
256 BOOL me_debug
= FALSE
;
257 HANDLE me_heap
= NULL
;
259 static BOOL ME_ListBoxRegistered
= FALSE
;
260 static BOOL ME_ComboBoxRegistered
= FALSE
;
262 static inline BOOL
is_version_nt(void)
264 return !(GetVersion() & 0x80000000);
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
);
276 p1
->member
.para
.next_para
= p2
;
277 p2
->member
.para
.prev_para
= p1
;
278 p2
->member
.para
.nCharOfs
= 0;
282 buf
->pCharStyle
= NULL
;
288 static LRESULT
ME_StreamInText(ME_TextEditor
*editor
, DWORD dwFormat
, ME_InStream
*stream
, ME_Style
*style
)
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 */
296 static const char bom_utf8
[] = {0xEF, 0xBB, 0xBF};
298 TRACE("%08x %p\n", dwFormat
, stream
);
302 WCHAR wszText
[STREAMIN_BUFFER_SIZE
+1];
306 ME_StreamInFill(stream
);
307 if (stream
->editstream
->dwError
)
311 total_bytes_read
+= stream
->dwSize
;
314 if (!(dwFormat
& SF_UNICODE
))
316 char * buf
= stream
->buffer
;
317 DWORD size
= stream
->dwSize
, end
;
322 if (stream
->dwSize
>= 3 && !memcmp(stream
->buffer
, bom_utf8
, 3))
334 memcpy(conv_buf
+ copy
, buf
, size
);
339 while ((buf
[end
-1] & 0xC0) == 0x80)
342 --total_bytes_read
; /* strange, but seems to match windows */
344 if (buf
[end
-1] & 0x80)
347 if ((buf
[end
-1] & 0xE0) == 0xC0)
349 if ((buf
[end
-1] & 0xF0) == 0xE0)
351 if ((buf
[end
-1] & 0xF8) == 0xF0)
354 if (size
- end
>= need
)
356 /* we have enough bytes for this sequence */
361 /* need more bytes, so don't transcode this sequence */
369 nWideChars
= MultiByteToWideChar(cp
, 0, buf
, end
, wszText
, STREAMIN_BUFFER_SIZE
);
376 memcpy(conv_buf
, buf
+ end
, size
- end
);
383 nWideChars
= stream
->dwSize
>> 1;
384 pText
= (WCHAR
*)stream
->buffer
;
387 ME_InsertTextFromCursor(editor
, 0, pText
, nWideChars
, style
);
388 if (stream
->dwSize
== 0)
392 return total_bytes_read
;
395 static void ME_ApplyBorderProperties(RTF_Info
*info
,
396 ME_BorderRect
*borderRect
,
397 RTFBorder
*borderDef
)
400 ME_Border
*pBorders
[] = {&borderRect
->top
,
404 for (i
= 0; i
< 4; i
++)
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
;
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);
417 pBorders
[i
]->colorRef
= RGB(0, 0, 0);
421 void ME_RTFCharAttrHook(RTF_Info
*info
)
424 fmt
.cbSize
= sizeof(fmt
);
428 switch(info
->rtfMinor
)
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
;
440 fmt
.dwMask
= CFM_BOLD
| CFM_WEIGHT
;
441 fmt
.dwEffects
= info
->rtfParam
? CFE_BOLD
: 0;
442 fmt
.wWeight
= info
->rtfParam
? FW_BOLD
: FW_NORMAL
;
445 fmt
.dwMask
= CFM_ITALIC
;
446 fmt
.dwEffects
= info
->rtfParam
? fmt
.dwMask
: 0;
449 fmt
.dwMask
= CFM_UNDERLINETYPE
| CFM_UNDERLINE
;
450 fmt
.bUnderlineType
= CFU_UNDERLINE
;
451 fmt
.dwEffects
= info
->rtfParam
? CFE_UNDERLINE
: 0;
453 case rtfDotUnderline
:
454 fmt
.dwMask
= CFM_UNDERLINETYPE
| CFM_UNDERLINE
;
455 fmt
.bUnderlineType
= CFU_UNDERLINEDOTTED
;
456 fmt
.dwEffects
= info
->rtfParam
? CFE_UNDERLINE
: 0;
459 fmt
.dwMask
= CFM_UNDERLINETYPE
| CFM_UNDERLINE
;
460 fmt
.bUnderlineType
= CFU_UNDERLINEDOUBLE
;
461 fmt
.dwEffects
= info
->rtfParam
? CFE_UNDERLINE
: 0;
463 case rtfWordUnderline
:
464 fmt
.dwMask
= CFM_UNDERLINETYPE
| CFM_UNDERLINE
;
465 fmt
.bUnderlineType
= CFU_UNDERLINEWORD
;
466 fmt
.dwEffects
= info
->rtfParam
? CFE_UNDERLINE
: 0;
469 fmt
.dwMask
= CFM_UNDERLINE
;
473 fmt
.dwMask
= CFM_STRIKEOUT
;
474 fmt
.dwEffects
= info
->rtfParam
? fmt
.dwMask
: 0;
478 case rtfSubScrShrink
:
479 case rtfSuperScrShrink
:
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;
487 fmt
.dwMask
= CFM_HIDDEN
;
488 fmt
.dwEffects
= info
->rtfParam
? fmt
.dwMask
: 0;
491 fmt
.dwMask
= CFM_BACKCOLOR
;
493 if (info
->rtfParam
== 0)
494 fmt
.dwEffects
= CFE_AUTOBACKCOLOR
;
495 else if (info
->rtfParam
!= rtfNoParam
)
497 RTFColor
*c
= RTFGetColor(info
, info
->rtfParam
);
498 if (c
&& c
->rtfCBlue
>= 0)
499 fmt
.crBackColor
= (c
->rtfCBlue
<<16)|(c
->rtfCGreen
<<8)|(c
->rtfCRed
);
501 fmt
.dwEffects
= CFE_AUTOBACKCOLOR
;
505 fmt
.dwMask
= CFM_COLOR
;
507 if (info
->rtfParam
== 0)
508 fmt
.dwEffects
= CFE_AUTOCOLOR
;
509 else if (info
->rtfParam
!= rtfNoParam
)
511 RTFColor
*c
= RTFGetColor(info
, info
->rtfParam
);
512 if (c
&& c
->rtfCBlue
>= 0)
513 fmt
.crTextColor
= (c
->rtfCBlue
<<16)|(c
->rtfCGreen
<<8)|(c
->rtfCRed
);
515 fmt
.dwEffects
= CFE_AUTOCOLOR
;
520 if (info
->rtfParam
!= rtfNoParam
)
522 RTFFont
*f
= RTFGetFont(info
, info
->rtfParam
);
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);
534 fmt
.dwMask
= CFM_SIZE
;
535 if (info
->rtfParam
!= rtfNoParam
)
536 fmt
.yHeight
= info
->rtfParam
*10;
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
;
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
)
554 switch(info
->rtfMinor
)
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
;
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;
580 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
582 if (info
->tableDef
&& info
->tableDef
->tableRowStart
&&
583 info
->tableDef
->tableRowStart
->member
.para
.nFlags
& MEPF_ROWEND
)
586 ME_DisplayItem
*para
;
587 /* We are just after a table row. */
588 RTFFlushOutputBuffer(info
);
589 cursor
= info
->editor
->pCursors
[0];
591 if (para
== info
->tableDef
->tableRowStart
->member
.para
.next_para
592 && !cursor
.nOffset
&& !cursor
.pRun
->member
.run
.nCharOfs
)
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
598 info
->tableDef
->tableRowStart
= NULL
;
599 info
->canInheritInTbl
= FALSE
;
602 } else { /* v1.0 - v3.0 */
603 info
->fmt
.dwMask
|= PFM_TABLE
;
604 info
->fmt
.wEffects
&= ~PFE_TABLE
;
608 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
610 while (info
->rtfParam
> info
->nestingLevel
) {
611 RTFTable
*tableDef
= heap_alloc_zero(sizeof(*tableDef
));
612 tableDef
->parent
= info
->tableDef
;
613 info
->tableDef
= tableDef
;
615 RTFFlushOutputBuffer(info
);
616 if (tableDef
->tableRowStart
&&
617 tableDef
->tableRowStart
->member
.para
.nFlags
& MEPF_ROWEND
)
619 ME_DisplayItem
*para
= tableDef
->tableRowStart
;
620 para
= para
->member
.para
.next_para
;
621 para
= ME_InsertTableRowStartAtParagraph(info
->editor
, para
);
622 tableDef
->tableRowStart
= para
;
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
);
632 info
->nestingLevel
++;
634 info
->canInheritInTbl
= FALSE
;
639 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
641 if (info
->nestingLevel
< 1)
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
)
651 ME_DisplayItem
*para
= tableDef
->tableRowStart
;
652 para
= para
->member
.para
.next_para
;
653 para
= ME_InsertTableRowStartAtParagraph(info
->editor
, para
);
654 tableDef
->tableRowStart
= para
;
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
);
663 info
->nestingLevel
= 1;
664 info
->canInheritInTbl
= TRUE
;
667 } else { /* v1.0 - v3.0 */
668 info
->fmt
.dwMask
|= PFM_TABLE
;
669 info
->fmt
.wEffects
|= PFE_TABLE
;
675 if ((info
->fmt
.dwMask
& (PFM_STARTINDENT
| PFM_OFFSET
)) != (PFM_STARTINDENT
| PFM_OFFSET
))
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
;
684 if (info
->rtfMinor
== rtfFirstIndent
)
686 info
->fmt
.dxStartIndent
+= info
->fmt
.dxOffset
+ info
->rtfParam
;
687 info
->fmt
.dxOffset
= -info
->rtfParam
;
690 info
->fmt
.dxStartIndent
= info
->rtfParam
- info
->fmt
.dxOffset
;
693 info
->fmt
.dwMask
|= PFM_RIGHTINDENT
;
694 info
->fmt
.dxRightIndent
= info
->rtfParam
;
698 info
->fmt
.dwMask
|= PFM_ALIGNMENT
;
699 info
->fmt
.wAlignment
= PFA_LEFT
;
702 info
->fmt
.dwMask
|= PFM_ALIGNMENT
;
703 info
->fmt
.wAlignment
= PFA_RIGHT
;
706 info
->fmt
.dwMask
|= PFM_ALIGNMENT
;
707 info
->fmt
.wAlignment
= PFA_CENTER
;
710 if (!(info
->fmt
.dwMask
& PFM_TABSTOPS
))
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
;
720 if (info
->fmt
.cTabCount
< MAX_TAB_STOPS
&& info
->rtfParam
< 0x1000000)
721 info
->fmt
.rgxTabs
[info
->fmt
.cTabCount
++] = info
->rtfParam
;
724 info
->fmt
.dwMask
|= PFM_KEEP
;
725 info
->fmt
.wEffects
|= PFE_KEEP
;
727 case rtfNoWidowControl
:
728 info
->fmt
.dwMask
|= PFM_NOWIDOWCONTROL
;
729 info
->fmt
.wEffects
|= PFE_NOWIDOWCONTROL
;
732 info
->fmt
.dwMask
|= PFM_KEEPNEXT
;
733 info
->fmt
.wEffects
|= PFE_KEEPNEXT
;
736 info
->fmt
.dwMask
|= PFM_SPACEAFTER
;
737 info
->fmt
.dySpaceAfter
= info
->rtfParam
;
740 info
->fmt
.dwMask
|= PFM_SPACEBEFORE
;
741 info
->fmt
.dySpaceBefore
= info
->rtfParam
;
743 case rtfSpaceBetween
:
744 info
->fmt
.dwMask
|= PFM_LINESPACING
;
745 if ((int)info
->rtfParam
> 0)
747 info
->fmt
.dyLineSpacing
= info
->rtfParam
;
748 info
->fmt
.bLineSpacingRule
= 3;
752 info
->fmt
.dyLineSpacing
= info
->rtfParam
;
753 info
->fmt
.bLineSpacingRule
= 4;
756 case rtfSpaceMultiply
:
757 info
->fmt
.dwMask
|= PFM_LINESPACING
;
758 info
->fmt
.dyLineSpacing
= info
->rtfParam
* 20;
759 info
->fmt
.bLineSpacingRule
= 5;
762 info
->fmt
.dwMask
|= PFM_NUMBERING
;
763 info
->fmt
.wNumbering
= PFN_BULLET
;
766 info
->fmt
.dwMask
|= PFM_NUMBERING
;
767 info
->fmt
.wNumbering
= 2; /* FIXME: MSDN says it's not used ?? */
770 info
->borderType
= RTFBorderParaLeft
;
771 info
->fmt
.wBorders
|= 1;
772 info
->fmt
.dwMask
|= PFM_BORDER
;
775 info
->borderType
= RTFBorderParaRight
;
776 info
->fmt
.wBorders
|= 2;
777 info
->fmt
.dwMask
|= PFM_BORDER
;
780 info
->borderType
= RTFBorderParaTop
;
781 info
->fmt
.wBorders
|= 4;
782 info
->fmt
.dwMask
|= PFM_BORDER
;
784 case rtfBorderBottom
:
785 info
->borderType
= RTFBorderParaBottom
;
786 info
->fmt
.wBorders
|= 8;
787 info
->fmt
.dwMask
|= PFM_BORDER
;
789 case rtfBorderSingle
:
790 info
->fmt
.wBorders
&= ~0x700;
791 info
->fmt
.wBorders
|= 1 << 8;
792 info
->fmt
.dwMask
|= PFM_BORDER
;
795 info
->fmt
.wBorders
&= ~0x700;
796 info
->fmt
.wBorders
|= 2 << 8;
797 info
->fmt
.dwMask
|= PFM_BORDER
;
799 case rtfBorderShadow
:
800 info
->fmt
.wBorders
&= ~0x700;
801 info
->fmt
.wBorders
|= 10 << 8;
802 info
->fmt
.dwMask
|= PFM_BORDER
;
804 case rtfBorderDouble
:
805 info
->fmt
.wBorders
&= ~0x700;
806 info
->fmt
.wBorders
|= 7 << 8;
807 info
->fmt
.dwMask
|= PFM_BORDER
;
810 info
->fmt
.wBorders
&= ~0x700;
811 info
->fmt
.wBorders
|= 11 << 8;
812 info
->fmt
.dwMask
|= PFM_BORDER
;
816 int borderSide
= info
->borderType
& RTFBorderSideMask
;
817 RTFTable
*tableDef
= info
->tableDef
;
818 if ((info
->borderType
& RTFBorderTypeMask
) == RTFBorderTypeCell
)
821 if (!tableDef
|| tableDef
->numCellsDefined
>= MAX_TABLE_CELLS
)
823 border
= &tableDef
->cells
[tableDef
->numCellsDefined
].border
[borderSide
];
824 border
->width
= info
->rtfParam
;
827 info
->fmt
.wBorderWidth
= info
->rtfParam
;
828 info
->fmt
.dwMask
|= PFM_BORDER
;
832 info
->fmt
.wBorderSpace
= info
->rtfParam
;
833 info
->fmt
.dwMask
|= PFM_BORDER
;
837 RTFTable
*tableDef
= info
->tableDef
;
838 int borderSide
= info
->borderType
& RTFBorderSideMask
;
839 int borderType
= info
->borderType
& RTFBorderTypeMask
;
842 case RTFBorderTypePara
:
843 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
845 /* v1.0 - 3.0 treat paragraph and row borders the same. */
846 case RTFBorderTypeRow
:
848 tableDef
->border
[borderSide
].color
= info
->rtfParam
;
851 case RTFBorderTypeCell
:
852 if (tableDef
&& tableDef
->numCellsDefined
< MAX_TABLE_CELLS
) {
853 tableDef
->cells
[tableDef
->numCellsDefined
].border
[borderSide
].color
= info
->rtfParam
;
860 info
->fmt
.dwMask
|= PFM_RTLPARA
;
861 info
->fmt
.wEffects
|= PFE_RTLPARA
;
864 info
->fmt
.dwMask
|= PFM_RTLPARA
;
865 info
->fmt
.wEffects
&= ~PFE_RTLPARA
;
870 void ME_RTFTblAttrHook(RTF_Info
*info
)
872 switch (info
->rtfMinor
)
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
);
883 ME_InitTableDef(info
->editor
, info
->tableDef
);
892 info
->tableDef
= ME_MakeTableDef(info
->editor
);
894 cellNum
= info
->tableDef
->numCellsDefined
;
895 if (cellNum
>= MAX_TABLE_CELLS
)
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
= ¶
->member
.para
.fmt
;
903 pFmt
->rgxTabs
[cellNum
] &= ~0x00FFFFFF;
904 pFmt
->rgxTabs
[cellNum
] |= 0x00FFFFFF & info
->rtfParam
;
906 info
->tableDef
->numCellsDefined
++;
910 info
->borderType
= RTFBorderRowTop
;
913 info
->borderType
= RTFBorderRowLeft
;
915 case rtfRowBordBottom
:
916 info
->borderType
= RTFBorderRowBottom
;
918 case rtfRowBordRight
:
919 info
->borderType
= RTFBorderRowRight
;
922 info
->borderType
= RTFBorderCellTop
;
924 case rtfCellBordLeft
:
925 info
->borderType
= RTFBorderCellLeft
;
927 case rtfCellBordBottom
:
928 info
->borderType
= RTFBorderCellBottom
;
930 case rtfCellBordRight
:
931 info
->borderType
= RTFBorderCellRight
;
935 info
->tableDef
->gapH
= info
->rtfParam
;
939 info
->tableDef
->leftEdge
= info
->rtfParam
;
944 void ME_RTFSpecialCharHook(RTF_Info
*info
)
946 RTFTable
*tableDef
= info
->tableDef
;
947 switch (info
->rtfMinor
)
950 if (info
->editor
->bEmulateVersion10
) /* v1.0 - v3.0 */
952 /* else fall through since v4.1 treats rtfNestCell and rtfCell the same */
956 RTFFlushOutputBuffer(info
);
957 if (!info
->editor
->bEmulateVersion10
) { /* v4.1 */
958 if (tableDef
->tableRowStart
)
960 if (!info
->nestingLevel
&&
961 tableDef
->tableRowStart
->member
.para
.nFlags
& MEPF_ROWEND
)
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;
969 ME_InsertTableCellFromCursor(info
->editor
);
971 } else { /* v1.0 - v3.0 */
972 ME_DisplayItem
*para
= info
->editor
->pCursors
[0].pPara
;
973 PARAFORMAT2
*pFmt
= ¶
->member
.para
.fmt
;
974 if (pFmt
->dwMask
& PFM_TABLE
&& pFmt
->wEffects
& PFE_TABLE
&&
975 tableDef
->numCellsInserted
< tableDef
->numCellsDefined
)
978 ME_InsertTextFromCursor(info
->editor
, 0, &tab
, 1, info
->style
);
979 tableDef
->numCellsInserted
++;
984 if (info
->editor
->bEmulateVersion10
) /* v1.0 - v3.0 */
986 /* else fall through since v4.1 treats rtfNestRow and rtfRow the same */
989 ME_DisplayItem
*para
, *cell
, *run
;
994 RTFFlushOutputBuffer(info
);
995 if (!info
->editor
->bEmulateVersion10
) { /* v4.1 */
996 if (!tableDef
->tableRowStart
)
998 if (!info
->nestingLevel
&&
999 tableDef
->tableRowStart
->member
.para
.nFlags
& MEPF_ROWEND
)
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
++;
1007 para
= tableDef
->tableRowStart
;
1008 cell
= ME_FindItemFwd(para
, diCell
);
1009 assert(cell
&& !cell
->member
.cell
.prev_cell
);
1010 if (tableDef
->numCellsDefined
< 1)
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
;
1022 para
= ME_InsertTableCellFromCursor(info
->editor
);
1023 cell
= para
->member
.para
.pCell
;
1024 cell
->member
.cell
.nRightBoundary
= nRightBoundary
;
1026 for (i
= 0; i
< tableDef
->numCellsDefined
; i
++)
1028 RTFCell
*cellDef
= &tableDef
->cells
[i
];
1029 cell
->member
.cell
.nRightBoundary
= cellDef
->rightBoundary
;
1030 ME_ApplyBorderProperties(info
, &cell
->member
.cell
.border
,
1032 cell
= cell
->member
.cell
.next_cell
;
1035 para
= ME_InsertTableCellFromCursor(info
->editor
);
1036 cell
= para
->member
.para
.pCell
;
1039 /* Cell for table row delimiter is empty */
1040 cell
->member
.cell
.nRightBoundary
= tableDef
->cells
[i
-1].rightBoundary
;
1043 run
= ME_FindItemFwd(cell
, diRun
);
1044 if (info
->editor
->pCursors
[0].pRun
!= run
||
1045 info
->editor
->pCursors
[0].nOffset
)
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],
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
, ¶
->member
.para
.border
,
1063 info
->nestingLevel
--;
1064 if (!info
->nestingLevel
)
1066 if (info
->canInheritInTbl
) {
1067 tableDef
->tableRowStart
= para
;
1069 while (info
->tableDef
) {
1070 tableDef
= info
->tableDef
;
1071 info
->tableDef
= tableDef
->parent
;
1072 heap_free(tableDef
);
1076 info
->tableDef
= tableDef
->parent
;
1077 heap_free(tableDef
);
1079 } else { /* v1.0 - v3.0 */
1081 ME_DisplayItem
*para
= info
->editor
->pCursors
[0].pPara
;
1082 PARAFORMAT2
*pFmt
= ¶
->member
.para
.fmt
;
1083 pFmt
->dxOffset
= info
->tableDef
->gapH
;
1084 pFmt
->dxStartIndent
= info
->tableDef
->leftEdge
;
1086 ME_ApplyBorderProperties(info
, ¶
->member
.para
.border
,
1088 while (tableDef
->numCellsInserted
< tableDef
->numCellsDefined
)
1091 ME_InsertTextFromCursor(info
->editor
, 0, &tab
, 1, info
->style
);
1092 tableDef
->numCellsInserted
++;
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;
1104 if (info
->editor
->bEmulateVersion10
) { /* v1.0 - 3.0 */
1105 ME_DisplayItem
*para
;
1107 RTFFlushOutputBuffer(info
);
1108 para
= info
->editor
->pCursors
[0].pPara
;
1109 pFmt
= ¶
->member
.para
.fmt
;
1110 if (pFmt
->dwMask
& PFM_TABLE
&& pFmt
->wEffects
& PFE_TABLE
)
1112 /* rtfPar is treated like a space within a table. */
1113 info
->rtfClass
= rtfText
;
1114 info
->rtfMajor
= ' ';
1116 else if (info
->rtfMinor
== rtfPar
&& tableDef
)
1117 tableDef
->numCellsInserted
= 0;
1123 static HRESULT
insert_static_object(ME_TextEditor
*editor
, HENHMETAFILE hemf
, HBITMAP hbmp
,
1126 LPOLEOBJECT lpObject
= NULL
;
1127 LPSTORAGE lpStorage
= NULL
;
1128 LPOLECLIENTSITE lpClientSite
= NULL
;
1129 LPDATAOBJECT lpDataObject
= NULL
;
1130 LPOLECACHE lpOleCache
= NULL
;
1134 HRESULT hr
= E_FAIL
;
1139 stgm
.tymed
= TYMED_ENHMF
;
1140 stgm
.u
.hEnhMetaFile
= hemf
;
1141 fm
.cfFormat
= CF_ENHMETAFILE
;
1145 stgm
.tymed
= TYMED_GDI
;
1146 stgm
.u
.hBitmap
= hbmp
;
1147 fm
.cfFormat
= CF_BITMAP
;
1149 stgm
.pUnkForRelease
= NULL
;
1152 fm
.dwAspect
= DVASPECT_CONTENT
;
1154 fm
.tymed
= stgm
.tymed
;
1158 if (!CreateIRichEditOle(NULL
, editor
, (LPVOID
*)&editor
->reOle
))
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
)
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;
1186 ME_InsertOLEFromCursor(editor
, &reobject
, 0);
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
);
1199 static void ME_RTFReadShpPictGroup( RTF_Info
*info
)
1207 if (info
->rtfClass
== rtfEOF
) return;
1208 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1210 if (--level
== 0) break;
1212 else if (RTFCheckCM( info
, rtfGroup
, rtfBeginGroup
))
1218 RTFRouteToken( info
);
1219 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1224 RTFRouteToken( info
); /* feed "}" back to router */
1228 static DWORD
read_hex_data( RTF_Info
*info
, BYTE
**out
)
1230 DWORD read
= 0, size
= 1024;
1236 if (info
->rtfClass
!= rtfText
)
1238 ERR("Called with incorrect token\n");
1242 buf
= HeapAlloc( GetProcessHeap(), 0, size
);
1245 val
= info
->rtfMajor
;
1246 for (flip
= TRUE
;; flip
= !flip
)
1248 RTFGetToken( info
);
1249 if (info
->rtfClass
== rtfEOF
)
1251 HeapFree( GetProcessHeap(), 0, buf
);
1254 if (info
->rtfClass
!= rtfText
) break;
1260 buf
= HeapReAlloc( GetProcessHeap(), 0, buf
, size
);
1263 buf
[read
++] = RTFCharToHex(val
) * 16 + RTFCharToHex(info
->rtfMajor
);
1266 val
= info
->rtfMajor
;
1268 if (flip
) FIXME("wrong hex string\n");
1274 static void ME_RTFReadPictGroup(RTF_Info
*info
)
1277 BYTE
*buffer
= NULL
;
1282 enum gfxkind
{gfx_unknown
= 0, gfx_enhmetafile
, gfx_metafile
, gfx_dib
} gfx
= gfx_unknown
;
1290 RTFGetToken( info
);
1292 if (info
->rtfClass
== rtfText
)
1297 size
= read_hex_data( info
, &buffer
);
1301 RTFSkipGroup( info
);
1303 } /* We potentially have a new token so fall through. */
1305 if (info
->rtfClass
== rtfEOF
) return;
1307 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1309 if (--level
== 0) break;
1312 if (RTFCheckCM( info
, rtfGroup
, rtfBeginGroup
))
1317 if (!RTFCheckCM( info
, rtfControl
, rtfPictAttr
))
1319 RTFRouteToken( info
);
1320 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1325 if (RTFCheckMM( info
, rtfPictAttr
, rtfWinMetafile
))
1327 mfp
.mm
= info
->rtfParam
;
1330 else if (RTFCheckMM( info
, rtfPictAttr
, rtfDevIndBitmap
))
1332 if (info
->rtfParam
!= 0) FIXME("dibitmap should be 0 (%d)\n", info
->rtfParam
);
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
;
1346 FIXME("Non supported attribute: %d %d %d\n", info
->rtfClass
, info
->rtfMajor
, info
->rtfMinor
);
1353 case gfx_enhmetafile
:
1354 if ((hemf
= SetEnhMetaFileBits( size
, buffer
)))
1355 insert_static_object( info
->editor
, hemf
, NULL
, &sz
);
1358 if ((hemf
= SetWinMetaFileBits( size
, buffer
, NULL
, &mfp
)))
1359 insert_static_object( info
->editor
, hemf
, NULL
, &sz
);
1363 BITMAPINFO
*bi
= (BITMAPINFO
*)buffer
;
1365 unsigned nc
= bi
->bmiHeader
.biClrUsed
;
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
);
1381 HeapFree( GetProcessHeap(), 0, buffer
);
1382 RTFRouteToken( info
); /* feed "}" back to router */
1386 /* for now, lookup the \result part and use it, whatever the object */
1387 static void ME_RTFReadObjectGroup(RTF_Info
*info
)
1392 if (info
->rtfClass
== rtfEOF
)
1394 if (RTFCheckCM(info
, rtfGroup
, rtfEndGroup
))
1396 if (RTFCheckCM(info
, rtfGroup
, rtfBeginGroup
))
1399 if (info
->rtfClass
== rtfEOF
)
1401 if (RTFCheckCMM(info
, rtfControl
, rtfDestination
, rtfObjResult
))
1405 while (RTFGetToken (info
) != rtfEOF
)
1407 if (info
->rtfClass
== rtfGroup
)
1409 if (info
->rtfMajor
== rtfBeginGroup
) level
++;
1410 else if (info
->rtfMajor
== rtfEndGroup
&& --level
< 0) break;
1412 RTFRouteToken(info
);
1415 else RTFSkipGroup(info
);
1418 if (!RTFCheckCM (info
, rtfControl
, rtfObjAttr
))
1420 FIXME("Non supported attribute: %d %d %d\n", info
->rtfClass
, info
->rtfMajor
, info
->rtfMinor
);
1424 RTFRouteToken(info
); /* feed "}" back to router */
1427 static void ME_RTFReadParnumGroup( RTF_Info
*info
)
1429 int level
= 1, type
= -1;
1430 WORD indent
= 0, start
= 1;
1431 WCHAR txt_before
= 0, txt_after
= 0;
1435 RTFGetToken( info
);
1437 if (RTFCheckCMM( info
, rtfControl
, rtfDestination
, rtfParNumTextBefore
) ||
1438 RTFCheckCMM( info
, rtfControl
, rtfDestination
, rtfParNumTextAfter
))
1440 int loc
= info
->rtfMinor
;
1442 RTFGetToken( info
);
1443 if (info
->rtfClass
== rtfText
)
1445 if (loc
== rtfParNumTextBefore
)
1446 txt_before
= info
->rtfMajor
;
1448 txt_after
= info
->rtfMajor
;
1451 /* falling through to catch EOFs and group level changes */
1454 if (info
->rtfClass
== rtfEOF
)
1457 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1459 if (--level
== 0) break;
1463 if (RTFCheckCM( info
, rtfGroup
, rtfBeginGroup
))
1469 /* Ignore non para-attr */
1470 if (!RTFCheckCM( info
, rtfControl
, rtfParAttr
))
1473 switch (info
->rtfMinor
)
1475 case rtfParLevel
: /* Para level is ignored */
1482 case rtfParNumDecimal
:
1485 case rtfParNumULetter
:
1486 type
= PFN_UCLETTER
;
1488 case rtfParNumURoman
:
1491 case rtfParNumLLetter
:
1492 type
= PFN_LCLETTER
;
1494 case rtfParNumLRoman
:
1498 case rtfParNumIndent
:
1499 indent
= info
->rtfParam
;
1501 case rtfParNumStartAt
:
1502 start
= info
->rtfParam
;
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
)
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
;
1522 info
->fmt
.wNumberingTab
= indent
;
1525 TRACE("type %d indent %d start %d txt before %04x txt after %04x\n",
1526 type
, indent
, start
, txt_before
, txt_after
);
1528 RTFRouteToken( info
); /* feed "}" back to router */
1531 static void ME_RTFReadHook(RTF_Info
*info
)
1533 switch(info
->rtfClass
)
1536 switch(info
->rtfMajor
)
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
;
1546 info
->styleChanged
= FALSE
;
1550 RTFFlushOutputBuffer(info
);
1552 if (info
->stackTop
<= 0)
1553 info
->rtfClass
= rtfEOF
;
1554 if (info
->stackTop
< 0)
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
;
1569 ME_StreamInFill(ME_InStream
*stream
)
1571 stream
->editstream
->dwError
= stream
->editstream
->pfnCallback(stream
->editstream
->dwCookie
,
1572 (BYTE
*)stream
->buffer
,
1573 sizeof(stream
->buffer
),
1574 (LONG
*)&stream
->dwSize
);
1578 static LRESULT
ME_StreamIn(ME_TextEditor
*editor
, DWORD format
, EDITSTREAM
*stream
, BOOL stripLastCR
)
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 */
1589 TRACE("stream==%p editor==%p format==0x%X\n", stream
, editor
, format
);
1590 editor
->nEventMask
= 0;
1592 ME_GetSelectionOfs(editor
, &from
, &to
);
1593 if (format
& SFF_SELECTION
&& editor
->mode
& TM_RICHTEXT
)
1595 ME_GetSelection(editor
, &selStart
, &selEnd
);
1596 style
= ME_GetSelectionInsertStyle(editor
);
1598 ME_InternalDeleteText(editor
, selStart
, to
- from
, FALSE
);
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
)
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;
1610 if (para
->member
.para
.nFlags
& MEPF_ROWSTART
)
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;
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
))
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
);
1630 ME_ClearTempStyle(editor
);
1631 ME_SetDefaultParaFormat(editor
, &editor
->pCursors
[0].pPara
->member
.para
.fmt
);
1635 /* Back up undo mode to a local variable */
1636 nUndoMode
= editor
->nUndoMode
;
1638 /* Only create an undo if SFF_SELECTION is set */
1639 if (!(format
& SFF_SELECTION
))
1640 editor
->nUndoMode
= umIgnore
;
1642 inStream
.editstream
= stream
;
1643 inStream
.editstream
->dwError
= 0;
1644 inStream
.dwSize
= 0;
1645 inStream
.dwUsed
= 0;
1647 if (format
& SF_RTF
)
1649 /* Check if it's really RTF, and if it is not, use plain text */
1650 ME_StreamInFill(&inStream
);
1651 if (!inStream
.editstream
->dwError
)
1653 if ((!editor
->bEmulateVersion10
&& strncmp(inStream
.buffer
, "{\\rtf", 5) && strncmp(inStream
.buffer
, "{\\urtf", 6))
1654 || (editor
->bEmulateVersion10
&& *inStream
.buffer
!= '{'))
1657 inStream
.editstream
->dwError
= -16;
1662 if (!invalidRTF
&& !inStream
.editstream
->dwError
)
1665 from
= ME_GetCursorOfs(&editor
->pCursors
[0]);
1666 if (format
& SF_RTF
) {
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
);
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 */
1683 RTFSetDestinationCallback(&parser
, rtfNoNestTables
, RTFSkipGroup
);
1684 RTFSetDestinationCallback(&parser
, rtfNestTableProps
, RTFReadGroup
);
1688 /* do the parsing */
1690 RTFFlushOutputBuffer(&parser
);
1691 if (!editor
->bEmulateVersion10
) { /* v4.1 */
1692 if (parser
.tableDef
&& parser
.tableDef
->tableRowStart
&&
1693 (parser
.nestingLevel
> 0 || parser
.canInheritInTbl
))
1695 /* Delete any incomplete table row at the end of the rich text. */
1697 ME_DisplayItem
*para
;
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)
1706 while (parser
.nestingLevel
> 1)
1707 ME_RTFSpecialCharHook(&parser
); /* Decrements nestingLevel */
1708 para
= parser
.tableDef
->tableRowStart
;
1709 ME_RTFSpecialCharHook(&parser
);
1711 para
= parser
.tableDef
->tableRowStart
;
1712 ME_RTFSpecialCharHook(&parser
);
1713 assert(para
->member
.para
.nFlags
& MEPF_ROWEND
);
1714 para
= para
->member
.para
.next_para
;
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
;
1727 ME_CheckTablesForCorruption(editor
);
1728 RTFDestroy(&parser
);
1730 if (parser
.stackTop
> 0)
1732 while (--parser
.stackTop
>= 0)
1734 ME_ReleaseStyle(parser
.style
);
1735 parser
.style
= parser
.stack
[parser
.stackTop
].style
;
1737 if (!inStream
.editstream
->dwError
)
1738 inStream
.editstream
->dwError
= HRESULT_FROM_WIN32(ERROR_HANDLE_EOF
);
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
1745 if (stripLastCR
&& !(format
& SFF_SELECTION
)) {
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
;
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
);
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
);
1771 to
= ME_GetCursorOfs(&editor
->pCursors
[0]);
1772 num_read
= to
- from
;
1774 style
= parser
.style
;
1776 else if (format
& SF_TEXT
)
1778 num_read
= ME_StreamInText(editor
, format
, &inStream
, style
);
1779 to
= ME_GetCursorOfs(&editor
->pCursors
[0]);
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
);
1790 /* Restore saved undo mode */
1791 editor
->nUndoMode
= nUndoMode
;
1793 /* even if we didn't add an undo, we need to commit anything on the stack */
1794 ME_CommitUndo(editor
);
1796 /* If SFF_SELECTION isn't set, delete any undos from before we started too */
1797 if (!(format
& SFF_SELECTION
))
1798 ME_EmptyUndoStack(editor
);
1800 ME_ReleaseStyle(style
);
1801 editor
->nEventMask
= nEventMask
;
1802 ME_UpdateRepaint(editor
, FALSE
);
1803 if (!(format
& SFF_SELECTION
)) {
1804 ME_ClearTempStyle(editor
);
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
);
1816 typedef struct tagME_RTFStringStreamStruct
1821 } ME_RTFStringStreamStruct
;
1823 static DWORD CALLBACK
ME_ReadFromRTFString(DWORD_PTR dwCookie
, LPBYTE lpBuff
, LONG cb
, LONG
*pcb
)
1825 ME_RTFStringStreamStruct
*pStruct
= (ME_RTFStringStreamStruct
*)dwCookie
;
1828 count
= min(cb
, pStruct
->length
- pStruct
->pos
);
1829 memmove(lpBuff
, pStruct
->string
+ pStruct
->pos
, count
);
1830 pStruct
->pos
+= count
;
1836 ME_StreamInRTFString(ME_TextEditor
*editor
, BOOL selection
, char *string
)
1839 ME_RTFStringStreamStruct data
;
1841 data
.string
= string
;
1842 data
.length
= strlen(string
);
1844 es
.dwCookie
= (DWORD_PTR
)&data
;
1845 es
.pfnCallback
= ME_ReadFromRTFString
;
1846 ME_StreamIn(editor
, SF_RTF
| (selection
? SFF_SELECTION
: 0), &es
, TRUE
);
1851 ME_FindText(ME_TextEditor
*editor
, DWORD flags
, const CHARRANGE
*chrg
, const WCHAR
*text
, CHARRANGE
*chrgText
)
1853 const int nLen
= lstrlenW(text
);
1854 const int nTextLen
= ME_GetTextLength(editor
);
1857 WCHAR wLastChar
= ' ';
1859 TRACE("flags==0x%08x, chrg->cpMin==%d, chrg->cpMax==%d text==%s\n",
1860 flags
, chrg
->cpMin
, chrg
->cpMax
, debugstr_w(text
));
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
));
1867 if (chrg
->cpMax
== -1)
1870 nMax
= chrg
->cpMax
> nTextLen
? nTextLen
: chrg
->cpMax
;
1872 /* In 1.0 emulation, if cpMax reaches end of text, add the FR_DOWN flag */
1873 if (editor
->bEmulateVersion10
&& nMax
== nTextLen
)
1878 /* In 1.0 emulation, cpMin must always be no greater than cpMax */
1879 if (editor
->bEmulateVersion10
&& nMax
< nMin
)
1883 chrgText
->cpMin
= -1;
1884 chrgText
->cpMax
= -1;
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.
1894 if (!editor
->bEmulateVersion10
&& !(flags
& FR_DOWN
))
1898 nMax
= nMin
> nTextLen
? nTextLen
: nMin
;
1899 if (nMin
< nSwap
|| chrg
->cpMax
== -1)
1905 if (!nLen
|| nMin
< 0 || nMax
< 0 || nMax
< nMin
)
1908 chrgText
->cpMin
= chrgText
->cpMax
= -1;
1912 if (flags
& FR_DOWN
) /* Forward search */
1914 /* If possible, find the character before where the search starts */
1915 if ((flags
& FR_WHOLEWORD
) && nMin
)
1917 ME_CursorFromCharOfs(editor
, nMin
- 1, &cursor
);
1918 wLastChar
= *get_text( &cursor
.pRun
->member
.run
, cursor
.nOffset
);
1919 ME_MoveCursorChars(editor
, &cursor
, 1, FALSE
);
1921 ME_CursorFromCharOfs(editor
, nMin
, &cursor
);
1924 while (cursor
.pRun
&& ME_GetCursorOfs(&cursor
) + nLen
<= nMax
)
1926 ME_DisplayItem
*pCurItem
= cursor
.pRun
;
1927 int nCurStart
= cursor
.nOffset
;
1930 while (pCurItem
&& ME_CharCompare( *get_text( &pCurItem
->member
.run
, nCurStart
+ nMatched
), text
[nMatched
], (flags
& FR_MATCHCASE
)))
1932 if ((flags
& FR_WHOLEWORD
) && isalnumW(wLastChar
))
1936 if (nMatched
== nLen
)
1938 ME_DisplayItem
*pNextItem
= pCurItem
;
1939 int nNextStart
= nCurStart
;
1942 /* Check to see if next character is a whitespace */
1943 if (flags
& FR_WHOLEWORD
)
1945 if (nCurStart
+ nMatched
== pCurItem
->member
.run
.len
)
1947 pNextItem
= ME_FindItemFwd(pCurItem
, diRun
);
1948 nNextStart
= -nMatched
;
1952 wNextChar
= *get_text( &pNextItem
->member
.run
, nNextStart
+ nMatched
);
1956 if (isalnumW(wNextChar
))
1960 cursor
.nOffset
+= cursor
.pPara
->member
.para
.nCharOfs
+ cursor
.pRun
->member
.run
.nCharOfs
;
1963 chrgText
->cpMin
= cursor
.nOffset
;
1964 chrgText
->cpMax
= cursor
.nOffset
+ nLen
;
1966 TRACE("found at %d-%d\n", cursor
.nOffset
, cursor
.nOffset
+ nLen
);
1967 return cursor
.nOffset
;
1969 if (nCurStart
+ nMatched
== pCurItem
->member
.run
.len
)
1971 pCurItem
= ME_FindItemFwd(pCurItem
, diRun
);
1972 nCurStart
= -nMatched
;
1976 wLastChar
= *get_text( &pCurItem
->member
.run
, nCurStart
+ nMatched
);
1981 if (cursor
.nOffset
== cursor
.pRun
->member
.run
.len
)
1983 ME_NextRun(&cursor
.pPara
, &cursor
.pRun
, TRUE
);
1988 else /* Backward search */
1990 /* If possible, find the character after where the search ends */
1991 if ((flags
& FR_WHOLEWORD
) && nMax
< nTextLen
- 1)
1993 ME_CursorFromCharOfs(editor
, nMax
+ 1, &cursor
);
1994 wLastChar
= *get_text( &cursor
.pRun
->member
.run
, cursor
.nOffset
);
1995 ME_MoveCursorChars(editor
, &cursor
, -1, FALSE
);
1997 ME_CursorFromCharOfs(editor
, nMax
, &cursor
);
2000 while (cursor
.pRun
&& ME_GetCursorOfs(&cursor
) - nLen
>= nMin
)
2002 ME_DisplayItem
*pCurItem
= cursor
.pRun
;
2003 ME_DisplayItem
*pCurPara
= cursor
.pPara
;
2004 int nCurEnd
= cursor
.nOffset
;
2009 ME_PrevRun(&pCurPara
, &pCurItem
, TRUE
);
2010 nCurEnd
= pCurItem
->member
.run
.len
;
2013 while (pCurItem
&& ME_CharCompare( *get_text( &pCurItem
->member
.run
, nCurEnd
- nMatched
- 1 ),
2014 text
[nLen
- nMatched
- 1], (flags
& FR_MATCHCASE
) ))
2016 if ((flags
& FR_WHOLEWORD
) && isalnumW(wLastChar
))
2020 if (nMatched
== nLen
)
2022 ME_DisplayItem
*pPrevItem
= pCurItem
;
2023 int nPrevEnd
= nCurEnd
;
2027 /* Check to see if previous character is a whitespace */
2028 if (flags
& FR_WHOLEWORD
)
2030 if (nPrevEnd
- nMatched
== 0)
2032 pPrevItem
= ME_FindItemBack(pCurItem
, diRun
);
2034 nPrevEnd
= pPrevItem
->member
.run
.len
+ nMatched
;
2038 wPrevChar
= *get_text( &pPrevItem
->member
.run
, nPrevEnd
- nMatched
- 1 );
2042 if (isalnumW(wPrevChar
))
2046 nStart
= pCurPara
->member
.para
.nCharOfs
2047 + pCurItem
->member
.run
.nCharOfs
+ nCurEnd
- nMatched
;
2050 chrgText
->cpMin
= nStart
;
2051 chrgText
->cpMax
= nStart
+ nLen
;
2053 TRACE("found at %d-%d\n", nStart
, nStart
+ nLen
);
2056 if (nCurEnd
- nMatched
== 0)
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
;
2065 wLastChar
= *get_text( &pCurItem
->member
.run
, nCurEnd
- nMatched
- 1 );
2070 if (cursor
.nOffset
< 0)
2072 ME_PrevRun(&cursor
.pPara
, &cursor
.pRun
, TRUE
);
2073 cursor
.nOffset
= cursor
.pRun
->member
.run
.len
;
2077 TRACE("not found\n");
2079 chrgText
->cpMin
= chrgText
->cpMax
= -1;
2083 static int ME_GetTextEx(ME_TextEditor
*editor
, GETTEXTEX
*ex
, LPARAM pText
)
2088 if (!ex
->cb
|| !pText
) return 0;
2090 if (ex
->flags
& ~(GT_SELECTION
| GT_USECRLF
))
2091 FIXME("GETTEXTEX flags 0x%08x not supported\n", ex
->flags
& ~(GT_SELECTION
| GT_USECRLF
));
2093 if (ex
->flags
& GT_SELECTION
)
2096 int nStartCur
= ME_GetSelectionOfs(editor
, &from
, &to
);
2097 start
= editor
->pCursors
[nStartCur
];
2102 ME_SetCursorToStart(editor
, &start
);
2105 if (ex
->codepage
== CP_UNICODE
)
2107 return ME_GetTextW(editor
, (LPWSTR
)pText
, ex
->cb
/ sizeof(WCHAR
) - 1,
2108 &start
, nChars
, ex
->flags
& GT_USECRLF
, FALSE
);
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
2117 int crlfmul
= (ex
->flags
& GT_USECRLF
) ? 2 : 1;
2122 buflen
= min(crlfmul
* nChars
, ex
->cb
- 1);
2123 buffer
= heap_alloc((buflen
+ 1) * sizeof(WCHAR
));
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 */
2135 static int ME_GetTextRange(ME_TextEditor
*editor
, WCHAR
*strText
,
2136 const ME_Cursor
*start
, int nLen
, BOOL unicode
)
2138 if (!strText
) return 0;
2140 return ME_GetTextW(editor
, strText
, INT_MAX
, start
, nLen
, FALSE
, FALSE
);
2143 WCHAR
*p
= heap_alloc((nLen
+1) * sizeof(*p
));
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
);
2153 static int handle_EM_EXSETSEL( ME_TextEditor
*editor
, int to
, int from
)
2157 TRACE("%d - %d\n", to
, from
);
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
);
2169 typedef struct tagME_GlobalDestStruct
2173 } ME_GlobalDestStruct
;
2175 static DWORD CALLBACK
ME_ReadFromHGLOBALUnicode(DWORD_PTR dwCookie
, LPBYTE lpBuff
, LONG cb
, LONG
*pcb
)
2177 ME_GlobalDestStruct
*pData
= (ME_GlobalDestStruct
*)dwCookie
;
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
];
2187 pData
->nLength
+= i
;
2189 GlobalUnlock(pData
->hData
);
2193 static DWORD CALLBACK
ME_ReadFromHGLOBALRTF(DWORD_PTR dwCookie
, LPBYTE lpBuff
, LONG cb
, LONG
*pcb
)
2195 ME_GlobalDestStruct
*pData
= (ME_GlobalDestStruct
*)dwCookie
;
2200 pSrc
= GlobalLock(pData
->hData
);
2201 for (i
= 0; i
<cb
&& pSrc
[pData
->nLength
+i
]; i
++) {
2202 pDest
[i
] = pSrc
[pData
->nLength
+i
];
2204 pData
->nLength
+= i
;
2206 GlobalUnlock(pData
->hData
);
2210 static const WCHAR rtfW
[] = {'R','i','c','h',' ','T','e','x','t',' ','F','o','r','m','a','t',0};
2212 static HRESULT
paste_rtf(ME_TextEditor
*editor
, FORMATETC
*fmt
, STGMEDIUM
*med
)
2215 ME_GlobalDestStruct gds
;
2218 gds
.hData
= med
->u
.hGlobal
;
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
);
2227 static HRESULT
paste_text(ME_TextEditor
*editor
, FORMATETC
*fmt
, STGMEDIUM
*med
)
2230 ME_GlobalDestStruct gds
;
2233 gds
.hData
= med
->u
.hGlobal
;
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
);
2242 static HRESULT
paste_emf(ME_TextEditor
*editor
, FORMATETC
*fmt
, STGMEDIUM
*med
)
2247 hr
= insert_static_object( editor
, med
->u
.hEnhMetaFile
, NULL
, &sz
);
2250 ME_CommitUndo( editor
);
2251 ME_UpdateRepaint( editor
, FALSE
);
2254 ReleaseStgMedium( med
);
2259 static struct paste_format
2262 HRESULT (*paste
)(ME_TextEditor
*, FORMATETC
*, STGMEDIUM
*);
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
},
2272 static void init_paste_formats(void)
2274 struct paste_format
*format
;
2279 for (format
= paste_formats
; format
->fmt
.cfFormat
; format
++)
2282 format
->fmt
.cfFormat
= RegisterClipboardFormatW( format
->name
);
2288 static BOOL
paste_special(ME_TextEditor
*editor
, UINT cf
, REPASTESPECIAL
*ps
, BOOL check_only
)
2292 struct paste_format
*format
;
2295 /* Protect read-only edit control from modification */
2296 if (editor
->styleFlags
& ES_READONLY
)
2299 MessageBeep(MB_ICONERROR
);
2303 init_paste_formats();
2305 if (ps
&& ps
->dwAspect
!= DVASPECT_CONTENT
)
2306 FIXME("Ignoring aspect %x\n", ps
->dwAspect
);
2308 hr
= OleGetClipboard( &data
);
2309 if (hr
!= S_OK
) return FALSE
;
2311 if (cf
== CF_TEXT
) cf
= CF_UNICODETEXT
;
2314 for (format
= paste_formats
; format
->fmt
.cfFormat
; format
++)
2316 if (cf
&& cf
!= format
->fmt
.cfFormat
) continue;
2317 hr
= IDataObject_QueryGetData( data
, &format
->fmt
);
2322 hr
= IDataObject_GetData( data
, &format
->fmt
, &med
);
2323 if (hr
!= S_OK
) goto done
;
2324 hr
= format
->paste( editor
, &format
->fmt
, &med
);
2331 IDataObject_Release( data
);
2336 static BOOL
ME_Copy(ME_TextEditor
*editor
, const ME_Cursor
*start
, int nChars
)
2338 LPDATAOBJECT dataObj
= NULL
;
2341 if (editor
->cPasswordMask
)
2342 return FALSE
; /* Copying or Cutting masked text isn't allowed */
2344 if(editor
->lpOleCallback
)
2347 range
.cpMin
= ME_GetCursorOfs(start
);
2348 range
.cpMax
= range
.cpMin
+ nChars
;
2349 hr
= IRichEditOleCallback_GetClipboardData(editor
->lpOleCallback
, &range
, RECO_COPY
, &dataObj
);
2351 if(FAILED(hr
) || !dataObj
)
2352 hr
= ME_GetDataObject(editor
, start
, nChars
, &dataObj
);
2354 hr
= OleSetClipboard(dataObj
);
2355 IDataObject_Release(dataObj
);
2357 return SUCCEEDED(hr
);
2360 static BOOL
copy_or_cut(ME_TextEditor
*editor
, BOOL cut
)
2363 int offs
, num_chars
;
2364 int start_cursor
= ME_GetSelectionOfs(editor
, &offs
, &num_chars
);
2365 ME_Cursor
*sel_start
= &editor
->pCursors
[start_cursor
];
2367 if (cut
&& (editor
->styleFlags
& ES_READONLY
))
2369 MessageBeep(MB_ICONERROR
);
2374 result
= ME_Copy(editor
, sel_start
, num_chars
);
2377 ME_InternalDeleteText(editor
, sel_start
, num_chars
, FALSE
);
2378 ME_CommitUndo(editor
);
2379 ME_UpdateRepaint(editor
, TRUE
);
2384 /* helper to send a msg filter notification */
2386 ME_FilterEvent(ME_TextEditor
*editor
, UINT msg
, WPARAM
* wParam
, LPARAM
* lParam
)
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
;
2395 msgf
.wParam
= *wParam
;
2396 msgf
.lParam
= *lParam
;
2397 if (SendMessageW(editor
->hwndParent
, WM_NOTIFY
, msgf
.nmhdr
.idFrom
, (LPARAM
)&msgf
))
2399 *wParam
= msgf
.wParam
;
2400 *lParam
= msgf
.lParam
;
2401 msgf
.wParam
= *wParam
;
2406 static void ME_UpdateSelectionLinkAttribute(ME_TextEditor
*editor
)
2408 ME_DisplayItem
*startPara
, *endPara
;
2409 ME_DisplayItem
*prev_para
;
2410 ME_Cursor
*from
, *to
;
2414 if (!editor
->AutoURLDetect_bEnable
) return;
2416 ME_GetSelection(editor
, &from
, &to
);
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
;
2423 /* Find paragraph that contains end cursor */
2424 endPara
= to
->pPara
->member
.para
.next_para
;
2426 start
.pPara
= startPara
;
2427 start
.pRun
= ME_FindItemFwd(startPara
, diRun
);
2429 nChars
= endPara
->member
.para
.nCharOfs
- startPara
->member
.para
.nCharOfs
;
2431 ME_UpdateLinkAttribute(editor
, &start
, nChars
);
2435 ME_KeyDown(ME_TextEditor
*editor
, WORD nKey
)
2437 BOOL ctrl_is_down
= GetKeyState(VK_CONTROL
) & 0x8000;
2438 BOOL shift_is_down
= GetKeyState(VK_SHIFT
) & 0x8000;
2440 if (editor
->bMouseCaptured
)
2442 if (nKey
!= VK_SHIFT
&& nKey
!= VK_CONTROL
&& nKey
!= VK_MENU
)
2443 editor
->nSelectionType
= stPosition
;
2451 editor
->nUDArrowX
= -1;
2457 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
2458 ME_ArrowKey(editor
, nKey
, shift_is_down
, ctrl_is_down
);
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
)
2466 if (ME_IsSelection(editor
))
2468 ME_DeleteSelection(editor
);
2469 ME_CommitUndo(editor
);
2471 else if (nKey
== VK_DELETE
)
2473 /* Delete stops group typing.
2474 * (See MSDN remarks on EM_STOPGROUPTYPING message) */
2475 ME_DeleteTextAtCursor(editor
, 1, 1);
2476 ME_CommitUndo(editor
);
2478 else if (ME_ArrowKey(editor
, VK_LEFT
, FALSE
, FALSE
))
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)
2488 ME_ArrowKey(editor
, VK_RIGHT
, FALSE
, FALSE
);
2490 ME_CommitCoalescingUndo(editor
);
2494 ME_MoveCursorFromTableRowStartParagraph(editor
);
2495 ME_UpdateSelectionLinkAttribute(editor
);
2496 ME_UpdateRepaint(editor
, FALSE
);
2497 ME_SendRequestResize(editor
, FALSE
);
2500 if (editor
->bDialogMode
)
2505 if (!(editor
->styleFlags
& ES_WANTRETURN
))
2507 if (editor
->hwndParent
)
2510 dw
= SendMessageW(editor
->hwndParent
, DM_GETDEFID
, 0, 0);
2511 if (HIWORD(dw
) == DC_HASDEFID
)
2513 HWND hwDefCtrl
= GetDlgItem(editor
->hwndParent
, LOWORD(dw
));
2516 SendMessageW(editor
->hwndParent
, WM_NEXTDLGCTL
, (WPARAM
)hwDefCtrl
, TRUE
);
2517 PostMessageW(hwDefCtrl
, WM_KEYDOWN
, VK_RETURN
, 0);
2525 if (editor
->styleFlags
& ES_MULTILINE
)
2527 ME_Cursor cursor
= editor
->pCursors
[0];
2528 ME_DisplayItem
*para
= cursor
.pPara
;
2530 const WCHAR endl
= '\r';
2531 const WCHAR endlv10
[] = {'\r','\n'};
2532 ME_Style
*style
, *eop_style
;
2534 if (editor
->styleFlags
& ES_READONLY
) {
2535 MessageBeep(MB_ICONERROR
);
2539 ME_GetSelectionOfs(editor
, &from
, &to
);
2540 if (editor
->nTextLimit
> ME_GetTextLength(editor
) - (to
-from
))
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
);
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
)
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
, ¶
->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
);
2581 } else { /* v1.0 - 3.0 */
2582 ME_DisplayItem
*para
= cursor
.pPara
;
2583 if (ME_IsInTable(para
))
2585 if (cursor
.pRun
->member
.run
.nFlags
& MERF_ENDPARA
)
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
);
2599 ME_ContinueCoalescingTransaction(editor
);
2600 if (cursor
.pRun
->member
.run
.nCharOfs
+ cursor
.nOffset
== 0 &&
2601 !ME_IsInTable(para
->member
.para
.prev_para
))
2603 /* Insert newline before table */
2604 cursor
.pRun
= ME_FindItemBack(para
, diRun
);
2606 editor
->pCursors
[0].pRun
= cursor
.pRun
;
2607 editor
->pCursors
[0].pPara
= para
->member
.para
.prev_para
;
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
);
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];
2621 ME_CommitCoalescingUndo(editor
);
2622 ME_UpdateRepaint(editor
, FALSE
);
2628 style
= ME_GetInsertStyle(editor
, 0);
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
;
2637 ME_ContinueCoalescingTransaction(editor
);
2639 ME_InsertEndRowFromCursor(editor
, 0);
2641 if (!editor
->bEmulateVersion10
)
2642 ME_InsertTextFromCursor(editor
, 0, &endl
, 1, eop_style
);
2644 ME_InsertTextFromCursor(editor
, 0, endlv10
, 2, eop_style
);
2645 ME_CommitCoalescingUndo(editor
);
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
);
2657 if (editor
->bDialogMode
&& editor
->hwndParent
)
2658 PostMessageW(editor
->hwndParent
, WM_CLOSE
, 0, 0);
2661 if (editor
->bDialogMode
&& editor
->hwndParent
)
2662 SendMessageW(editor
->hwndParent
, WM_NEXTDLGCTL
, shift_is_down
, 0);
2667 handle_EM_EXSETSEL( editor
, 0, -1 );
2673 return paste_special( editor
, 0, NULL
, FALSE
);
2678 return copy_or_cut(editor
, nKey
== 'X');
2696 if (nKey
!= VK_SHIFT
&& nKey
!= VK_CONTROL
&& nKey
&& nKey
!= VK_MENU
)
2697 editor
->nUDArrowX
= -1;
2704 chf
.cbSize
= sizeof(chf
);
2706 ME_GetSelectionCharFormat(editor
, &chf
);
2707 ME_DumpStyleToBuf(&chf
, buf
);
2708 MessageBoxA(NULL
, buf
, "Style dump", MB_OK
);
2712 ME_CheckCharOffsets(editor
);
2719 static LRESULT
ME_Char(ME_TextEditor
*editor
, WPARAM charCode
,
2720 LPARAM flags
, BOOL unicode
)
2724 if (editor
->bMouseCaptured
)
2728 wstr
= (WCHAR
)charCode
;
2731 CHAR charA
= charCode
;
2732 MultiByteToWideChar(CP_ACP
, 0, &charA
, 1, &wstr
, 1);
2735 if (editor
->styleFlags
& ES_READONLY
) {
2736 MessageBeep(MB_ICONERROR
);
2737 return 0; /* FIXME really 0 ? */
2740 if ((unsigned)wstr
>= ' ' || wstr
== '\t')
2742 ME_Cursor cursor
= editor
->pCursors
[0];
2743 ME_DisplayItem
*para
= cursor
.pPara
;
2745 BOOL ctrl_is_down
= GetKeyState(VK_CONTROL
) & 0x8000;
2746 ME_GetSelectionOfs(editor
, &from
, &to
);
2748 /* v4.1 allows tabs to be inserted with ctrl key down */
2749 !(ctrl_is_down
&& !editor
->bEmulateVersion10
))
2751 ME_DisplayItem
*para
;
2752 BOOL bSelectedRow
= FALSE
;
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
)
2760 para
= para
->member
.para
.prev_para
;
2761 bSelectedRow
= TRUE
;
2763 if (ME_IsInTable(para
))
2765 ME_TabPressedInTable(editor
, bSelectedRow
);
2766 ME_CommitUndo(editor
);
2769 } else if (!editor
->bEmulateVersion10
) { /* v4.1 */
2770 if (para
->member
.para
.nFlags
& MEPF_ROWEND
) {
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];
2781 } else { /* v1.0 - 3.0 */
2782 if (ME_IsInTable(cursor
.pRun
) &&
2783 cursor
.pRun
->member
.run
.nFlags
& MERF_ENDPARA
&&
2786 /* Text should not be inserted at the end of the table. */
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
))
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
);
2803 ME_UpdateSelectionLinkAttribute(editor
);
2804 ME_UpdateRepaint(editor
, FALSE
);
2809 /* Process the message and calculate the new click count.
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
,
2815 static int clickNum
= 0;
2816 if (msg
< WM_MOUSEFIRST
|| msg
> WM_MOUSELAST
)
2819 if ((msg
== WM_LBUTTONDBLCLK
) ||
2820 (msg
== WM_RBUTTONDBLCLK
) ||
2821 (msg
== WM_MBUTTONDBLCLK
) ||
2822 (msg
== WM_XBUTTONDBLCLK
))
2824 msg
-= (WM_LBUTTONDBLCLK
- WM_LBUTTONDOWN
);
2827 if ((msg
== WM_LBUTTONDOWN
) ||
2828 (msg
== WM_RBUTTONDOWN
) ||
2829 (msg
== WM_MBUTTONDOWN
) ||
2830 (msg
== WM_XBUTTONDOWN
))
2832 static MSG prevClickMsg
;
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))
2855 prevClickMsg
= clickMsg
;
2862 static BOOL
is_link( ME_Run
*run
)
2864 return (run
->style
->fmt
.dwMask
& CFM_LINK
) && (run
->style
->fmt
.dwEffects
& CFE_LINK
);
2867 static BOOL
ME_SetCursor(ME_TextEditor
*editor
)
2873 DWORD messagePos
= GetMessagePos();
2874 pt
.x
= (short)LOWORD(messagePos
);
2875 pt
.y
= (short)HIWORD(messagePos
);
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
))
2884 ITextHost_TxSetCursor(editor
->texthost
,
2885 LoadCursorW(NULL
, (WCHAR
*)IDC_ARROW
), FALSE
);
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
))
2893 ITextHost_TxSetCursor(editor
->texthost
,
2894 LoadCursorW(NULL
, (WCHAR
*)IDC_ARROW
), FALSE
);
2898 ITextHost_TxScreenToClient(editor
->texthost
, &pt
);
2900 if (editor
->nSelectionType
== stLine
&& editor
->bMouseCaptured
) {
2901 ITextHost_TxSetCursor(editor
->texthost
, hLeft
, FALSE
);
2904 if (!editor
->bEmulateVersion10
/* v4.1 */ &&
2905 pt
.y
< editor
->rcFormat
.top
&&
2906 pt
.x
< editor
->rcFormat
.left
)
2908 ITextHost_TxSetCursor(editor
->texthost
, hLeft
, FALSE
);
2911 if (pt
.y
< editor
->rcFormat
.top
|| pt
.y
> editor
->rcFormat
.bottom
)
2913 if (editor
->bEmulateVersion10
) /* v1.0 - 3.0 */
2914 ITextHost_TxSetCursor(editor
->texthost
,
2915 LoadCursorW(NULL
, (WCHAR
*)IDC_ARROW
), FALSE
);
2917 ITextHost_TxSetCursor(editor
->texthost
,
2918 LoadCursorW(NULL
, (WCHAR
*)IDC_IBEAM
), TRUE
);
2921 if (pt
.x
< editor
->rcFormat
.left
)
2923 ITextHost_TxSetCursor(editor
->texthost
, hLeft
, FALSE
);
2926 ME_CharFromPos(editor
, pt
.x
, pt
.y
, &cursor
, &isExact
);
2931 run
= &cursor
.pRun
->member
.run
;
2934 ITextHost_TxSetCursor(editor
->texthost
,
2935 LoadCursorW(NULL
, (WCHAR
*)IDC_HAND
),
2940 if (ME_IsSelection(editor
))
2942 int selStart
, selEnd
;
2943 int offset
= ME_GetCursorOfs(&cursor
);
2945 ME_GetSelectionOfs(editor
, &selStart
, &selEnd
);
2946 if (selStart
<= offset
&& selEnd
>= offset
) {
2947 ITextHost_TxSetCursor(editor
->texthost
,
2948 LoadCursorW(NULL
, (WCHAR
*)IDC_ARROW
),
2954 ITextHost_TxSetCursor(editor
->texthost
,
2955 LoadCursorW(NULL
, (WCHAR
*)IDC_IBEAM
), TRUE
);
2959 static void ME_SetDefaultFormatRect(ME_TextEditor
*editor
)
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;
2967 static LONG
ME_GetSelectionType(ME_TextEditor
*editor
)
2969 LONG sel_type
= SEL_EMPTY
;
2972 ME_GetSelectionOfs(editor
, &start
, &end
);
2974 sel_type
= SEL_EMPTY
;
2977 LONG object_count
= 0, character_count
= 0;
2980 for (i
= 0; i
< end
- start
; i
++)
2984 ME_CursorFromCharOfs(editor
, start
+ i
, &cursor
);
2985 if (cursor
.pRun
->member
.run
.reobj
)
2989 if (character_count
>= 2 && object_count
>= 2)
2990 return (SEL_TEXT
| SEL_MULTICHAR
| SEL_OBJECT
| SEL_MULTIOBJECT
);
2992 if (character_count
)
2994 sel_type
|= SEL_TEXT
;
2995 if (character_count
>= 2)
2996 sel_type
|= SEL_MULTICHAR
;
3000 sel_type
|= SEL_OBJECT
;
3001 if (object_count
>= 2)
3002 sel_type
|= SEL_MULTIOBJECT
;
3008 static BOOL
ME_ShowContextMenu(ME_TextEditor
*editor
, int x
, int y
)
3014 if(!editor
->lpOleCallback
|| !editor
->hWnd
)
3016 ME_GetSelectionOfs(editor
, &selrange
.cpMin
, &selrange
.cpMax
);
3017 seltype
= ME_GetSelectionType(editor
);
3018 if(SUCCEEDED(IRichEditOleCallback_GetContextMenu(editor
->lpOleCallback
, seltype
, NULL
, &selrange
, &menu
)))
3020 TrackPopupMenu(menu
, TPM_LEFTALIGN
| TPM_RIGHTBUTTON
, x
, y
, 0, editor
->hwndParent
, NULL
);
3026 ME_TextEditor
*ME_MakeEditor(ITextHost
*texthost
, BOOL bEmulateVersion10
)
3028 ME_TextEditor
*ed
= heap_alloc(sizeof(*ed
));
3034 ed
->hwndParent
= NULL
;
3035 ed
->sizeWindow
.cx
= ed
->sizeWindow
.cy
= 0;
3036 ed
->texthost
= texthost
;
3038 ed
->bEmulateVersion10
= bEmulateVersion10
;
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
),
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.
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;
3070 ed
->rgbBackColor
= -1;
3071 ed
->hbrBackground
= GetSysColorBrush(COLOR_WINDOW
);
3072 ed
->bCaretAtEnd
= FALSE
;
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
++)
3095 ed
->pFontCache
[i
].nRefs
= 0;
3096 ed
->pFontCache
[i
].nAge
= 0;
3097 ed
->pFontCache
[i
].hFont
= NULL
;
3100 ME_CheckCharOffsets(ed
);
3101 SetRectEmpty(&ed
->rcFormat
);
3102 ed
->bDefaultFormatRect
= TRUE
;
3103 ITextHost_TxGetSelectionBarWidth(ed
->texthost
, &selbarwidth
);
3105 /* FIXME: Convert selbarwidth from HIMETRIC to pixels */
3106 ed
->selofs
= SELECTIONBAR_WIDTH
;
3107 ed
->styleFlags
|= ES_SELECTIONBAR
;
3111 ed
->nSelectionType
= stPosition
;
3113 ed
->cPasswordMask
= 0;
3114 if (props
& TXTBIT_USEPASSWORD
)
3115 ITextHost_TxGetPasswordChar(texthost
, &ed
->cPasswordMask
);
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;
3123 ed
->bWordWrap
= FALSE
;
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
;
3136 ed
->notified_cr
.cpMin
= ed
->notified_cr
.cpMax
= 0;
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;
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;
3151 ed
->wheel_remain
= 0;
3153 list_init( &ed
->style_list
);
3154 list_init( &ed
->reobj_list
);
3155 OleInitialize(NULL
);
3160 void ME_DestroyEditor(ME_TextEditor
*editor
)
3162 ME_DisplayItem
*pFirst
= editor
->pBuffer
->pFirst
;
3163 ME_DisplayItem
*p
= pFirst
, *pNext
= NULL
;
3164 ME_Style
*s
, *cursor2
;
3167 ME_ClearTempStyle(editor
);
3168 ME_EmptyUndoStack(editor
);
3171 ME_DestroyDisplayItem(p
);
3175 LIST_FOR_EACH_ENTRY_SAFE( s
, cursor2
, &editor
->style_list
, ME_Style
, entry
)
3176 ME_DestroyStyle( s
);
3178 ME_ReleaseStyle(editor
->pBuffer
->pDefaultStyle
);
3179 for (i
=0; i
<HFONT_CACHE_SIZE
; i
++)
3181 if (editor
->pFontCache
[i
].hFont
)
3182 DeleteObject(editor
->pFontCache
[i
].hFont
);
3184 if (editor
->rgbBackColor
!= -1)
3185 DeleteObject(editor
->hbrBackground
);
3186 if(editor
->lpOleCallback
)
3187 IRichEditOleCallback_Release(editor
->lpOleCallback
);
3188 ITextHost_Release(editor
->texthost
);
3191 IRichEditOle_Release(editor
->reOle
);
3192 editor
->reOle
= NULL
;
3196 heap_free(editor
->pBuffer
);
3197 heap_free(editor
->pCursors
);
3201 BOOL WINAPI
DllMain(HINSTANCE hinstDLL
, DWORD fdwReason
, LPVOID lpvReserved
)
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
));
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);
3225 HeapDestroy (me_heap
);
3232 static inline int get_default_line_height( ME_TextEditor
*editor
)
3236 if (editor
->pBuffer
&& editor
->pBuffer
->pDefaultStyle
)
3237 height
= editor
->pBuffer
->pDefaultStyle
->tm
.tmHeight
;
3238 if (height
<= 0) height
= 24;
3243 static inline int calc_wheel_change( int *remain
, int amount_per_click
)
3245 int change
= amount_per_click
* (float)*remain
/ WHEEL_DELTA
;
3246 *remain
-= WHEEL_DELTA
* change
/ amount_per_click
;
3250 static const char * const edit_messages
[] = {
3279 "EM_SETPASSWORDCHAR",
3280 "EM_EMPTYUNDOBUFFER",
3281 "EM_GETFIRSTVISIBLELINE",
3283 "EM_SETWORDBREAKPROC",
3284 "EM_GETWORDBREAKPROC",
3285 "EM_GETPASSWORDCHAR",
3295 static const char * const richedit_messages
[] = {
3300 "EM_EXLINEFROMCHAR",
3306 "EM_GETOLEINTERFACE",
3316 "EM_SETOLECALLBACK",
3318 "EM_SETTARGETDEVICE",
3326 "EM_GETWORDBREAKPROCEX",
3327 "EM_SETWORDBREAKPROCEX",
3329 "EM_UNKNOWN_USER_83",
3334 "EM_STOPGROUPTYPING",
3338 "EM_GETAUTOURLDETECT",
3341 "EM_GETTEXTLENGTHEX",
3344 "EM_UNKNOWN_USER_98",
3345 "EM_UNKNOWN_USER_99",
3346 "EM_SETPUNCTUATION",
3347 "EM_GETPUNCTUATION",
3348 "EM_SETWORDWRAPMODE",
3349 "EM_GETWORDWRAPMODE",
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",
3372 "EM_SETIMEMODEBIAS",
3377 get_msg_name(UINT msg
)
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
];
3386 static void ME_LinkNotify(ME_TextEditor
*editor
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
3390 ME_Cursor cursor
; /* The start of the clicked text. */
3393 x
= (short)LOWORD(lParam
);
3394 y
= (short)HIWORD(lParam
);
3395 ME_CharFromPos(editor
, x
, y
, &cursor
, &isExact
);
3396 if (!isExact
) return;
3398 if (is_link( &cursor
.pRun
->member
.run
))
3399 { /* The clicked run has CFE_LINK set */
3402 info
.nmhdr
.hwndFrom
= NULL
;
3403 info
.nmhdr
.idFrom
= 0;
3404 info
.nmhdr
.code
= EN_LINK
;
3406 info
.wParam
= wParam
;
3407 info
.lParam
= lParam
;
3410 /* find the first contiguous run with CFE_LINK set */
3411 info
.chrg
.cpMin
= ME_GetCursorOfs(&cursor
);
3413 while (ME_PrevRun( NULL
, &di
, FALSE
) && is_link( &di
->member
.run
))
3414 info
.chrg
.cpMin
-= di
->member
.run
.len
;
3416 /* find the last contiguous run with CFE_LINK set */
3417 info
.chrg
.cpMax
= ME_GetCursorOfs(&cursor
) + cursor
.pRun
->member
.run
.len
;
3419 while (ME_NextRun( NULL
, &di
, FALSE
) && is_link( &di
->member
.run
))
3420 info
.chrg
.cpMax
+= di
->member
.run
.len
;
3422 ITextHost_TxNotify(editor
->texthost
, info
.nmhdr
.code
, &info
);
3426 void ME_ReplaceSel(ME_TextEditor
*editor
, BOOL can_undo
, const WCHAR
*str
, int len
)
3428 int from
, to
, nStartCursor
;
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 */
3438 * FIXME question: does abc\n mean: put abc,
3439 * clear temp style, put \n? (would require a change)
3441 if (len
>0 && str
[len
-1] == '\n')
3442 ME_ClearTempStyle(editor
);
3443 ME_CommitUndo(editor
);
3444 ME_UpdateSelectionLinkAttribute(editor
);
3446 ME_EmptyUndoStack(editor
);
3447 ME_UpdateRepaint(editor
, FALSE
);
3450 static void ME_SetText(ME_TextEditor
*editor
, void *text
, BOOL unicode
)
3452 LONG codepage
= unicode
? CP_UNICODE
: CP_ACP
;
3455 LPWSTR wszText
= ME_ToUnicode(codepage
, text
, &textLen
);
3456 ME_InsertTextFromCursor(editor
, 0, wszText
, textLen
, editor
->pBuffer
->pDefaultStyle
);
3457 ME_EndToUnicode(codepage
, wszText
);
3460 static LRESULT
ME_WmCreate(ME_TextEditor
*editor
, LPARAM lParam
, BOOL unicode
)
3462 CREATESTRUCTW
*createW
= (CREATESTRUCTW
*)lParam
;
3463 CREATESTRUCTA
*createA
= (CREATESTRUCTA
*)lParam
;
3468 text
= unicode
? (void*)createW
->lpszName
: (void*)createA
->lpszName
;
3470 ME_SetDefaultFormatRect(editor
);
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
);
3476 if (~editor
->styleFlags
& ES_DISABLENOSCROLL
|| editor
->styleFlags
& WS_HSCROLL
)
3477 ITextHost_TxSetScrollRange(editor
->texthost
, SB_HORZ
, 0, max
, TRUE
);
3479 if (editor
->styleFlags
& ES_DISABLENOSCROLL
)
3481 if (editor
->styleFlags
& WS_VSCROLL
)
3483 ITextHost_TxEnableScrollBar(editor
->texthost
, SB_VERT
, ESB_DISABLE_BOTH
);
3484 ITextHost_TxShowScrollBar(editor
->texthost
, SB_VERT
, TRUE
);
3486 if (editor
->styleFlags
& WS_HSCROLL
)
3488 ITextHost_TxEnableScrollBar(editor
->texthost
, SB_HORZ
, ESB_DISABLE_BOTH
);
3489 ITextHost_TxShowScrollBar(editor
->texthost
, SB_HORZ
, TRUE
);
3495 ME_SetText(editor
, text
, unicode
);
3496 ME_SetCursorToStart(editor
, &editor
->pCursors
[0]);
3497 ME_SetCursorToStart(editor
, &editor
->pCursors
[1]);
3500 ME_CommitUndo(editor
);
3501 ME_WrapMarkedParagraphs(editor
);
3502 ME_MoveCaret(editor
);
3507 #define UNSUPPORTED_MSG(e) \
3509 FIXME(#e ": stub\n"); \
3510 *phresult = S_FALSE; \
3513 /* Handle messages for windowless and windowed richedit controls.
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
)
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
)
3548 /* Messages specific to Richedit controls */
3551 return ME_StreamIn(editor
, wParam
, (EDITSTREAM
*)lParam
, TRUE
);
3553 return ME_StreamOut(editor
, wParam
, (EDITSTREAM
*)lParam
);
3556 UINT code
= DLGC_WANTCHARS
|DLGC_WANTTAB
|DLGC_WANTARROWS
;
3559 editor
->bDialogMode
= TRUE
;
3560 if (editor
->styleFlags
& ES_MULTILINE
)
3561 code
|= DLGC_WANTMESSAGE
;
3562 if (!(editor
->styleFlags
& ES_SAVESEL
))
3563 code
|= DLGC_HASSETSEL
;
3566 case EM_EMPTYUNDOBUFFER
:
3567 ME_EmptyUndoStack(editor
);
3571 /* Note: wParam/lParam can be NULL */
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)
3578 return MAKELONG(*pfrom
,*pto
);
3582 CHARRANGE
*pRange
= (CHARRANGE
*)lParam
;
3583 ME_GetSelectionOfs(editor
, &pRange
->cpMin
, &pRange
->cpMax
);
3584 TRACE("EM_EXGETSEL = (%d,%d)\n", pRange
->cpMin
, pRange
->cpMax
);
3587 case EM_SETUNDOLIMIT
:
3589 if ((int)wParam
< 0)
3590 editor
->nUndoLimit
= STACK_SIZE_DEFAULT
;
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
;
3599 return !list_empty( &editor
->undo_stack
);
3601 return !list_empty( &editor
->redo_stack
);
3602 case WM_UNDO
: /* FIXME: actually not the same */
3604 return ME_Undo(editor
);
3606 return ME_Redo(editor
);
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
;
3616 case EM_SETFONTSIZE
:
3619 LONG tmp_size
, size
;
3620 BOOL is_increase
= ((LONG
)wParam
> 0);
3622 if (editor
->mode
& TM_PLAINTEXT
)
3625 cf
.cbSize
= sizeof(cf
);
3626 cf
.dwMask
= CFM_SIZE
;
3627 ME_GetSelectionCharFormat(editor
, &cf
);
3628 tmp_size
= (cf
.yHeight
/ 20) + wParam
;
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)
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
);
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
;
3684 changedSettings
= oldSettings
^ settings
;
3686 if (changedSettings
) {
3687 editor
->styleFlags
= (editor
->styleFlags
& ~mask
) | (settings
& mask
);
3689 if (changedSettings
& ECO_SELECTIONBAR
)
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
;
3697 editor
->rcFormat
.left
-= editor
->selofs
;
3700 ME_RewrapRepaint(editor
);
3703 if ((changedSettings
& settings
& ES_NOHIDESEL
) && !editor
->bHaveFocus
)
3704 ME_InvalidateSelection( editor
);
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");
3722 return handle_EM_EXSETSEL( editor
, wParam
, lParam
);
3724 case EM_SETSCROLLPOS
:
3726 POINT
*point
= (POINT
*)lParam
;
3727 ME_ScrollAbs(editor
, point
->x
, point
->y
);
3730 case EM_AUTOURLDETECT
:
3732 if (wParam
==1 || wParam
==0)
3734 editor
->AutoURLDetect_bEnable
= (BOOL
)wParam
;
3737 return E_INVALIDARG
;
3739 case EM_GETAUTOURLDETECT
:
3741 return editor
->AutoURLDetect_bEnable
;
3745 CHARRANGE range
= *(CHARRANGE
*)lParam
;
3747 return handle_EM_EXSETSEL( editor
, range
.cpMin
, range
.cpMax
);
3749 case EM_SHOWSCROLLBAR
:
3762 flags
= WS_HSCROLL
|WS_VSCROLL
;
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
);
3777 editor
->styleFlags
&= ~flags
;
3778 ITextHost_TxShowScrollBar(editor
->texthost
, wParam
, FALSE
);
3785 SETTEXTEX
*pStruct
= (SETTEXTEX
*)wParam
;
3788 BOOL bRtf
, bUnicode
, bSelection
, bUTF8
;
3789 int oldModify
= editor
->nModifyStep
;
3790 static const char utf8_bom
[] = {0xef, 0xbb, 0xbf};
3792 if (!pStruct
) return 0;
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)));
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
);
3805 bSelection
= (pStruct
->flags
& ST_SELECTION
) != 0;
3807 int nStartCursor
= ME_GetSelectionOfs(editor
, &from
, &to
);
3808 style
= ME_GetSelectionInsertStyle(editor
);
3809 ME_InternalDeleteText(editor
, &editor
->pCursors
[nStartCursor
], to
- from
, FALSE
);
3812 ME_SetCursorToStart(editor
, &start
);
3813 ME_InternalDeleteText(editor
, &start
, ME_GetTextLength(editor
), FALSE
);
3814 style
= editor
->pBuffer
->pDefaultStyle
;
3818 ME_StreamInRTFString(editor
, bSelection
, (char *)lParam
);
3820 /* FIXME: The length returned doesn't include the rtf control
3821 * characters, only the actual text. */
3822 len
= lParam
? strlen((char *)lParam
) : 0;
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
);
3830 wszText
= ME_ToUnicode(pStruct
->codepage
, (void *)lParam
, &len
);
3831 ME_InsertTextFromCursor(editor
, 0, wszText
, len
, style
);
3832 ME_EndToUnicode(pStruct
->codepage
, wszText
);
3837 ME_ReleaseStyle(style
);
3838 ME_UpdateSelectionLinkAttribute(editor
);
3842 ME_SetCursorToStart(editor
, &cursor
);
3843 ME_UpdateLinkAttribute(editor
, &cursor
, INT_MAX
);
3845 ME_CommitUndo(editor
);
3846 if (!(pStruct
->flags
& ST_KEEPUNDO
))
3848 editor
->nModifyStep
= oldModify
;
3849 ME_EmptyUndoStack(editor
);
3851 ME_UpdateRepaint(editor
, FALSE
);
3854 case EM_SELECTIONTYPE
:
3855 return ME_GetSelectionType(editor
);
3856 case EM_SETBKGNDCOLOR
:
3859 if (editor
->rgbBackColor
!= -1) {
3860 DeleteObject(editor
->hbrBackground
);
3861 lColor
= editor
->rgbBackColor
;
3863 else lColor
= ITextHost_TxGetSysColor(editor
->texthost
, COLOR_WINDOW
);
3867 editor
->rgbBackColor
= -1;
3868 editor
->hbrBackground
= GetSysColorBrush(COLOR_WINDOW
);
3872 editor
->rgbBackColor
= lParam
;
3873 editor
->hbrBackground
= CreateSolidBrush(editor
->rgbBackColor
);
3875 ITextHost_TxInvalidateRect(editor
->texthost
, NULL
, TRUE
);
3879 return editor
->nModifyStep
== 0 ? 0 : -1;
3883 editor
->nModifyStep
= 1;
3885 editor
->nModifyStep
= 0;
3889 case EM_SETREADONLY
:
3892 editor
->styleFlags
|= ES_READONLY
;
3894 editor
->styleFlags
&= ~ES_READONLY
;
3897 case EM_SETEVENTMASK
:
3899 DWORD nOldMask
= editor
->nEventMask
;
3901 editor
->nEventMask
= lParam
;
3904 case EM_GETEVENTMASK
:
3905 return editor
->nEventMask
;
3906 case EM_SETCHARFORMAT
:
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
);
3917 ME_SetCursorToStart(editor
, &start
);
3918 ME_SetCharFormat(editor
, &start
, NULL
, p
);
3919 editor
->nModifyStep
= 1;
3921 } else if (wParam
& SCF_SELECTION
) {
3922 if (editor
->mode
& TM_PLAINTEXT
)
3924 if (wParam
& SCF_WORD
) {
3926 ME_Cursor end
= editor
->pCursors
[0];
3927 ME_MoveCursorWords(editor
, &end
, +1);
3929 ME_MoveCursorWords(editor
, &start
, -1);
3930 ME_SetCharFormat(editor
, &start
, &end
, p
);
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
);
3938 ME_CommitUndo(editor
);
3941 ME_WrapMarkedParagraphs(editor
);
3942 ME_UpdateScrollBar(editor
);
3947 case EM_GETCHARFORMAT
:
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
))
3955 tmp
.cbSize
= sizeof(tmp
);
3957 ME_GetDefaultCharFormat(editor
, &tmp
);
3959 ME_GetSelectionCharFormat(editor
, &tmp
);
3960 ME_CopyToCFAny(dst
, &tmp
);
3963 case EM_SETPARAFORMAT
:
3965 BOOL result
= ME_SetSelectionParaFormat(editor
, (PARAFORMAT2
*)lParam
);
3966 ME_WrapMarkedParagraphs(editor
);
3967 ME_UpdateScrollBar(editor
);
3969 ME_CommitUndo(editor
);
3972 case EM_GETPARAFORMAT
:
3973 ME_GetSelectionParaFormat(editor
, (PARAFORMAT2
*)lParam
);
3974 return ((PARAFORMAT2
*)lParam
)->dwMask
;
3975 case EM_GETFIRSTVISIBLELINE
:
3977 ME_DisplayItem
*p
= editor
->pBuffer
->pFirst
;
3978 int y
= editor
->vert_si
.nPos
;
3983 p
= ME_FindItemFwd(p
, diStartRowOrParagraphOrEnd
);
3984 if (p
->type
== diTextEnd
)
3986 if (p
->type
== diParagraph
) {
3987 ypara
= p
->member
.para
.pt
.y
;
3990 ystart
= ypara
+ p
->member
.row
.pt
.y
;
3991 yend
= ystart
+ p
->member
.row
.nHeight
;
3999 case EM_HIDESELECTION
:
4001 editor
->bHideSelection
= (wParam
!= 0);
4002 ME_InvalidateSelection(editor
);
4007 if (!(editor
->styleFlags
& ES_MULTILINE
))
4009 ME_ScrollDown( editor
, lParam
* get_default_line_height( editor
) );
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
);
4024 LONG codepage
= unicode
? CP_UNICODE
: CP_ACP
;
4025 LPWSTR wszText
= ME_ToUnicode(codepage
, (void *)lParam
, &len
);
4027 TRACE("EM_REPLACESEL - %s\n", debugstr_w(wszText
));
4029 ME_ReplaceSel(editor
, !!wParam
, wszText
, len
);
4030 ME_EndToUnicode(codepage
, wszText
);
4033 case EM_SCROLLCARET
:
4034 ME_EnsureVisible(editor
, &editor
->pCursors
[0]);
4041 BOOL bRepaint
= LOWORD(lParam
);
4044 wParam
= (WPARAM
)GetStockObject(SYSTEM_FONT
);
4046 if (!GetObjectW((HGDIOBJ
)wParam
, sizeof(LOGFONTW
), &lf
))
4049 hDC
= ITextHost_TxGetDC(editor
->texthost
);
4050 ME_CharFormatFromLogFont(hDC
, &lf
, &fmt
);
4051 ITextHost_TxReleaseDC(editor
->texthost
, hDC
);
4052 if (editor
->mode
& TM_RICHTEXT
) {
4054 ME_SetCursorToStart(editor
, &start
);
4055 ME_SetCharFormat(editor
, &start
, NULL
, &fmt
);
4057 ME_SetDefaultCharFormat(editor
, &fmt
);
4059 ME_CommitUndo(editor
);
4060 ME_MarkAllForWrapping(editor
);
4061 ME_WrapMarkedParagraphs(editor
);
4062 ME_UpdateScrollBar(editor
);
4070 ME_SetCursorToStart(editor
, &cursor
);
4071 ME_InternalDeleteText(editor
, &cursor
, ME_GetTextLength(editor
), FALSE
);
4074 TRACE("WM_SETTEXT lParam==%lx\n",lParam
);
4075 if (!strncmp((char *)lParam
, "{\\rtf", 5) ||
4076 !strncmp((char *)lParam
, "{\\urtf", 6))
4078 /* Undocumented: WM_SETTEXT supports RTF text */
4079 ME_StreamInRTFString(editor
, 0, (char *)lParam
);
4082 ME_SetText(editor
, (void*)lParam
, unicode
);
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
);
4096 return paste_special( editor
, 0, NULL
, TRUE
);
4098 case WM_MBUTTONDOWN
:
4102 case EM_PASTESPECIAL
:
4103 paste_special( editor
, wParam
, (REPASTESPECIAL
*)lParam
, FALSE
);
4107 copy_or_cut(editor
, msg
== WM_CUT
);
4109 case WM_GETTEXTLENGTH
:
4111 GETTEXTLENGTHEX how
;
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
);
4118 case EM_GETTEXTLENGTHEX
:
4119 return ME_GetTextLengthEx(editor
, (GETTEXTLENGTHEX
*)wParam
);
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
);
4131 return ME_GetTextEx(editor
, (GETTEXTEX
*)wParam
, lParam
);
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
);
4139 case EM_GETSCROLLPOS
:
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
);
4151 case EM_GETTEXTRANGE
:
4153 TEXTRANGEW
*rng
= (TEXTRANGEW
*)lParam
;
4155 int nStart
= rng
->chrg
.cpMin
;
4156 int nEnd
= rng
->chrg
.cpMax
;
4157 int textlength
= ME_GetTextLength(editor
);
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
)
4164 if (nStart
>= nEnd
) return 0;
4166 ME_CursorFromCharOfs(editor
, nStart
, &start
);
4167 return ME_GetTextRange(editor
, rng
->lpstrText
, &start
, nEnd
- nStart
, unicode
);
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
;
4177 TRACE("EM_GETLINE: row=%d, nMaxChars=%d (%s)\n", (int) wParam
, nMaxChars
,
4178 unicode
? "Unicode" : "Ansi");
4180 run
= ME_FindRowWithNumber(editor
, wParam
);
4184 while (nCharsLeft
&& (run
= ME_FindItemFwd(run
, diRunOrStartRow
))
4185 && run
->type
== diRun
)
4187 WCHAR
*str
= get_text( &run
->member
.run
, 0 );
4190 nCopy
= min(nCharsLeft
, run
->member
.run
.len
);
4193 memcpy(dest
, str
, nCopy
* sizeof(WCHAR
));
4195 nCopy
= WideCharToMultiByte(CP_ACP
, 0, str
, nCopy
, dest
,
4196 nCharsLeft
, NULL
, NULL
);
4197 dest
+= nCopy
* (unicode
? sizeof(WCHAR
) : 1);
4198 nCharsLeft
-= nCopy
;
4201 /* append line termination, space allowing */
4205 *((WCHAR
*)dest
) = '\0';
4212 TRACE("EM_GETLINE: got %u characters\n", nMaxChars
- nCharsLeft
);
4213 return nMaxChars
- nCharsLeft
- (wroteNull
? 1 : 0);
4215 case EM_GETLINECOUNT
:
4217 ME_DisplayItem
*item
= editor
->pBuffer
->pFirst
->next
;
4220 ME_DisplayItem
*prev_para
= NULL
, *last_para
= NULL
;
4222 while (item
!= editor
->pBuffer
->pLast
)
4224 assert(item
->type
== diParagraph
);
4225 prev_para
= ME_FindItemBack(item
, diRun
);
4227 assert(prev_para
->member
.run
.nFlags
& MERF_ENDPARA
);
4229 nRows
+= item
->member
.para
.nRows
;
4230 item
= item
->member
.para
.next_para
;
4232 last_para
= ME_FindItemBack(item
, diRun
);
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')
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. */
4246 TRACE("EM_GETLINECOUNT: nRows==%d\n", nRows
);
4247 return max(1, nRows
);
4249 case EM_LINEFROMCHAR
:
4252 return ME_RowNumberFromCharOfs(editor
, ME_GetCursorOfs(&editor
->pCursors
[1]));
4254 return ME_RowNumberFromCharOfs(editor
, wParam
);
4256 case EM_EXLINEFROMCHAR
:
4259 return ME_RowNumberFromCharOfs(editor
, ME_GetCursorOfs(&editor
->pCursors
[1]));
4261 return ME_RowNumberFromCharOfs(editor
, lParam
);
4265 ME_DisplayItem
*item
, *para
;
4269 item
= ME_FindItemBack(editor
->pCursors
[0].pRun
, diStartRow
);
4271 item
= ME_FindRowWithNumber(editor
, wParam
);
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
);
4282 ME_DisplayItem
*item
, *item_end
;
4283 int nChars
= 0, nThisLineOfs
= 0, nNextLineOfs
= 0;
4284 ME_DisplayItem
*para
, *run
;
4286 if (wParam
> ME_GetTextLength(editor
))
4290 FIXME("EM_LINELENGTH: returning number of unselected characters on lines with selection unsupported.\n");
4293 ME_RunOfsFromCharOfs(editor
, wParam
, ¶
, &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);
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
;
4304 nChars
= nNextLineOfs
- nThisLineOfs
;
4305 TRACE("EM_LINELENGTH(%ld)==%d\n",wParam
, nChars
);
4308 case EM_EXLIMITTEXT
:
4310 if ((int)lParam
< 0)
4313 editor
->nTextLimit
= 65536;
4315 editor
->nTextLimit
= (int) lParam
;
4321 editor
->nTextLimit
= 65536;
4323 editor
->nTextLimit
= (int) wParam
;
4326 case EM_GETLIMITTEXT
:
4328 return editor
->nTextLimit
;
4334 FINDTEXTA
*ft
= (FINDTEXTA
*)lParam
;
4335 int nChars
= MultiByteToWideChar(CP_ACP
, 0, ft
->lpstrText
, -1, NULL
, 0);
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
);
4343 FINDTEXTW
*ft
= (FINDTEXTW
*)lParam
;
4344 r
= ME_FindText(editor
, wParam
, &ft
->chrg
, ft
->lpstrText
, NULL
);
4352 FINDTEXTEXA
*ex
= (FINDTEXTEXA
*)lParam
;
4353 int nChars
= MultiByteToWideChar(CP_ACP
, 0, ex
->lpstrText
, -1, NULL
, 0);
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
);
4361 FINDTEXTEXW
*ex
= (FINDTEXTEXW
*)lParam
;
4362 r
= ME_FindText(editor
, wParam
, &ex
->chrg
, ex
->lpstrText
, &ex
->chrgText
);
4368 FINDTEXTW
*ft
= (FINDTEXTW
*)lParam
;
4369 return ME_FindText(editor
, wParam
, &ft
->chrg
, ft
->lpstrText
, NULL
);
4371 case EM_FINDTEXTEXW
:
4373 FINDTEXTEXW
*ex
= (FINDTEXTEXW
*)lParam
;
4374 return ME_FindText(editor
, wParam
, &ex
->chrg
, ex
->lpstrText
, &ex
->chrgText
);
4377 if (!wParam
|| !lParam
)
4379 *(int *)wParam
= editor
->nZoomNumerator
;
4380 *(int *)lParam
= editor
->nZoomDenominator
;
4383 return ME_SetZoom(editor
, wParam
, lParam
);
4384 case EM_CHARFROMPOS
:
4387 if (ME_CharFromPos(editor
, ((POINTL
*)lParam
)->x
, ((POINTL
*)lParam
)->y
,
4389 return ME_GetCursorOfs(&cursor
);
4393 case EM_POSFROMCHAR
:
4395 ME_DisplayItem
*pPara
, *pRun
;
4396 int nCharOfs
, nOffset
, nLength
;
4400 /* detect which API version we're dealing with */
4401 if (wParam
>= 0x40000)
4403 nLength
= ME_GetTextLength(editor
);
4404 nCharOfs
= min(nCharOfs
, nLength
);
4405 nCharOfs
= max(nCharOfs
, 0);
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
;
4414 pt
.x
-= editor
->horz_si
.nPos
;
4415 pt
.y
-= editor
->vert_si
.nPos
;
4417 if (wParam
>= 0x40000) {
4418 *(POINTL
*)wParam
= pt
;
4420 return (wParam
>= 0x40000) ? 0 : MAKELONG( pt
.x
, pt
.y
);
4423 return ME_WmCreate(editor
, lParam
, unicode
);
4425 ME_DestroyEditor(editor
);
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
);
4435 case WM_LBUTTONDBLCLK
:
4436 case WM_LBUTTONDOWN
:
4438 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
4439 if ((editor
->nEventMask
& ENM_MOUSEEVENTS
) &&
4440 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
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
;
4452 if ((editor
->nEventMask
& ENM_MOUSEEVENTS
) &&
4453 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4455 if (editor
->bMouseCaptured
)
4456 ME_MouseMove(editor
, (short)LOWORD(lParam
), (short)HIWORD(lParam
));
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
);
4464 if (editor
->bMouseCaptured
) {
4465 ITextHost_TxSetCapture(editor
->texthost
, FALSE
);
4466 editor
->bMouseCaptured
= FALSE
;
4468 if (editor
->nSelectionType
== stDocument
)
4469 editor
->nSelectionType
= stPosition
;
4470 if ((editor
->nEventMask
& ENM_MOUSEEVENTS
) &&
4471 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4475 ME_SetCursor(editor
);
4476 ME_LinkNotify(editor
, msg
, wParam
, lParam
);
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
))
4486 ME_LinkNotify(editor
, msg
, wParam
, lParam
);
4488 case WM_CONTEXTMENU
:
4489 if (!ME_ShowContextMenu(editor
, (short)LOWORD(lParam
), (short)HIWORD(lParam
)))
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
);
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
);
4509 TRACE("editor wnd command = %d\n", LOWORD(wParam
));
4512 if ((editor
->nEventMask
& ENM_KEYEVENTS
) &&
4513 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4517 if ((editor
->nEventMask
& ENM_KEYEVENTS
) &&
4518 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4520 if (ME_KeyDown(editor
, LOWORD(wParam
)))
4524 if ((editor
->nEventMask
& ENM_KEYEVENTS
) &&
4525 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4527 return ME_Char(editor
, wParam
, lParam
, unicode
);
4531 if(wParam
== UNICODE_NOCHAR
) return TRUE
;
4532 if(wParam
<= 0x000fffff)
4534 if(wParam
> 0xffff) /* convert to surrogates */
4537 ME_Char(editor
, (wParam
>> 10) + 0xd800, 0, TRUE
);
4538 ME_Char(editor
, (wParam
& 0x03ff) + 0xdc00, 0, TRUE
);
4540 ME_Char(editor
, wParam
, 0, TRUE
);
4546 case EM_STOPGROUPTYPING
:
4547 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
4551 const int scrollUnit
= 7;
4553 switch(LOWORD(wParam
))
4556 ME_ScrollAbs(editor
, 0, 0);
4559 ME_ScrollAbs(editor
,
4560 editor
->horz_si
.nMax
- (int)editor
->horz_si
.nPage
,
4561 editor
->vert_si
.nMax
- (int)editor
->vert_si
.nPage
);
4564 ME_ScrollLeft(editor
, scrollUnit
);
4567 ME_ScrollRight(editor
, scrollUnit
);
4570 ME_ScrollLeft(editor
, editor
->sizeWindow
.cx
);
4573 ME_ScrollRight(editor
, editor
->sizeWindow
.cx
);
4576 case SB_THUMBPOSITION
:
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
);
4587 case EM_SCROLL
: /* fall through */
4591 int lineHeight
= get_default_line_height( editor
);
4593 origNPos
= editor
->vert_si
.nPos
;
4595 switch(LOWORD(wParam
))
4598 ME_ScrollAbs(editor
, 0, 0);
4601 ME_ScrollAbs(editor
,
4602 editor
->horz_si
.nMax
- (int)editor
->horz_si
.nPage
,
4603 editor
->vert_si
.nMax
- (int)editor
->vert_si
.nPage
);
4606 ME_ScrollUp(editor
,lineHeight
);
4609 ME_ScrollDown(editor
,lineHeight
);
4612 ME_ScrollUp(editor
,editor
->sizeWindow
.cy
);
4615 ME_ScrollDown(editor
,editor
->sizeWindow
.cy
);
4618 case SB_THUMBPOSITION
:
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
);
4627 if (msg
== EM_SCROLL
)
4628 return 0x00010000 | (((editor
->vert_si
.nPos
- origNPos
)/lineHeight
) & 0xffff);
4636 if ((editor
->nEventMask
& ENM_MOUSEEVENTS
) &&
4637 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4640 ctrl_is_down
= GetKeyState(VK_CONTROL
) & 0x8000;
4642 delta
= GET_WHEEL_DELTA_WPARAM(wParam
);
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
;
4649 editor
->wheel_remain
= delta
;
4651 if (editor
->wheel_remain
)
4655 if (!editor
->nZoomNumerator
|| !editor
->nZoomDenominator
)
4659 numerator
= editor
->nZoomNumerator
* 100 / editor
->nZoomDenominator
;
4661 numerator
+= calc_wheel_change( &editor
->wheel_remain
, 10 );
4662 if (numerator
>= 10 && numerator
<= 500)
4663 ME_SetZoom(editor
, numerator
, 100);
4668 SystemParametersInfoW( SPI_GETWHEELSCROLLLINES
, 0, &max_lines
, 0 );
4670 lines
= calc_wheel_change( &editor
->wheel_remain
, (int)max_lines
);
4672 ME_ScrollDown( editor
, -lines
* get_default_line_height( editor
) );
4679 *((RECT
*)lParam
) = editor
->rcFormat
;
4680 if (editor
->bDefaultFormatRect
)
4681 ((RECT
*)lParam
)->left
-= editor
->selofs
;
4691 RECT
*rc
= (RECT
*)lParam
;
4693 border
= editor
->exStyleFlags
& WS_EX_CLIENTEDGE
? 1 : 0;
4694 ITextHost_TxGetClientRect(editor
->texthost
, &clientRect
);
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
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
;
4713 editor
->bDefaultFormatRect
= FALSE
;
4717 ME_SetDefaultFormatRect(editor
);
4718 editor
->bDefaultFormatRect
= TRUE
;
4720 ME_MarkAllForWrapping(editor
);
4721 ME_WrapMarkedParagraphs(editor
);
4722 ME_UpdateScrollBar(editor
);
4723 if (msg
!= EM_SETRECTNP
)
4727 case EM_REQUESTRESIZE
:
4728 ME_SendRequestResize(editor
, TRUE
);
4732 case WM_WINDOWPOSCHANGED
:
4735 WINDOWPOS
*winpos
= (WINDOWPOS
*)lParam
;
4737 if (winpos
->flags
& SWP_NOCLIENTSIZE
) goto do_default
;
4738 ITextHost_TxGetClientRect(editor
->texthost
, &clientRect
);
4739 if (editor
->bDefaultFormatRect
) {
4740 ME_SetDefaultFormatRect(editor
);
4742 editor
->rcFormat
.right
+= clientRect
.right
- editor
->prevClientRect
.right
;
4743 editor
->rcFormat
.bottom
+= clientRect
.bottom
- editor
->prevClientRect
.bottom
;
4745 editor
->prevClientRect
= clientRect
;
4746 ME_RewrapRepaint(editor
);
4749 /* IME messages to make richedit controls IME aware */
4750 case WM_IME_SETCONTEXT
:
4751 case WM_IME_CONTROL
:
4753 case WM_IME_COMPOSITIONFULL
:
4755 case WM_IME_STARTCOMPOSITION
:
4757 editor
->imeStartIndex
=ME_GetCursorOfs(&editor
->pCursors
[0]);
4758 ME_DeleteSelection(editor
);
4759 ME_CommitUndo(editor
);
4760 ME_UpdateRepaint(editor
, FALSE
);
4763 case WM_IME_COMPOSITION
:
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
))
4773 LPWSTR lpCompStr
= NULL
;
4775 DWORD dwIndex
= lParam
& GCS_RESULTSTR
;
4777 dwIndex
= GCS_COMPSTR
;
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
);
4786 if (dwIndex
== GCS_COMPSTR
)
4787 ME_SetSelection(editor
,editor
->imeStartIndex
,
4788 editor
->imeStartIndex
+ dwBufLen
/sizeof(WCHAR
));
4790 ME_ReleaseStyle(style
);
4791 ME_CommitUndo(editor
);
4792 ME_UpdateRepaint(editor
, FALSE
);
4795 case WM_IME_ENDCOMPOSITION
:
4797 ME_DeleteSelection(editor
);
4798 editor
->imeStartIndex
=-1;
4801 case EM_GETOLEINTERFACE
:
4804 if (!CreateIRichEditOle(NULL
, editor
, (LPVOID
*)&editor
->reOle
))
4806 *(LPVOID
*)lParam
= editor
->reOle
;
4807 IRichEditOle_AddRef(editor
->reOle
);
4810 case EM_GETPASSWORDCHAR
:
4812 return editor
->cPasswordMask
;
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
);
4821 case EM_GETWORDBREAKPROC
:
4822 return (LRESULT
)editor
->pfnWordBreak
;
4823 case EM_SETWORDBREAKPROC
:
4825 EDITWORDBREAKPROCW pfnOld
= editor
->pfnWordBreak
;
4827 editor
->pfnWordBreak
= (EDITWORDBREAKPROCW
)lParam
;
4828 return (LRESULT
)pfnOld
;
4830 case EM_GETTEXTMODE
:
4831 return editor
->mode
;
4832 case EM_SETTEXTMODE
:
4837 if (ME_GetTextLength(editor
) ||
4838 !list_empty( &editor
->undo_stack
) || !list_empty( &editor
->redo_stack
))
4839 return E_UNEXPECTED
;
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
;
4846 if (wParam
& (TM_RICHTEXT
| TM_PLAINTEXT
))
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
;
4863 /* FIXME: Currently no support for undo level and code page options */
4864 editor
->mode
= (editor
->mode
& ~mask
) | changes
;
4867 case EM_SETPASSWORDCHAR
:
4869 editor
->cPasswordMask
= wParam
;
4870 ME_RewrapRepaint(editor
);
4873 case EM_SETTARGETDEVICE
:
4876 BOOL
new = (lParam
== 0 && (editor
->styleFlags
& ES_MULTILINE
));
4877 if (editor
->nAvailWidth
|| editor
->bWordWrap
!= new)
4879 editor
->bWordWrap
= new;
4880 editor
->nAvailWidth
= 0; /* wrap to client area */
4881 ME_RewrapRepaint(editor
);
4884 int width
= max(0, lParam
);
4885 if ((editor
->styleFlags
& ES_MULTILINE
) &&
4886 (!editor
->bWordWrap
|| editor
->nAvailWidth
!= width
))
4888 editor
->nAvailWidth
= width
;
4889 editor
->bWordWrap
= TRUE
;
4890 ME_RewrapRepaint(editor
);
4892 FIXME("EM_SETTARGETDEVICE doesn't use non-NULL target devices\n");
4897 *phresult
= S_FALSE
;
4903 static BOOL
create_windowed_editor(HWND hwnd
, CREATESTRUCTW
*create
, BOOL emulate_10
)
4905 ITextHost
*host
= ME_CreateTextHost( hwnd
, create
, emulate_10
);
4906 ME_TextEditor
*editor
;
4908 if (!host
) return FALSE
;
4910 editor
= ME_MakeEditor( host
, emulate_10
);
4913 ITextHost_Release( host
);
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
;
4922 SetWindowLongPtrW( hwnd
, 0, (LONG_PTR
)editor
);
4927 static LRESULT
RichEditWndProc_common(HWND hWnd
, UINT msg
, WPARAM wParam
,
4928 LPARAM lParam
, BOOL unicode
)
4930 ME_TextEditor
*editor
;
4932 LRESULT lresult
= 0;
4934 TRACE("enter hwnd %p msg %04x (%s) %lx %lx, unicode %d\n",
4935 hWnd
, msg
, get_msg_name(msg
), wParam
, lParam
, unicode
);
4937 editor
= (ME_TextEditor
*)GetWindowLongPtrW(hWnd
, 0);
4940 if (msg
== WM_NCCREATE
)
4942 CREATESTRUCTW
*pcs
= (CREATESTRUCTW
*)lParam
;
4944 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd
, pcs
->style
);
4945 return create_windowed_editor( hWnd
, pcs
, FALSE
);
4949 return DefWindowProcW(hWnd
, msg
, wParam
, lParam
);
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
)
4968 rc
.bottom
= editor
->rcFormat
.top
;
4969 FillRect(hDC
, &rc
, editor
->hbrBackground
);
4970 ps
.rcPaint
.top
= editor
->rcFormat
.top
;
4972 if (ps
.rcPaint
.bottom
> editor
->rcFormat
.bottom
) {
4974 rc
.top
= editor
->rcFormat
.bottom
;
4975 FillRect(hDC
, &rc
, editor
->hbrBackground
);
4976 ps
.rcPaint
.bottom
= editor
->rcFormat
.bottom
;
4978 if (ps
.rcPaint
.left
< editor
->rcFormat
.left
) {
4980 rc
.right
= editor
->rcFormat
.left
;
4981 FillRect(hDC
, &rc
, editor
->hbrBackground
);
4982 ps
.rcPaint
.left
= editor
->rcFormat
.left
;
4984 if (ps
.rcPaint
.right
> editor
->rcFormat
.right
) {
4986 rc
.left
= editor
->rcFormat
.right
;
4987 FillRect(hDC
, &rc
, editor
->hbrBackground
);
4988 ps
.rcPaint
.right
= editor
->rcFormat
.right
;
4991 ME_PaintContent(editor
, hDC
, &ps
.rcPaint
);
4992 EndPaint(editor
->hWnd
, &ps
);
4997 HDC hDC
= (HDC
)wParam
;
5000 if (GetUpdateRect(editor
->hWnd
, &rc
, TRUE
))
5001 FillRect(hDC
, &rc
, editor
->hbrBackground
);
5007 const DWORD mask
= ECO_VERTICAL
| ECO_AUTOHSCROLL
| ECO_AUTOVSCROLL
|
5008 ECO_NOHIDESEL
| ECO_READONLY
| ECO_WANTRETURN
|
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
);
5016 case EM_SETREADONLY
:
5019 lresult
= ME_HandleMessage(editor
, msg
, wParam
, lParam
, unicode
, &hresult
);
5020 dwStyle
= GetWindowLongW(hWnd
, GWL_STYLE
);
5021 dwStyle
&= ~ES_READONLY
;
5023 dwStyle
|= ES_READONLY
;
5024 SetWindowLongW(hWnd
, GWL_STYLE
, dwStyle
);
5028 lresult
= ME_HandleMessage(editor
, msg
, wParam
, lParam
, unicode
, &hresult
);
5031 if (hresult
== S_FALSE
)
5032 lresult
= DefWindowProcW(hWnd
, msg
, wParam
, lParam
);
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
);
5040 static LRESULT WINAPI
RichEditWndProcW(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
5042 BOOL unicode
= TRUE
;
5044 /* Under Win9x RichEdit20W returns ANSI strings, see the tests. */
5045 if (msg
== WM_GETTEXT
&& (GetVersion() & 0x80000000))
5048 return RichEditWndProc_common(hWnd
, msg
, wParam
, lParam
, unicode
);
5051 static LRESULT WINAPI
RichEditWndProcA(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
5053 return RichEditWndProc_common(hWnd
, msg
, wParam
, lParam
, FALSE
);
5056 /******************************************************************
5057 * RichEditANSIWndProc (RICHED20.10)
5059 LRESULT WINAPI
RichEditANSIWndProc(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
5061 return RichEditWndProcA(hWnd
, msg
, wParam
, lParam
);
5064 /******************************************************************
5065 * RichEdit10ANSIWndProc (RICHED20.9)
5067 LRESULT WINAPI
RichEdit10ANSIWndProc(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
5069 if (msg
== WM_NCCREATE
&& !GetWindowLongPtrW(hWnd
, 0))
5071 CREATESTRUCTW
*pcs
= (CREATESTRUCTW
*)lParam
;
5073 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd
, pcs
->style
);
5074 return create_windowed_editor( hWnd
, pcs
, TRUE
);
5076 return RichEditANSIWndProc(hWnd
, msg
, wParam
, lParam
);
5079 void ME_SendOldNotify(ME_TextEditor
*editor
, int nCode
)
5081 ITextHost_TxNotify(editor
->texthost
, nCode
, NULL
);
5084 /* Fill buffer with srcChars unicode characters from the start cursor.
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.
5092 * returns the number of characters written excluding the NULL terminator.
5094 * The written text is always NULL terminated.
5096 int ME_GetTextW(ME_TextEditor
*editor
, WCHAR
*buffer
, int buflen
,
5097 const ME_Cursor
*start
, int srcChars
, BOOL bCRLF
,
5100 ME_DisplayItem
*pRun
, *pNextRun
;
5101 const WCHAR
*pStart
= buffer
;
5102 const WCHAR cr_lf
[] = {'\r', '\n', 0};
5106 /* bCRLF flag is only honored in 2.0 and up. 1.0 must always return text verbatim */
5107 if (editor
->bEmulateVersion10
) bCRLF
= FALSE
;
5111 pNextRun
= ME_FindItemFwd(pRun
, diRun
);
5113 nLen
= pRun
->member
.run
.len
- start
->nOffset
;
5114 str
= get_text( &pRun
->member
.run
, start
->nOffset
);
5116 while (srcChars
&& buflen
&& pNextRun
)
5118 int nFlags
= pRun
->member
.run
.nFlags
;
5120 if (bCRLF
&& nFlags
& MERF_ENDPARA
&& ~nFlags
& MERF_ENDCELL
)
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
);
5130 nLen
= min(nLen
, srcChars
);
5134 nLen
= min(nLen
, buflen
);
5137 CopyMemory(buffer
, str
, sizeof(WCHAR
) * nLen
);
5142 pNextRun
= ME_FindItemFwd(pRun
, diRun
);
5144 nLen
= pRun
->member
.run
.len
;
5145 str
= get_text( &pRun
->member
.run
, 0 );
5147 /* append '\r' to the last paragraph. */
5148 if (pRun
->next
->type
== diTextEnd
&& bEOP
)
5154 return buffer
- pStart
;
5157 static BOOL
ME_RegisterEditorClass(HINSTANCE hInstance
)
5162 wcW
.style
= CS_DBLCLKS
| CS_HREDRAW
| CS_VREDRAW
| CS_GLOBALCLASS
;
5163 wcW
.lpfnWndProc
= RichEditWndProcW
;
5165 wcW
.cbWndExtra
= sizeof(ME_TextEditor
*);
5166 wcW
.hInstance
= NULL
; /* hInstance would register DLL-local class */
5168 wcW
.hCursor
= LoadCursorW(NULL
, (LPWSTR
)IDC_IBEAM
);
5169 wcW
.hbrBackground
= GetStockObject(NULL_BRUSH
);
5170 wcW
.lpszMenuName
= NULL
;
5172 if (is_version_nt())
5174 wcW
.lpszClassName
= RICHEDIT_CLASS20W
;
5175 if (!RegisterClassW(&wcW
)) return FALSE
;
5176 wcW
.lpszClassName
= MSFTEDIT_CLASS
;
5177 if (!RegisterClassW(&wcW
)) return FALSE
;
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
;
5188 wcA
.style
= CS_DBLCLKS
| CS_HREDRAW
| CS_VREDRAW
| CS_GLOBALCLASS
;
5189 wcA
.lpfnWndProc
= RichEditWndProcA
;
5191 wcA
.cbWndExtra
= sizeof(ME_TextEditor
*);
5192 wcA
.hInstance
= NULL
; /* hInstance would register DLL-local class */
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
;
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
);
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
);
5219 /******************************************************************
5220 * REExtendedRegisterClass (RICHED20.8)
5222 * FIXME undocumented
5223 * Need to check for errors and implement controls and callbacks
5225 LRESULT WINAPI
REExtendedRegisterClass(void)
5230 FIXME("semi stub\n");
5234 wcW
.hInstance
= NULL
;
5237 wcW
.hbrBackground
= (HBRUSH
)(COLOR_WINDOW
+1);
5238 wcW
.lpszMenuName
= NULL
;
5240 if (!ME_ListBoxRegistered
)
5242 wcW
.style
= CS_PARENTDC
| CS_DBLCLKS
| CS_GLOBALCLASS
;
5243 wcW
.lpfnWndProc
= REListWndProc
;
5244 wcW
.lpszClassName
= REListBox20W
;
5245 if (RegisterClassW(&wcW
)) ME_ListBoxRegistered
= TRUE
;
5248 if (!ME_ComboBoxRegistered
)
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
;
5257 if (ME_ListBoxRegistered
)
5259 if (ME_ComboBoxRegistered
)
5265 static int wchar_comp( const void *key
, const void *elem
)
5267 return *(const WCHAR
*)key
- *(const WCHAR
*)elem
;
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
)
5274 /* NB this list is sorted */
5275 static const WCHAR neutral_chars
[] = {'!','\"','\'','(',')',',','-','.',':',';','<','>','?','[',']','{','}'};
5277 /* Some shortcuts */
5278 if (isalnum( c
)) return FALSE
;
5279 if (c
> neutral_chars
[sizeof(neutral_chars
) / sizeof(neutral_chars
[0]) - 1]) return FALSE
;
5281 return !!bsearch( &c
, neutral_chars
, sizeof(neutral_chars
) / sizeof(neutral_chars
[0]),
5282 sizeof(c
), wchar_comp
);
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.
5292 static BOOL
ME_FindNextURLCandidate(ME_TextEditor
*editor
,
5293 const ME_Cursor
*start
,
5295 ME_Cursor
*candidate_min
,
5296 ME_Cursor
*candidate_max
)
5298 ME_Cursor cursor
= *start
, neutral_end
, space_end
;
5299 BOOL candidateStarted
= FALSE
, quoted
= FALSE
;
5304 WCHAR
*str
= get_text( &cursor
.pRun
->member
.run
, 0 );
5305 int run_len
= cursor
.pRun
->member
.run
.len
;
5307 nChars
-= run_len
- cursor
.nOffset
;
5309 /* Find start of candidate */
5310 if (!candidateStarted
)
5312 while (cursor
.nOffset
< run_len
)
5314 c
= str
[cursor
.nOffset
];
5315 if (!isspaceW( c
) && !isurlneutral( c
))
5317 *candidate_min
= cursor
;
5318 candidateStarted
= TRUE
;
5319 neutral_end
.pPara
= NULL
;
5320 space_end
.pPara
= NULL
;
5324 quoted
= (c
== '<');
5329 /* Find end of candidate */
5330 if (candidateStarted
)
5332 while (cursor
.nOffset
< run_len
)
5334 c
= str
[cursor
.nOffset
];
5337 if (quoted
&& c
!= '\r')
5339 if (!space_end
.pPara
)
5341 if (neutral_end
.pPara
)
5342 space_end
= neutral_end
;
5350 else if (isurlneutral( c
))
5352 if (quoted
&& c
== '>')
5354 neutral_end
.pPara
= NULL
;
5355 space_end
.pPara
= NULL
;
5358 if (!neutral_end
.pPara
)
5359 neutral_end
= cursor
;
5362 neutral_end
.pPara
= NULL
;
5369 if (!ME_NextRun(&cursor
.pPara
, &cursor
.pRun
, TRUE
))
5374 if (candidateStarted
)
5376 if (space_end
.pPara
)
5377 *candidate_max
= space_end
;
5378 else if (neutral_end
.pPara
)
5379 *candidate_max
= neutral_end
;
5381 *candidate_max
= cursor
;
5384 *candidate_max
= *candidate_min
= cursor
;
5389 * This proc evaluates the selection and returns TRUE if it can be considered an URL
5391 static BOOL
ME_IsCandidateAnURL(ME_TextEditor
*editor
, const ME_Cursor
*start
, int nChars
)
5393 #define MAX_PREFIX_LEN 9
5395 const WCHAR text
[MAX_PREFIX_LEN
];
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},
5411 WCHAR bufferW
[MAX_PREFIX_LEN
+ 1];
5414 ME_GetTextW(editor
, bufferW
, MAX_PREFIX_LEN
, start
, nChars
, FALSE
, FALSE
);
5415 for (i
= 0; i
< sizeof(prefixes
) / sizeof(*prefixes
); i
++)
5417 if (nChars
< prefixes
[i
].length
) continue;
5418 if (!memcmp(prefixes
[i
].text
, bufferW
, prefixes
[i
].length
* sizeof(WCHAR
)))
5422 #undef MAX_PREFIX_LEN
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.
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.
5434 * nChars may be set to INT_MAX to update to the end of the text.
5436 * Returns TRUE if at least one section was modified.
5438 static BOOL
ME_UpdateLinkAttribute(ME_TextEditor
*editor
, ME_Cursor
*start
, int nChars
)
5440 BOOL modified
= FALSE
;
5441 ME_Cursor startCur
= *start
;
5443 if (!editor
->AutoURLDetect_bEnable
) return FALSE
;
5448 ME_Cursor candidateStart
, candidateEnd
;
5450 if (ME_FindNextURLCandidate(editor
, &startCur
, nChars
,
5451 &candidateStart
, &candidateEnd
))
5453 /* Section before candidate is not an URL */
5454 int cMin
= ME_GetCursorOfs(&candidateStart
);
5455 int cMax
= ME_GetCursorOfs(&candidateEnd
);
5457 if (!ME_IsCandidateAnURL(editor
, &candidateStart
, cMax
- cMin
))
5458 candidateStart
= candidateEnd
;
5459 nChars
-= cMax
- ME_GetCursorOfs(&startCur
);
5463 /* No more candidates until end of selection */
5467 if (startCur
.pRun
!= candidateStart
.pRun
||
5468 startCur
.nOffset
!= candidateStart
.nOffset
)
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
))
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
;
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
5484 while (candidateEnd
.nOffset
>= candidateEnd
.pRun
->member
.run
.len
)
5486 candidateEnd
.nOffset
-= candidateEnd
.pRun
->member
.run
.len
;
5487 candidateEnd
.pRun
= ME_FindItemFwd(candidateEnd
.pRun
, diRun
);
5492 if (candidateStart
.pRun
!= candidateEnd
.pRun
||
5493 candidateStart
.nOffset
!= candidateEnd
.nOffset
)
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
))
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
);
5509 startCur
= candidateEnd
;
5510 } while (nChars
> 0);