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
238 #define STACK_SIZE_DEFAULT 100
239 #define STACK_SIZE_MAX 1000
241 #define TEXT_LIMIT_DEFAULT 32767
243 WINE_DEFAULT_DEBUG_CHANNEL(richedit
);
245 static BOOL
ME_RegisterEditorClass(HINSTANCE
);
246 static BOOL
ME_UpdateLinkAttribute(ME_TextEditor
*editor
, ME_Cursor
*start
, int nChars
);
248 static const WCHAR REListBox20W
[] = {'R','E','L','i','s','t','B','o','x','2','0','W', 0};
249 static const WCHAR REComboBox20W
[] = {'R','E','C','o','m','b','o','B','o','x','2','0','W', 0};
250 static HCURSOR hLeft
;
252 BOOL me_debug
= FALSE
;
253 HANDLE me_heap
= NULL
;
255 static BOOL ME_ListBoxRegistered
= FALSE
;
256 static BOOL ME_ComboBoxRegistered
= FALSE
;
258 static inline BOOL
is_version_nt(void)
260 return !(GetVersion() & 0x80000000);
263 static ME_TextBuffer
*ME_MakeText(void) {
264 ME_TextBuffer
*buf
= heap_alloc(sizeof(*buf
));
265 ME_DisplayItem
*p1
= ME_MakeDI(diTextStart
);
266 ME_DisplayItem
*p2
= ME_MakeDI(diTextEnd
);
272 p1
->member
.para
.next_para
= p2
;
273 p2
->member
.para
.prev_para
= p1
;
274 p2
->member
.para
.nCharOfs
= 0;
278 buf
->pCharStyle
= NULL
;
284 static LRESULT
ME_StreamInText(ME_TextEditor
*editor
, DWORD dwFormat
, ME_InStream
*stream
, ME_Style
*style
)
287 LRESULT total_bytes_read
= 0;
288 BOOL is_read
= FALSE
;
289 DWORD cp
= CP_ACP
, copy
= 0;
290 char conv_buf
[4 + STREAMIN_BUFFER_SIZE
]; /* up to 4 additional UTF-8 bytes */
292 static const char bom_utf8
[] = {0xEF, 0xBB, 0xBF};
294 TRACE("%08x %p\n", dwFormat
, stream
);
298 WCHAR wszText
[STREAMIN_BUFFER_SIZE
+1];
302 ME_StreamInFill(stream
);
303 if (stream
->editstream
->dwError
)
307 total_bytes_read
+= stream
->dwSize
;
310 if (!(dwFormat
& SF_UNICODE
))
312 char * buf
= stream
->buffer
;
313 DWORD size
= stream
->dwSize
, end
;
318 if (stream
->dwSize
>= 3 && !memcmp(stream
->buffer
, bom_utf8
, 3))
330 memcpy(conv_buf
+ copy
, buf
, size
);
335 while ((buf
[end
-1] & 0xC0) == 0x80)
338 --total_bytes_read
; /* strange, but seems to match windows */
340 if (buf
[end
-1] & 0x80)
343 if ((buf
[end
-1] & 0xE0) == 0xC0)
345 if ((buf
[end
-1] & 0xF0) == 0xE0)
347 if ((buf
[end
-1] & 0xF8) == 0xF0)
350 if (size
- end
>= need
)
352 /* we have enough bytes for this sequence */
357 /* need more bytes, so don't transcode this sequence */
365 nWideChars
= MultiByteToWideChar(cp
, 0, buf
, end
, wszText
, STREAMIN_BUFFER_SIZE
);
372 memcpy(conv_buf
, buf
+ end
, size
- end
);
379 nWideChars
= stream
->dwSize
>> 1;
380 pText
= (WCHAR
*)stream
->buffer
;
383 ME_InsertTextFromCursor(editor
, 0, pText
, nWideChars
, style
);
384 if (stream
->dwSize
== 0)
388 return total_bytes_read
;
391 static void ME_ApplyBorderProperties(RTF_Info
*info
,
392 ME_BorderRect
*borderRect
,
393 RTFBorder
*borderDef
)
396 ME_Border
*pBorders
[] = {&borderRect
->top
,
400 for (i
= 0; i
< 4; i
++)
402 RTFColor
*colorDef
= info
->colorList
;
403 pBorders
[i
]->width
= borderDef
[i
].width
;
404 colorNum
= borderDef
[i
].color
;
405 while (colorDef
&& colorDef
->rtfCNum
!= colorNum
)
406 colorDef
= colorDef
->rtfNextColor
;
408 pBorders
[i
]->colorRef
= RGB(
409 colorDef
->rtfCRed
>= 0 ? colorDef
->rtfCRed
: 0,
410 colorDef
->rtfCGreen
>= 0 ? colorDef
->rtfCGreen
: 0,
411 colorDef
->rtfCBlue
>= 0 ? colorDef
->rtfCBlue
: 0);
413 pBorders
[i
]->colorRef
= RGB(0, 0, 0);
417 void ME_RTFCharAttrHook(RTF_Info
*info
)
420 fmt
.cbSize
= sizeof(fmt
);
424 switch(info
->rtfMinor
)
427 /* FIXME add more flags once they're implemented */
428 fmt
.dwMask
= CFM_BOLD
| CFM_ITALIC
| CFM_UNDERLINE
| CFM_UNDERLINETYPE
| CFM_STRIKEOUT
|
429 CFM_COLOR
| CFM_BACKCOLOR
| CFM_SIZE
| CFM_WEIGHT
;
430 fmt
.dwEffects
= CFE_AUTOCOLOR
| CFE_AUTOBACKCOLOR
;
431 fmt
.yHeight
= 12*20; /* 12pt */
432 fmt
.wWeight
= FW_NORMAL
;
433 fmt
.bUnderlineType
= CFU_UNDERLINE
;
436 fmt
.dwMask
= CFM_BOLD
| CFM_WEIGHT
;
437 fmt
.dwEffects
= info
->rtfParam
? CFE_BOLD
: 0;
438 fmt
.wWeight
= info
->rtfParam
? FW_BOLD
: FW_NORMAL
;
441 fmt
.dwMask
= CFM_ITALIC
;
442 fmt
.dwEffects
= info
->rtfParam
? fmt
.dwMask
: 0;
445 fmt
.dwMask
= CFM_UNDERLINETYPE
| CFM_UNDERLINE
;
446 fmt
.bUnderlineType
= CFU_UNDERLINE
;
447 fmt
.dwEffects
= info
->rtfParam
? CFE_UNDERLINE
: 0;
449 case rtfDotUnderline
:
450 fmt
.dwMask
= CFM_UNDERLINETYPE
| CFM_UNDERLINE
;
451 fmt
.bUnderlineType
= CFU_UNDERLINEDOTTED
;
452 fmt
.dwEffects
= info
->rtfParam
? CFE_UNDERLINE
: 0;
455 fmt
.dwMask
= CFM_UNDERLINETYPE
| CFM_UNDERLINE
;
456 fmt
.bUnderlineType
= CFU_UNDERLINEDOUBLE
;
457 fmt
.dwEffects
= info
->rtfParam
? CFE_UNDERLINE
: 0;
459 case rtfWordUnderline
:
460 fmt
.dwMask
= CFM_UNDERLINETYPE
| CFM_UNDERLINE
;
461 fmt
.bUnderlineType
= CFU_UNDERLINEWORD
;
462 fmt
.dwEffects
= info
->rtfParam
? CFE_UNDERLINE
: 0;
465 fmt
.dwMask
= CFM_UNDERLINE
;
469 fmt
.dwMask
= CFM_STRIKEOUT
;
470 fmt
.dwEffects
= info
->rtfParam
? fmt
.dwMask
: 0;
474 case rtfSubScrShrink
:
475 case rtfSuperScrShrink
:
477 fmt
.dwMask
= CFM_SUBSCRIPT
|CFM_SUPERSCRIPT
;
478 if (info
->rtfMinor
== rtfSubScrShrink
) fmt
.dwEffects
= CFE_SUBSCRIPT
;
479 if (info
->rtfMinor
== rtfSuperScrShrink
) fmt
.dwEffects
= CFE_SUPERSCRIPT
;
480 if (info
->rtfMinor
== rtfNoSuperSub
) fmt
.dwEffects
= 0;
483 fmt
.dwMask
= CFM_HIDDEN
;
484 fmt
.dwEffects
= info
->rtfParam
? fmt
.dwMask
: 0;
487 fmt
.dwMask
= CFM_BACKCOLOR
;
489 if (info
->rtfParam
== 0)
490 fmt
.dwEffects
= CFE_AUTOBACKCOLOR
;
491 else if (info
->rtfParam
!= rtfNoParam
)
493 RTFColor
*c
= RTFGetColor(info
, info
->rtfParam
);
494 if (c
&& c
->rtfCBlue
>= 0)
495 fmt
.crBackColor
= (c
->rtfCBlue
<<16)|(c
->rtfCGreen
<<8)|(c
->rtfCRed
);
497 fmt
.dwEffects
= CFE_AUTOBACKCOLOR
;
501 fmt
.dwMask
= CFM_COLOR
;
503 if (info
->rtfParam
== 0)
504 fmt
.dwEffects
= CFE_AUTOCOLOR
;
505 else if (info
->rtfParam
!= rtfNoParam
)
507 RTFColor
*c
= RTFGetColor(info
, info
->rtfParam
);
508 if (c
&& c
->rtfCBlue
>= 0)
509 fmt
.crTextColor
= (c
->rtfCBlue
<<16)|(c
->rtfCGreen
<<8)|(c
->rtfCRed
);
511 fmt
.dwEffects
= CFE_AUTOCOLOR
;
516 if (info
->rtfParam
!= rtfNoParam
)
518 RTFFont
*f
= RTFGetFont(info
, info
->rtfParam
);
521 MultiByteToWideChar(CP_ACP
, 0, f
->rtfFName
, -1, fmt
.szFaceName
, ARRAY_SIZE(fmt
.szFaceName
));
522 fmt
.szFaceName
[ARRAY_SIZE(fmt
.szFaceName
)-1] = '\0';
523 fmt
.bCharSet
= f
->rtfFCharSet
;
524 fmt
.dwMask
= CFM_FACE
| CFM_CHARSET
;
525 fmt
.bPitchAndFamily
= f
->rtfFPitch
| (f
->rtfFFamily
<< 4);
530 fmt
.dwMask
= CFM_SIZE
;
531 if (info
->rtfParam
!= rtfNoParam
)
532 fmt
.yHeight
= info
->rtfParam
*10;
537 RTFFlushOutputBuffer(info
);
538 /* FIXME too slow ? how come ? */
539 style2
= ME_ApplyStyle(info
->editor
, info
->style
, &fmt
);
540 ME_ReleaseStyle(info
->style
);
541 info
->style
= style2
;
542 info
->styleChanged
= TRUE
;
546 /* FIXME this function doesn't get any information about context of the RTF tag, which is very bad,
547 the same tags mean different things in different contexts */
548 void ME_RTFParAttrHook(RTF_Info
*info
)
550 switch(info
->rtfMinor
)
552 case rtfParDef
: /* restores default paragraph attributes */
553 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
554 info
->borderType
= RTFBorderParaLeft
;
555 else /* v1.0 - 3.0 */
556 info
->borderType
= RTFBorderParaTop
;
557 info
->fmt
.dwMask
= PFM_ALIGNMENT
| PFM_BORDER
| PFM_LINESPACING
| PFM_TABSTOPS
|
558 PFM_OFFSET
| PFM_RIGHTINDENT
| PFM_SPACEAFTER
| PFM_SPACEBEFORE
|
559 PFM_STARTINDENT
| PFM_RTLPARA
| PFM_NUMBERING
| PFM_NUMBERINGSTART
|
560 PFM_NUMBERINGSTYLE
| PFM_NUMBERINGTAB
;
562 info
->fmt
.wAlignment
= PFA_LEFT
;
563 info
->fmt
.cTabCount
= 0;
564 info
->fmt
.dxOffset
= info
->fmt
.dxStartIndent
= info
->fmt
.dxRightIndent
= 0;
565 info
->fmt
.wBorderWidth
= info
->fmt
.wBorders
= 0;
566 info
->fmt
.wBorderSpace
= 0;
567 info
->fmt
.bLineSpacingRule
= 0;
568 info
->fmt
.dySpaceBefore
= info
->fmt
.dySpaceAfter
= 0;
569 info
->fmt
.dyLineSpacing
= 0;
570 info
->fmt
.wEffects
&= ~PFE_RTLPARA
;
571 info
->fmt
.wNumbering
= 0;
572 info
->fmt
.wNumberingStart
= 0;
573 info
->fmt
.wNumberingStyle
= 0;
574 info
->fmt
.wNumberingTab
= 0;
576 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
578 if (info
->tableDef
&& info
->tableDef
->tableRowStart
&&
579 info
->tableDef
->tableRowStart
->member
.para
.nFlags
& MEPF_ROWEND
)
582 ME_DisplayItem
*para
;
583 /* We are just after a table row. */
584 RTFFlushOutputBuffer(info
);
585 cursor
= info
->editor
->pCursors
[0];
587 if (para
== info
->tableDef
->tableRowStart
->member
.para
.next_para
588 && !cursor
.nOffset
&& !cursor
.pRun
->member
.run
.nCharOfs
)
590 /* Since the table row end, no text has been inserted, and the \intbl
591 * control word has not be used. We can confirm that we are not in a
594 info
->tableDef
->tableRowStart
= NULL
;
595 info
->canInheritInTbl
= FALSE
;
598 } else { /* v1.0 - v3.0 */
599 info
->fmt
.dwMask
|= PFM_TABLE
;
600 info
->fmt
.wEffects
&= ~PFE_TABLE
;
604 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
606 while (info
->rtfParam
> info
->nestingLevel
) {
607 RTFTable
*tableDef
= heap_alloc_zero(sizeof(*tableDef
));
608 tableDef
->parent
= info
->tableDef
;
609 info
->tableDef
= tableDef
;
611 RTFFlushOutputBuffer(info
);
612 if (tableDef
->tableRowStart
&&
613 tableDef
->tableRowStart
->member
.para
.nFlags
& MEPF_ROWEND
)
615 ME_DisplayItem
*para
= tableDef
->tableRowStart
;
616 para
= para
->member
.para
.next_para
;
617 para
= ME_InsertTableRowStartAtParagraph(info
->editor
, para
);
618 tableDef
->tableRowStart
= para
;
622 cursor
= info
->editor
->pCursors
[0];
623 if (cursor
.nOffset
|| cursor
.pRun
->member
.run
.nCharOfs
)
624 ME_InsertTextFromCursor(info
->editor
, 0, &endl
, 1, info
->style
);
625 tableDef
->tableRowStart
= ME_InsertTableRowStartFromCursor(info
->editor
);
628 info
->nestingLevel
++;
630 info
->canInheritInTbl
= FALSE
;
635 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
637 if (info
->nestingLevel
< 1)
641 info
->tableDef
= heap_alloc_zero(sizeof(*info
->tableDef
));
642 tableDef
= info
->tableDef
;
643 RTFFlushOutputBuffer(info
);
644 if (tableDef
->tableRowStart
&&
645 tableDef
->tableRowStart
->member
.para
.nFlags
& MEPF_ROWEND
)
647 ME_DisplayItem
*para
= tableDef
->tableRowStart
;
648 para
= para
->member
.para
.next_para
;
649 para
= ME_InsertTableRowStartAtParagraph(info
->editor
, para
);
650 tableDef
->tableRowStart
= para
;
654 cursor
= info
->editor
->pCursors
[0];
655 if (cursor
.nOffset
|| cursor
.pRun
->member
.run
.nCharOfs
)
656 ME_InsertTextFromCursor(info
->editor
, 0, &endl
, 1, info
->style
);
657 tableDef
->tableRowStart
= ME_InsertTableRowStartFromCursor(info
->editor
);
659 info
->nestingLevel
= 1;
660 info
->canInheritInTbl
= TRUE
;
663 } else { /* v1.0 - v3.0 */
664 info
->fmt
.dwMask
|= PFM_TABLE
;
665 info
->fmt
.wEffects
|= PFE_TABLE
;
671 if ((info
->fmt
.dwMask
& (PFM_STARTINDENT
| PFM_OFFSET
)) != (PFM_STARTINDENT
| PFM_OFFSET
))
674 fmt
.cbSize
= sizeof(fmt
);
675 ME_GetSelectionParaFormat(info
->editor
, &fmt
);
676 info
->fmt
.dwMask
|= PFM_STARTINDENT
| PFM_OFFSET
;
677 info
->fmt
.dxStartIndent
= fmt
.dxStartIndent
;
678 info
->fmt
.dxOffset
= fmt
.dxOffset
;
680 if (info
->rtfMinor
== rtfFirstIndent
)
682 info
->fmt
.dxStartIndent
+= info
->fmt
.dxOffset
+ info
->rtfParam
;
683 info
->fmt
.dxOffset
= -info
->rtfParam
;
686 info
->fmt
.dxStartIndent
= info
->rtfParam
- info
->fmt
.dxOffset
;
689 info
->fmt
.dwMask
|= PFM_RIGHTINDENT
;
690 info
->fmt
.dxRightIndent
= info
->rtfParam
;
694 info
->fmt
.dwMask
|= PFM_ALIGNMENT
;
695 info
->fmt
.wAlignment
= PFA_LEFT
;
698 info
->fmt
.dwMask
|= PFM_ALIGNMENT
;
699 info
->fmt
.wAlignment
= PFA_RIGHT
;
702 info
->fmt
.dwMask
|= PFM_ALIGNMENT
;
703 info
->fmt
.wAlignment
= PFA_CENTER
;
706 if (!(info
->fmt
.dwMask
& PFM_TABSTOPS
))
709 fmt
.cbSize
= sizeof(fmt
);
710 ME_GetSelectionParaFormat(info
->editor
, &fmt
);
711 memcpy(info
->fmt
.rgxTabs
, fmt
.rgxTabs
,
712 fmt
.cTabCount
* sizeof(fmt
.rgxTabs
[0]));
713 info
->fmt
.cTabCount
= fmt
.cTabCount
;
714 info
->fmt
.dwMask
|= PFM_TABSTOPS
;
716 if (info
->fmt
.cTabCount
< MAX_TAB_STOPS
&& info
->rtfParam
< 0x1000000)
717 info
->fmt
.rgxTabs
[info
->fmt
.cTabCount
++] = info
->rtfParam
;
720 info
->fmt
.dwMask
|= PFM_KEEP
;
721 info
->fmt
.wEffects
|= PFE_KEEP
;
723 case rtfNoWidowControl
:
724 info
->fmt
.dwMask
|= PFM_NOWIDOWCONTROL
;
725 info
->fmt
.wEffects
|= PFE_NOWIDOWCONTROL
;
728 info
->fmt
.dwMask
|= PFM_KEEPNEXT
;
729 info
->fmt
.wEffects
|= PFE_KEEPNEXT
;
732 info
->fmt
.dwMask
|= PFM_SPACEAFTER
;
733 info
->fmt
.dySpaceAfter
= info
->rtfParam
;
736 info
->fmt
.dwMask
|= PFM_SPACEBEFORE
;
737 info
->fmt
.dySpaceBefore
= info
->rtfParam
;
739 case rtfSpaceBetween
:
740 info
->fmt
.dwMask
|= PFM_LINESPACING
;
741 if ((int)info
->rtfParam
> 0)
743 info
->fmt
.dyLineSpacing
= info
->rtfParam
;
744 info
->fmt
.bLineSpacingRule
= 3;
748 info
->fmt
.dyLineSpacing
= info
->rtfParam
;
749 info
->fmt
.bLineSpacingRule
= 4;
752 case rtfSpaceMultiply
:
753 info
->fmt
.dwMask
|= PFM_LINESPACING
;
754 info
->fmt
.dyLineSpacing
= info
->rtfParam
* 20;
755 info
->fmt
.bLineSpacingRule
= 5;
758 info
->fmt
.dwMask
|= PFM_NUMBERING
;
759 info
->fmt
.wNumbering
= PFN_BULLET
;
762 info
->fmt
.dwMask
|= PFM_NUMBERING
;
763 info
->fmt
.wNumbering
= 2; /* FIXME: MSDN says it's not used ?? */
766 info
->borderType
= RTFBorderParaLeft
;
767 info
->fmt
.wBorders
|= 1;
768 info
->fmt
.dwMask
|= PFM_BORDER
;
771 info
->borderType
= RTFBorderParaRight
;
772 info
->fmt
.wBorders
|= 2;
773 info
->fmt
.dwMask
|= PFM_BORDER
;
776 info
->borderType
= RTFBorderParaTop
;
777 info
->fmt
.wBorders
|= 4;
778 info
->fmt
.dwMask
|= PFM_BORDER
;
780 case rtfBorderBottom
:
781 info
->borderType
= RTFBorderParaBottom
;
782 info
->fmt
.wBorders
|= 8;
783 info
->fmt
.dwMask
|= PFM_BORDER
;
785 case rtfBorderSingle
:
786 info
->fmt
.wBorders
&= ~0x700;
787 info
->fmt
.wBorders
|= 1 << 8;
788 info
->fmt
.dwMask
|= PFM_BORDER
;
791 info
->fmt
.wBorders
&= ~0x700;
792 info
->fmt
.wBorders
|= 2 << 8;
793 info
->fmt
.dwMask
|= PFM_BORDER
;
795 case rtfBorderShadow
:
796 info
->fmt
.wBorders
&= ~0x700;
797 info
->fmt
.wBorders
|= 10 << 8;
798 info
->fmt
.dwMask
|= PFM_BORDER
;
800 case rtfBorderDouble
:
801 info
->fmt
.wBorders
&= ~0x700;
802 info
->fmt
.wBorders
|= 7 << 8;
803 info
->fmt
.dwMask
|= PFM_BORDER
;
806 info
->fmt
.wBorders
&= ~0x700;
807 info
->fmt
.wBorders
|= 11 << 8;
808 info
->fmt
.dwMask
|= PFM_BORDER
;
812 int borderSide
= info
->borderType
& RTFBorderSideMask
;
813 RTFTable
*tableDef
= info
->tableDef
;
814 if ((info
->borderType
& RTFBorderTypeMask
) == RTFBorderTypeCell
)
817 if (!tableDef
|| tableDef
->numCellsDefined
>= MAX_TABLE_CELLS
)
819 border
= &tableDef
->cells
[tableDef
->numCellsDefined
].border
[borderSide
];
820 border
->width
= info
->rtfParam
;
823 info
->fmt
.wBorderWidth
= info
->rtfParam
;
824 info
->fmt
.dwMask
|= PFM_BORDER
;
828 info
->fmt
.wBorderSpace
= info
->rtfParam
;
829 info
->fmt
.dwMask
|= PFM_BORDER
;
833 RTFTable
*tableDef
= info
->tableDef
;
834 int borderSide
= info
->borderType
& RTFBorderSideMask
;
835 int borderType
= info
->borderType
& RTFBorderTypeMask
;
838 case RTFBorderTypePara
:
839 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
841 /* v1.0 - 3.0 treat paragraph and row borders the same. */
842 case RTFBorderTypeRow
:
844 tableDef
->border
[borderSide
].color
= info
->rtfParam
;
847 case RTFBorderTypeCell
:
848 if (tableDef
&& tableDef
->numCellsDefined
< MAX_TABLE_CELLS
) {
849 tableDef
->cells
[tableDef
->numCellsDefined
].border
[borderSide
].color
= info
->rtfParam
;
856 info
->fmt
.dwMask
|= PFM_RTLPARA
;
857 info
->fmt
.wEffects
|= PFE_RTLPARA
;
860 info
->fmt
.dwMask
|= PFM_RTLPARA
;
861 info
->fmt
.wEffects
&= ~PFE_RTLPARA
;
866 void ME_RTFTblAttrHook(RTF_Info
*info
)
868 switch (info
->rtfMinor
)
872 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
873 info
->borderType
= 0; /* Not sure */
874 else /* v1.0 - 3.0 */
875 info
->borderType
= RTFBorderRowTop
;
876 if (!info
->tableDef
) {
877 info
->tableDef
= ME_MakeTableDef(info
->editor
);
879 ME_InitTableDef(info
->editor
, info
->tableDef
);
888 info
->tableDef
= ME_MakeTableDef(info
->editor
);
890 cellNum
= info
->tableDef
->numCellsDefined
;
891 if (cellNum
>= MAX_TABLE_CELLS
)
893 info
->tableDef
->cells
[cellNum
].rightBoundary
= info
->rtfParam
;
894 if (cellNum
< MAX_TAB_STOPS
) {
895 /* Tab stops were used to store cell positions before v4.1 but v4.1
896 * still seems to set the tabstops without using them. */
897 ME_DisplayItem
*para
= info
->editor
->pCursors
[0].pPara
;
898 PARAFORMAT2
*pFmt
= ¶
->member
.para
.fmt
;
899 pFmt
->rgxTabs
[cellNum
] &= ~0x00FFFFFF;
900 pFmt
->rgxTabs
[cellNum
] |= 0x00FFFFFF & info
->rtfParam
;
902 info
->tableDef
->numCellsDefined
++;
906 info
->borderType
= RTFBorderRowTop
;
909 info
->borderType
= RTFBorderRowLeft
;
911 case rtfRowBordBottom
:
912 info
->borderType
= RTFBorderRowBottom
;
914 case rtfRowBordRight
:
915 info
->borderType
= RTFBorderRowRight
;
918 info
->borderType
= RTFBorderCellTop
;
920 case rtfCellBordLeft
:
921 info
->borderType
= RTFBorderCellLeft
;
923 case rtfCellBordBottom
:
924 info
->borderType
= RTFBorderCellBottom
;
926 case rtfCellBordRight
:
927 info
->borderType
= RTFBorderCellRight
;
931 info
->tableDef
->gapH
= info
->rtfParam
;
935 info
->tableDef
->leftEdge
= info
->rtfParam
;
940 void ME_RTFSpecialCharHook(RTF_Info
*info
)
942 RTFTable
*tableDef
= info
->tableDef
;
943 switch (info
->rtfMinor
)
946 if (info
->editor
->bEmulateVersion10
) /* v1.0 - v3.0 */
948 /* else fall through since v4.1 treats rtfNestCell and rtfCell the same */
952 RTFFlushOutputBuffer(info
);
953 if (!info
->editor
->bEmulateVersion10
) { /* v4.1 */
954 if (tableDef
->tableRowStart
)
956 if (!info
->nestingLevel
&&
957 tableDef
->tableRowStart
->member
.para
.nFlags
& MEPF_ROWEND
)
959 ME_DisplayItem
*para
= tableDef
->tableRowStart
;
960 para
= para
->member
.para
.next_para
;
961 para
= ME_InsertTableRowStartAtParagraph(info
->editor
, para
);
962 tableDef
->tableRowStart
= para
;
963 info
->nestingLevel
= 1;
965 ME_InsertTableCellFromCursor(info
->editor
);
967 } else { /* v1.0 - v3.0 */
968 ME_DisplayItem
*para
= info
->editor
->pCursors
[0].pPara
;
969 PARAFORMAT2
*pFmt
= ¶
->member
.para
.fmt
;
970 if (pFmt
->dwMask
& PFM_TABLE
&& pFmt
->wEffects
& PFE_TABLE
&&
971 tableDef
->numCellsInserted
< tableDef
->numCellsDefined
)
974 ME_InsertTextFromCursor(info
->editor
, 0, &tab
, 1, info
->style
);
975 tableDef
->numCellsInserted
++;
980 if (info
->editor
->bEmulateVersion10
) /* v1.0 - v3.0 */
982 /* else fall through since v4.1 treats rtfNestRow and rtfRow the same */
985 ME_DisplayItem
*para
, *cell
, *run
;
990 RTFFlushOutputBuffer(info
);
991 if (!info
->editor
->bEmulateVersion10
) { /* v4.1 */
992 if (!tableDef
->tableRowStart
)
994 if (!info
->nestingLevel
&&
995 tableDef
->tableRowStart
->member
.para
.nFlags
& MEPF_ROWEND
)
997 para
= tableDef
->tableRowStart
;
998 para
= para
->member
.para
.next_para
;
999 para
= ME_InsertTableRowStartAtParagraph(info
->editor
, para
);
1000 tableDef
->tableRowStart
= para
;
1001 info
->nestingLevel
++;
1003 para
= tableDef
->tableRowStart
;
1004 cell
= ME_FindItemFwd(para
, diCell
);
1005 assert(cell
&& !cell
->member
.cell
.prev_cell
);
1006 if (tableDef
->numCellsDefined
< 1)
1008 /* 2000 twips appears to be the cell size that native richedit uses
1009 * when no cell sizes are specified. */
1010 const int defaultCellSize
= 2000;
1011 int nRightBoundary
= defaultCellSize
;
1012 cell
->member
.cell
.nRightBoundary
= nRightBoundary
;
1013 while (cell
->member
.cell
.next_cell
) {
1014 cell
= cell
->member
.cell
.next_cell
;
1015 nRightBoundary
+= defaultCellSize
;
1016 cell
->member
.cell
.nRightBoundary
= nRightBoundary
;
1018 para
= ME_InsertTableCellFromCursor(info
->editor
);
1019 cell
= para
->member
.para
.pCell
;
1020 cell
->member
.cell
.nRightBoundary
= nRightBoundary
;
1022 for (i
= 0; i
< tableDef
->numCellsDefined
; i
++)
1024 RTFCell
*cellDef
= &tableDef
->cells
[i
];
1025 cell
->member
.cell
.nRightBoundary
= cellDef
->rightBoundary
;
1026 ME_ApplyBorderProperties(info
, &cell
->member
.cell
.border
,
1028 cell
= cell
->member
.cell
.next_cell
;
1031 para
= ME_InsertTableCellFromCursor(info
->editor
);
1032 cell
= para
->member
.para
.pCell
;
1035 /* Cell for table row delimiter is empty */
1036 cell
->member
.cell
.nRightBoundary
= tableDef
->cells
[i
-1].rightBoundary
;
1039 run
= ME_FindItemFwd(cell
, diRun
);
1040 if (info
->editor
->pCursors
[0].pRun
!= run
||
1041 info
->editor
->pCursors
[0].nOffset
)
1044 /* Delete inserted cells that aren't defined. */
1045 info
->editor
->pCursors
[1].pRun
= run
;
1046 info
->editor
->pCursors
[1].pPara
= ME_GetParagraph(run
);
1047 info
->editor
->pCursors
[1].nOffset
= 0;
1048 nOfs
= ME_GetCursorOfs(&info
->editor
->pCursors
[1]);
1049 nChars
= ME_GetCursorOfs(&info
->editor
->pCursors
[0]) - nOfs
;
1050 ME_InternalDeleteText(info
->editor
, &info
->editor
->pCursors
[1],
1054 para
= ME_InsertTableRowEndFromCursor(info
->editor
);
1055 para
->member
.para
.fmt
.dxOffset
= abs(info
->tableDef
->gapH
);
1056 para
->member
.para
.fmt
.dxStartIndent
= info
->tableDef
->leftEdge
;
1057 ME_ApplyBorderProperties(info
, ¶
->member
.para
.border
,
1059 info
->nestingLevel
--;
1060 if (!info
->nestingLevel
)
1062 if (info
->canInheritInTbl
) {
1063 tableDef
->tableRowStart
= para
;
1065 while (info
->tableDef
) {
1066 tableDef
= info
->tableDef
;
1067 info
->tableDef
= tableDef
->parent
;
1068 heap_free(tableDef
);
1072 info
->tableDef
= tableDef
->parent
;
1073 heap_free(tableDef
);
1075 } else { /* v1.0 - v3.0 */
1077 ME_DisplayItem
*para
= info
->editor
->pCursors
[0].pPara
;
1078 PARAFORMAT2
*pFmt
= ¶
->member
.para
.fmt
;
1079 pFmt
->dxOffset
= info
->tableDef
->gapH
;
1080 pFmt
->dxStartIndent
= info
->tableDef
->leftEdge
;
1082 ME_ApplyBorderProperties(info
, ¶
->member
.para
.border
,
1084 while (tableDef
->numCellsInserted
< tableDef
->numCellsDefined
)
1087 ME_InsertTextFromCursor(info
->editor
, 0, &tab
, 1, info
->style
);
1088 tableDef
->numCellsInserted
++;
1090 pFmt
->cTabCount
= min(tableDef
->numCellsDefined
, MAX_TAB_STOPS
);
1091 if (!tableDef
->numCellsDefined
)
1092 pFmt
->wEffects
&= ~PFE_TABLE
;
1093 ME_InsertTextFromCursor(info
->editor
, 0, &endl
, 1, info
->style
);
1094 tableDef
->numCellsInserted
= 0;
1100 if (info
->editor
->bEmulateVersion10
) { /* v1.0 - 3.0 */
1101 ME_DisplayItem
*para
;
1103 RTFFlushOutputBuffer(info
);
1104 para
= info
->editor
->pCursors
[0].pPara
;
1105 pFmt
= ¶
->member
.para
.fmt
;
1106 if (pFmt
->dwMask
& PFM_TABLE
&& pFmt
->wEffects
& PFE_TABLE
)
1108 /* rtfPar is treated like a space within a table. */
1109 info
->rtfClass
= rtfText
;
1110 info
->rtfMajor
= ' ';
1112 else if (info
->rtfMinor
== rtfPar
&& tableDef
)
1113 tableDef
->numCellsInserted
= 0;
1119 static HRESULT
insert_static_object(ME_TextEditor
*editor
, HENHMETAFILE hemf
, HBITMAP hbmp
,
1122 LPOLEOBJECT lpObject
= NULL
;
1123 LPSTORAGE lpStorage
= NULL
;
1124 LPOLECLIENTSITE lpClientSite
= NULL
;
1125 LPDATAOBJECT lpDataObject
= NULL
;
1126 LPOLECACHE lpOleCache
= NULL
;
1130 HRESULT hr
= E_FAIL
;
1135 stgm
.tymed
= TYMED_ENHMF
;
1136 stgm
.u
.hEnhMetaFile
= hemf
;
1137 fm
.cfFormat
= CF_ENHMETAFILE
;
1141 stgm
.tymed
= TYMED_GDI
;
1142 stgm
.u
.hBitmap
= hbmp
;
1143 fm
.cfFormat
= CF_BITMAP
;
1145 stgm
.pUnkForRelease
= NULL
;
1148 fm
.dwAspect
= DVASPECT_CONTENT
;
1150 fm
.tymed
= stgm
.tymed
;
1154 if (!CreateIRichEditOle(NULL
, editor
, (LPVOID
*)&editor
->reOle
))
1158 if (OleCreateDefaultHandler(&CLSID_NULL
, NULL
, &IID_IOleObject
, (void**)&lpObject
) == S_OK
&&
1159 IRichEditOle_GetClientSite(editor
->reOle
, &lpClientSite
) == S_OK
&&
1160 IOleObject_SetClientSite(lpObject
, lpClientSite
) == S_OK
&&
1161 IOleObject_GetUserClassID(lpObject
, &clsid
) == S_OK
&&
1162 IOleObject_QueryInterface(lpObject
, &IID_IOleCache
, (void**)&lpOleCache
) == S_OK
&&
1163 IOleCache_Cache(lpOleCache
, &fm
, 0, &conn
) == S_OK
&&
1164 IOleObject_QueryInterface(lpObject
, &IID_IDataObject
, (void**)&lpDataObject
) == S_OK
&&
1165 IDataObject_SetData(lpDataObject
, &fm
, &stgm
, TRUE
) == S_OK
)
1169 reobject
.cbStruct
= sizeof(reobject
);
1170 reobject
.cp
= REO_CP_SELECTION
;
1171 reobject
.clsid
= clsid
;
1172 reobject
.poleobj
= lpObject
;
1173 reobject
.pstg
= lpStorage
;
1174 reobject
.polesite
= lpClientSite
;
1175 /* convert from twips to .01 mm */
1176 reobject
.sizel
.cx
= MulDiv(sz
->cx
, 254, 144);
1177 reobject
.sizel
.cy
= MulDiv(sz
->cy
, 254, 144);
1178 reobject
.dvaspect
= DVASPECT_CONTENT
;
1179 reobject
.dwFlags
= 0; /* FIXME */
1180 reobject
.dwUser
= 0;
1182 ME_InsertOLEFromCursor(editor
, &reobject
, 0);
1186 if (lpObject
) IOleObject_Release(lpObject
);
1187 if (lpClientSite
) IOleClientSite_Release(lpClientSite
);
1188 if (lpStorage
) IStorage_Release(lpStorage
);
1189 if (lpDataObject
) IDataObject_Release(lpDataObject
);
1190 if (lpOleCache
) IOleCache_Release(lpOleCache
);
1195 static void ME_RTFReadShpPictGroup( RTF_Info
*info
)
1203 if (info
->rtfClass
== rtfEOF
) return;
1204 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1206 if (--level
== 0) break;
1208 else if (RTFCheckCM( info
, rtfGroup
, rtfBeginGroup
))
1214 RTFRouteToken( info
);
1215 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1220 RTFRouteToken( info
); /* feed "}" back to router */
1224 static DWORD
read_hex_data( RTF_Info
*info
, BYTE
**out
)
1226 DWORD read
= 0, size
= 1024;
1232 if (info
->rtfClass
!= rtfText
)
1234 ERR("Called with incorrect token\n");
1238 buf
= HeapAlloc( GetProcessHeap(), 0, size
);
1241 val
= info
->rtfMajor
;
1242 for (flip
= TRUE
;; flip
= !flip
)
1244 RTFGetToken( info
);
1245 if (info
->rtfClass
== rtfEOF
)
1247 HeapFree( GetProcessHeap(), 0, buf
);
1250 if (info
->rtfClass
!= rtfText
) break;
1256 buf
= HeapReAlloc( GetProcessHeap(), 0, buf
, size
);
1259 buf
[read
++] = RTFCharToHex(val
) * 16 + RTFCharToHex(info
->rtfMajor
);
1262 val
= info
->rtfMajor
;
1264 if (flip
) FIXME("wrong hex string\n");
1270 static void ME_RTFReadPictGroup(RTF_Info
*info
)
1273 BYTE
*buffer
= NULL
;
1278 enum gfxkind
{gfx_unknown
= 0, gfx_enhmetafile
, gfx_metafile
, gfx_dib
} gfx
= gfx_unknown
;
1286 RTFGetToken( info
);
1288 if (info
->rtfClass
== rtfText
)
1293 size
= read_hex_data( info
, &buffer
);
1297 RTFSkipGroup( info
);
1299 } /* We potentially have a new token so fall through. */
1301 if (info
->rtfClass
== rtfEOF
) return;
1303 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1305 if (--level
== 0) break;
1308 if (RTFCheckCM( info
, rtfGroup
, rtfBeginGroup
))
1313 if (!RTFCheckCM( info
, rtfControl
, rtfPictAttr
))
1315 RTFRouteToken( info
);
1316 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1321 if (RTFCheckMM( info
, rtfPictAttr
, rtfWinMetafile
))
1323 mfp
.mm
= info
->rtfParam
;
1326 else if (RTFCheckMM( info
, rtfPictAttr
, rtfDevIndBitmap
))
1328 if (info
->rtfParam
!= 0) FIXME("dibitmap should be 0 (%d)\n", info
->rtfParam
);
1331 else if (RTFCheckMM( info
, rtfPictAttr
, rtfEmfBlip
))
1332 gfx
= gfx_enhmetafile
;
1333 else if (RTFCheckMM( info
, rtfPictAttr
, rtfPicWid
))
1334 mfp
.xExt
= info
->rtfParam
;
1335 else if (RTFCheckMM( info
, rtfPictAttr
, rtfPicHt
))
1336 mfp
.yExt
= info
->rtfParam
;
1337 else if (RTFCheckMM( info
, rtfPictAttr
, rtfPicGoalWid
))
1338 sz
.cx
= info
->rtfParam
;
1339 else if (RTFCheckMM( info
, rtfPictAttr
, rtfPicGoalHt
))
1340 sz
.cy
= info
->rtfParam
;
1342 FIXME("Non supported attribute: %d %d %d\n", info
->rtfClass
, info
->rtfMajor
, info
->rtfMinor
);
1349 case gfx_enhmetafile
:
1350 if ((hemf
= SetEnhMetaFileBits( size
, buffer
)))
1351 insert_static_object( info
->editor
, hemf
, NULL
, &sz
);
1354 if ((hemf
= SetWinMetaFileBits( size
, buffer
, NULL
, &mfp
)))
1355 insert_static_object( info
->editor
, hemf
, NULL
, &sz
);
1359 BITMAPINFO
*bi
= (BITMAPINFO
*)buffer
;
1361 unsigned nc
= bi
->bmiHeader
.biClrUsed
;
1363 /* not quite right, especially for bitfields type of compression */
1364 if (!nc
&& bi
->bmiHeader
.biBitCount
<= 8)
1365 nc
= 1 << bi
->bmiHeader
.biBitCount
;
1366 if ((hbmp
= CreateDIBitmap( hdc
, &bi
->bmiHeader
,
1367 CBM_INIT
, (char*)(bi
+ 1) + nc
* sizeof(RGBQUAD
),
1368 bi
, DIB_RGB_COLORS
)) )
1369 insert_static_object( info
->editor
, NULL
, hbmp
, &sz
);
1370 ReleaseDC( 0, hdc
);
1377 HeapFree( GetProcessHeap(), 0, buffer
);
1378 RTFRouteToken( info
); /* feed "}" back to router */
1382 /* for now, lookup the \result part and use it, whatever the object */
1383 static void ME_RTFReadObjectGroup(RTF_Info
*info
)
1388 if (info
->rtfClass
== rtfEOF
)
1390 if (RTFCheckCM(info
, rtfGroup
, rtfEndGroup
))
1392 if (RTFCheckCM(info
, rtfGroup
, rtfBeginGroup
))
1395 if (info
->rtfClass
== rtfEOF
)
1397 if (RTFCheckCMM(info
, rtfControl
, rtfDestination
, rtfObjResult
))
1401 while (RTFGetToken (info
) != rtfEOF
)
1403 if (info
->rtfClass
== rtfGroup
)
1405 if (info
->rtfMajor
== rtfBeginGroup
) level
++;
1406 else if (info
->rtfMajor
== rtfEndGroup
&& --level
< 0) break;
1408 RTFRouteToken(info
);
1411 else RTFSkipGroup(info
);
1414 if (!RTFCheckCM (info
, rtfControl
, rtfObjAttr
))
1416 FIXME("Non supported attribute: %d %d %d\n", info
->rtfClass
, info
->rtfMajor
, info
->rtfMinor
);
1420 RTFRouteToken(info
); /* feed "}" back to router */
1423 static void ME_RTFReadParnumGroup( RTF_Info
*info
)
1425 int level
= 1, type
= -1;
1426 WORD indent
= 0, start
= 1;
1427 WCHAR txt_before
= 0, txt_after
= 0;
1431 RTFGetToken( info
);
1433 if (RTFCheckCMM( info
, rtfControl
, rtfDestination
, rtfParNumTextBefore
) ||
1434 RTFCheckCMM( info
, rtfControl
, rtfDestination
, rtfParNumTextAfter
))
1436 int loc
= info
->rtfMinor
;
1438 RTFGetToken( info
);
1439 if (info
->rtfClass
== rtfText
)
1441 if (loc
== rtfParNumTextBefore
)
1442 txt_before
= info
->rtfMajor
;
1444 txt_after
= info
->rtfMajor
;
1447 /* falling through to catch EOFs and group level changes */
1450 if (info
->rtfClass
== rtfEOF
)
1453 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1455 if (--level
== 0) break;
1459 if (RTFCheckCM( info
, rtfGroup
, rtfBeginGroup
))
1465 /* Ignore non para-attr */
1466 if (!RTFCheckCM( info
, rtfControl
, rtfParAttr
))
1469 switch (info
->rtfMinor
)
1471 case rtfParLevel
: /* Para level is ignored */
1478 case rtfParNumDecimal
:
1481 case rtfParNumULetter
:
1482 type
= PFN_UCLETTER
;
1484 case rtfParNumURoman
:
1487 case rtfParNumLLetter
:
1488 type
= PFN_LCLETTER
;
1490 case rtfParNumLRoman
:
1494 case rtfParNumIndent
:
1495 indent
= info
->rtfParam
;
1497 case rtfParNumStartAt
:
1498 start
= info
->rtfParam
;
1505 info
->fmt
.dwMask
|= (PFM_NUMBERING
| PFM_NUMBERINGSTART
| PFM_NUMBERINGSTYLE
| PFM_NUMBERINGTAB
);
1506 info
->fmt
.wNumbering
= type
;
1507 info
->fmt
.wNumberingStart
= start
;
1508 info
->fmt
.wNumberingStyle
= PFNS_PAREN
;
1509 if (type
!= PFN_BULLET
)
1511 if (txt_before
== 0 && txt_after
== 0)
1512 info
->fmt
.wNumberingStyle
= PFNS_PLAIN
;
1513 else if (txt_after
== '.')
1514 info
->fmt
.wNumberingStyle
= PFNS_PERIOD
;
1515 else if (txt_before
== '(' && txt_after
== ')')
1516 info
->fmt
.wNumberingStyle
= PFNS_PARENS
;
1518 info
->fmt
.wNumberingTab
= indent
;
1521 TRACE("type %d indent %d start %d txt before %04x txt after %04x\n",
1522 type
, indent
, start
, txt_before
, txt_after
);
1524 RTFRouteToken( info
); /* feed "}" back to router */
1527 static void ME_RTFReadHook(RTF_Info
*info
)
1529 switch(info
->rtfClass
)
1532 switch(info
->rtfMajor
)
1535 if (info
->stackTop
< maxStack
) {
1536 info
->stack
[info
->stackTop
].style
= info
->style
;
1537 ME_AddRefStyle(info
->style
);
1538 info
->stack
[info
->stackTop
].codePage
= info
->codePage
;
1539 info
->stack
[info
->stackTop
].unicodeLength
= info
->unicodeLength
;
1542 info
->styleChanged
= FALSE
;
1546 RTFFlushOutputBuffer(info
);
1548 if (info
->stackTop
<= 0)
1549 info
->rtfClass
= rtfEOF
;
1550 if (info
->stackTop
< 0)
1553 ME_ReleaseStyle(info
->style
);
1554 info
->style
= info
->stack
[info
->stackTop
].style
;
1555 info
->codePage
= info
->stack
[info
->stackTop
].codePage
;
1556 info
->unicodeLength
= info
->stack
[info
->stackTop
].unicodeLength
;
1565 ME_StreamInFill(ME_InStream
*stream
)
1567 stream
->editstream
->dwError
= stream
->editstream
->pfnCallback(stream
->editstream
->dwCookie
,
1568 (BYTE
*)stream
->buffer
,
1569 sizeof(stream
->buffer
),
1570 (LONG
*)&stream
->dwSize
);
1574 static LRESULT
ME_StreamIn(ME_TextEditor
*editor
, DWORD format
, EDITSTREAM
*stream
, BOOL stripLastCR
)
1578 int from
, to
, nUndoMode
;
1579 int nEventMask
= editor
->nEventMask
;
1580 ME_InStream inStream
;
1581 BOOL invalidRTF
= FALSE
;
1582 ME_Cursor
*selStart
, *selEnd
;
1583 LRESULT num_read
= 0; /* bytes read for SF_TEXT, non-control chars inserted for SF_RTF */
1585 TRACE("stream==%p editor==%p format==0x%X\n", stream
, editor
, format
);
1586 editor
->nEventMask
= 0;
1588 ME_GetSelectionOfs(editor
, &from
, &to
);
1589 if (format
& SFF_SELECTION
&& editor
->mode
& TM_RICHTEXT
)
1591 ME_GetSelection(editor
, &selStart
, &selEnd
);
1592 style
= ME_GetSelectionInsertStyle(editor
);
1594 ME_InternalDeleteText(editor
, selStart
, to
- from
, FALSE
);
1596 /* Don't insert text at the end of the table row */
1597 if (!editor
->bEmulateVersion10
) { /* v4.1 */
1598 ME_DisplayItem
*para
= editor
->pCursors
->pPara
;
1599 if (para
->member
.para
.nFlags
& MEPF_ROWEND
)
1601 para
= para
->member
.para
.next_para
;
1602 editor
->pCursors
[0].pPara
= para
;
1603 editor
->pCursors
[0].pRun
= ME_FindItemFwd(para
, diRun
);
1604 editor
->pCursors
[0].nOffset
= 0;
1606 if (para
->member
.para
.nFlags
& MEPF_ROWSTART
)
1608 para
= para
->member
.para
.next_para
;
1609 editor
->pCursors
[0].pPara
= para
;
1610 editor
->pCursors
[0].pRun
= ME_FindItemFwd(para
, diRun
);
1611 editor
->pCursors
[0].nOffset
= 0;
1613 editor
->pCursors
[1] = editor
->pCursors
[0];
1614 } else { /* v1.0 - 3.0 */
1615 if (editor
->pCursors
[0].pRun
->member
.run
.nFlags
& MERF_ENDPARA
&&
1616 ME_IsInTable(editor
->pCursors
[0].pRun
))
1620 style
= editor
->pBuffer
->pDefaultStyle
;
1621 ME_AddRefStyle(style
);
1622 ME_SetSelection(editor
, 0, 0);
1623 ME_InternalDeleteText(editor
, &editor
->pCursors
[1],
1624 ME_GetTextLength(editor
), FALSE
);
1626 ME_ClearTempStyle(editor
);
1627 ME_SetDefaultParaFormat(editor
, &editor
->pCursors
[0].pPara
->member
.para
.fmt
);
1631 /* Back up undo mode to a local variable */
1632 nUndoMode
= editor
->nUndoMode
;
1634 /* Only create an undo if SFF_SELECTION is set */
1635 if (!(format
& SFF_SELECTION
))
1636 editor
->nUndoMode
= umIgnore
;
1638 inStream
.editstream
= stream
;
1639 inStream
.editstream
->dwError
= 0;
1640 inStream
.dwSize
= 0;
1641 inStream
.dwUsed
= 0;
1643 if (format
& SF_RTF
)
1645 /* Check if it's really RTF, and if it is not, use plain text */
1646 ME_StreamInFill(&inStream
);
1647 if (!inStream
.editstream
->dwError
)
1649 if ((!editor
->bEmulateVersion10
&& strncmp(inStream
.buffer
, "{\\rtf", 5) && strncmp(inStream
.buffer
, "{\\urtf", 6))
1650 || (editor
->bEmulateVersion10
&& *inStream
.buffer
!= '{'))
1653 inStream
.editstream
->dwError
= -16;
1658 if (!invalidRTF
&& !inStream
.editstream
->dwError
)
1661 from
= ME_GetCursorOfs(&editor
->pCursors
[0]);
1662 if (format
& SF_RTF
) {
1664 /* setup the RTF parser */
1665 memset(&parser
, 0, sizeof parser
);
1666 RTFSetEditStream(&parser
, &inStream
);
1667 parser
.rtfFormat
= format
&(SF_TEXT
|SF_RTF
);
1668 parser
.editor
= editor
;
1669 parser
.style
= style
;
1670 WriterInit(&parser
);
1672 RTFSetReadHook(&parser
, ME_RTFReadHook
);
1673 RTFSetDestinationCallback(&parser
, rtfShpPict
, ME_RTFReadShpPictGroup
);
1674 RTFSetDestinationCallback(&parser
, rtfPict
, ME_RTFReadPictGroup
);
1675 RTFSetDestinationCallback(&parser
, rtfObject
, ME_RTFReadObjectGroup
);
1676 RTFSetDestinationCallback(&parser
, rtfParNumbering
, ME_RTFReadParnumGroup
);
1677 if (!parser
.editor
->bEmulateVersion10
) /* v4.1 */
1679 RTFSetDestinationCallback(&parser
, rtfNoNestTables
, RTFSkipGroup
);
1680 RTFSetDestinationCallback(&parser
, rtfNestTableProps
, RTFReadGroup
);
1684 /* do the parsing */
1686 RTFFlushOutputBuffer(&parser
);
1687 if (!editor
->bEmulateVersion10
) { /* v4.1 */
1688 if (parser
.tableDef
&& parser
.tableDef
->tableRowStart
&&
1689 (parser
.nestingLevel
> 0 || parser
.canInheritInTbl
))
1691 /* Delete any incomplete table row at the end of the rich text. */
1693 ME_DisplayItem
*para
;
1695 parser
.rtfMinor
= rtfRow
;
1696 /* Complete the table row before deleting it.
1697 * By doing it this way we will have the current paragraph format set
1698 * properly to reflect that is not in the complete table, and undo items
1699 * will be added for this change to the current paragraph format. */
1700 if (parser
.nestingLevel
> 0)
1702 while (parser
.nestingLevel
> 1)
1703 ME_RTFSpecialCharHook(&parser
); /* Decrements nestingLevel */
1704 para
= parser
.tableDef
->tableRowStart
;
1705 ME_RTFSpecialCharHook(&parser
);
1707 para
= parser
.tableDef
->tableRowStart
;
1708 ME_RTFSpecialCharHook(&parser
);
1709 assert(para
->member
.para
.nFlags
& MEPF_ROWEND
);
1710 para
= para
->member
.para
.next_para
;
1713 editor
->pCursors
[1].pPara
= para
;
1714 editor
->pCursors
[1].pRun
= ME_FindItemFwd(para
, diRun
);
1715 editor
->pCursors
[1].nOffset
= 0;
1716 nOfs
= ME_GetCursorOfs(&editor
->pCursors
[1]);
1717 nChars
= ME_GetCursorOfs(&editor
->pCursors
[0]) - nOfs
;
1718 ME_InternalDeleteText(editor
, &editor
->pCursors
[1], nChars
, TRUE
);
1719 if (parser
.tableDef
)
1720 parser
.tableDef
->tableRowStart
= NULL
;
1723 ME_CheckTablesForCorruption(editor
);
1724 RTFDestroy(&parser
);
1726 if (parser
.stackTop
> 0)
1728 while (--parser
.stackTop
>= 0)
1730 ME_ReleaseStyle(parser
.style
);
1731 parser
.style
= parser
.stack
[parser
.stackTop
].style
;
1733 if (!inStream
.editstream
->dwError
)
1734 inStream
.editstream
->dwError
= HRESULT_FROM_WIN32(ERROR_HANDLE_EOF
);
1737 /* Remove last line break, as mandated by tests. This is not affected by
1738 CR/LF counters, since RTF streaming presents only \para tokens, which
1739 are converted according to the standard rules: \r for 2.0, \r\n for 1.0
1741 if (stripLastCR
&& !(format
& SFF_SELECTION
)) {
1743 ME_GetSelection(editor
, &selStart
, &selEnd
);
1744 newto
= ME_GetCursorOfs(selEnd
);
1745 if (newto
> to
+ (editor
->bEmulateVersion10
? 1 : 0)) {
1746 WCHAR lastchar
[3] = {'\0', '\0'};
1747 int linebreakSize
= editor
->bEmulateVersion10
? 2 : 1;
1748 ME_Cursor linebreakCursor
= *selEnd
, lastcharCursor
= *selEnd
;
1751 /* Set the final eop to the char fmt of the last char */
1752 cf
.cbSize
= sizeof(cf
);
1753 cf
.dwMask
= CFM_ALL2
;
1754 ME_MoveCursorChars(editor
, &lastcharCursor
, -1, FALSE
);
1755 ME_GetCharFormat(editor
, &lastcharCursor
, &linebreakCursor
, &cf
);
1756 ME_SetSelection(editor
, newto
, -1);
1757 ME_SetSelectionCharFormat(editor
, &cf
);
1758 ME_SetSelection(editor
, newto
, newto
);
1760 ME_MoveCursorChars(editor
, &linebreakCursor
, -linebreakSize
, FALSE
);
1761 ME_GetTextW(editor
, lastchar
, 2, &linebreakCursor
, linebreakSize
, FALSE
, FALSE
);
1762 if (lastchar
[0] == '\r' && (lastchar
[1] == '\n' || lastchar
[1] == '\0')) {
1763 ME_InternalDeleteText(editor
, &linebreakCursor
, linebreakSize
, FALSE
);
1767 to
= ME_GetCursorOfs(&editor
->pCursors
[0]);
1768 num_read
= to
- from
;
1770 style
= parser
.style
;
1772 else if (format
& SF_TEXT
)
1774 num_read
= ME_StreamInText(editor
, format
, &inStream
, style
);
1775 to
= ME_GetCursorOfs(&editor
->pCursors
[0]);
1778 ERR("EM_STREAMIN without SF_TEXT or SF_RTF\n");
1779 /* put the cursor at the top */
1780 if (!(format
& SFF_SELECTION
))
1781 ME_SetSelection(editor
, 0, 0);
1782 ME_CursorFromCharOfs(editor
, from
, &start
);
1783 ME_UpdateLinkAttribute(editor
, &start
, to
- from
);
1786 /* Restore saved undo mode */
1787 editor
->nUndoMode
= nUndoMode
;
1789 /* even if we didn't add an undo, we need to commit anything on the stack */
1790 ME_CommitUndo(editor
);
1792 /* If SFF_SELECTION isn't set, delete any undos from before we started too */
1793 if (!(format
& SFF_SELECTION
))
1794 ME_EmptyUndoStack(editor
);
1796 ME_ReleaseStyle(style
);
1797 editor
->nEventMask
= nEventMask
;
1798 ME_UpdateRepaint(editor
, FALSE
);
1799 if (!(format
& SFF_SELECTION
)) {
1800 ME_ClearTempStyle(editor
);
1802 ITextHost_TxShowCaret(editor
->texthost
, FALSE
);
1803 ME_MoveCaret(editor
);
1804 ITextHost_TxShowCaret(editor
->texthost
, TRUE
);
1805 ME_SendSelChange(editor
);
1806 ME_SendRequestResize(editor
, FALSE
);
1812 typedef struct tagME_RTFStringStreamStruct
1817 } ME_RTFStringStreamStruct
;
1819 static DWORD CALLBACK
ME_ReadFromRTFString(DWORD_PTR dwCookie
, LPBYTE lpBuff
, LONG cb
, LONG
*pcb
)
1821 ME_RTFStringStreamStruct
*pStruct
= (ME_RTFStringStreamStruct
*)dwCookie
;
1824 count
= min(cb
, pStruct
->length
- pStruct
->pos
);
1825 memmove(lpBuff
, pStruct
->string
+ pStruct
->pos
, count
);
1826 pStruct
->pos
+= count
;
1832 ME_StreamInRTFString(ME_TextEditor
*editor
, BOOL selection
, char *string
)
1835 ME_RTFStringStreamStruct data
;
1837 data
.string
= string
;
1838 data
.length
= strlen(string
);
1840 es
.dwCookie
= (DWORD_PTR
)&data
;
1841 es
.pfnCallback
= ME_ReadFromRTFString
;
1842 ME_StreamIn(editor
, SF_RTF
| (selection
? SFF_SELECTION
: 0), &es
, TRUE
);
1847 ME_FindText(ME_TextEditor
*editor
, DWORD flags
, const CHARRANGE
*chrg
, const WCHAR
*text
, CHARRANGE
*chrgText
)
1849 const int nLen
= lstrlenW(text
);
1850 const int nTextLen
= ME_GetTextLength(editor
);
1853 WCHAR wLastChar
= ' ';
1855 TRACE("flags==0x%08x, chrg->cpMin==%d, chrg->cpMax==%d text==%s\n",
1856 flags
, chrg
->cpMin
, chrg
->cpMax
, debugstr_w(text
));
1858 if (flags
& ~(FR_DOWN
| FR_MATCHCASE
| FR_WHOLEWORD
))
1859 FIXME("Flags 0x%08x not implemented\n",
1860 flags
& ~(FR_DOWN
| FR_MATCHCASE
| FR_WHOLEWORD
));
1863 if (chrg
->cpMax
== -1)
1866 nMax
= chrg
->cpMax
> nTextLen
? nTextLen
: chrg
->cpMax
;
1868 /* In 1.0 emulation, if cpMax reaches end of text, add the FR_DOWN flag */
1869 if (editor
->bEmulateVersion10
&& nMax
== nTextLen
)
1874 /* In 1.0 emulation, cpMin must always be no greater than cpMax */
1875 if (editor
->bEmulateVersion10
&& nMax
< nMin
)
1879 chrgText
->cpMin
= -1;
1880 chrgText
->cpMax
= -1;
1885 /* when searching up, if cpMin < cpMax, then instead of searching
1886 * on [cpMin,cpMax], we search on [0,cpMin], otherwise, search on
1887 * [cpMax, cpMin]. The exception is when cpMax is -1, in which
1888 * case, it is always bigger than cpMin.
1890 if (!editor
->bEmulateVersion10
&& !(flags
& FR_DOWN
))
1894 nMax
= nMin
> nTextLen
? nTextLen
: nMin
;
1895 if (nMin
< nSwap
|| chrg
->cpMax
== -1)
1901 if (!nLen
|| nMin
< 0 || nMax
< 0 || nMax
< nMin
)
1904 chrgText
->cpMin
= chrgText
->cpMax
= -1;
1908 if (flags
& FR_DOWN
) /* Forward search */
1910 /* If possible, find the character before where the search starts */
1911 if ((flags
& FR_WHOLEWORD
) && nMin
)
1913 ME_CursorFromCharOfs(editor
, nMin
- 1, &cursor
);
1914 wLastChar
= *get_text( &cursor
.pRun
->member
.run
, cursor
.nOffset
);
1915 ME_MoveCursorChars(editor
, &cursor
, 1, FALSE
);
1917 ME_CursorFromCharOfs(editor
, nMin
, &cursor
);
1920 while (cursor
.pRun
&& ME_GetCursorOfs(&cursor
) + nLen
<= nMax
)
1922 ME_DisplayItem
*pCurItem
= cursor
.pRun
;
1923 int nCurStart
= cursor
.nOffset
;
1926 while (pCurItem
&& ME_CharCompare( *get_text( &pCurItem
->member
.run
, nCurStart
+ nMatched
), text
[nMatched
], (flags
& FR_MATCHCASE
)))
1928 if ((flags
& FR_WHOLEWORD
) && isalnumW(wLastChar
))
1932 if (nMatched
== nLen
)
1934 ME_DisplayItem
*pNextItem
= pCurItem
;
1935 int nNextStart
= nCurStart
;
1938 /* Check to see if next character is a whitespace */
1939 if (flags
& FR_WHOLEWORD
)
1941 if (nCurStart
+ nMatched
== pCurItem
->member
.run
.len
)
1943 pNextItem
= ME_FindItemFwd(pCurItem
, diRun
);
1944 nNextStart
= -nMatched
;
1948 wNextChar
= *get_text( &pNextItem
->member
.run
, nNextStart
+ nMatched
);
1952 if (isalnumW(wNextChar
))
1956 cursor
.nOffset
+= cursor
.pPara
->member
.para
.nCharOfs
+ cursor
.pRun
->member
.run
.nCharOfs
;
1959 chrgText
->cpMin
= cursor
.nOffset
;
1960 chrgText
->cpMax
= cursor
.nOffset
+ nLen
;
1962 TRACE("found at %d-%d\n", cursor
.nOffset
, cursor
.nOffset
+ nLen
);
1963 return cursor
.nOffset
;
1965 if (nCurStart
+ nMatched
== pCurItem
->member
.run
.len
)
1967 pCurItem
= ME_FindItemFwd(pCurItem
, diRun
);
1968 nCurStart
= -nMatched
;
1972 wLastChar
= *get_text( &pCurItem
->member
.run
, nCurStart
+ nMatched
);
1977 if (cursor
.nOffset
== cursor
.pRun
->member
.run
.len
)
1979 ME_NextRun(&cursor
.pPara
, &cursor
.pRun
, TRUE
);
1984 else /* Backward search */
1986 /* If possible, find the character after where the search ends */
1987 if ((flags
& FR_WHOLEWORD
) && nMax
< nTextLen
- 1)
1989 ME_CursorFromCharOfs(editor
, nMax
+ 1, &cursor
);
1990 wLastChar
= *get_text( &cursor
.pRun
->member
.run
, cursor
.nOffset
);
1991 ME_MoveCursorChars(editor
, &cursor
, -1, FALSE
);
1993 ME_CursorFromCharOfs(editor
, nMax
, &cursor
);
1996 while (cursor
.pRun
&& ME_GetCursorOfs(&cursor
) - nLen
>= nMin
)
1998 ME_DisplayItem
*pCurItem
= cursor
.pRun
;
1999 ME_DisplayItem
*pCurPara
= cursor
.pPara
;
2000 int nCurEnd
= cursor
.nOffset
;
2005 ME_PrevRun(&pCurPara
, &pCurItem
, TRUE
);
2006 nCurEnd
= pCurItem
->member
.run
.len
;
2009 while (pCurItem
&& ME_CharCompare( *get_text( &pCurItem
->member
.run
, nCurEnd
- nMatched
- 1 ),
2010 text
[nLen
- nMatched
- 1], (flags
& FR_MATCHCASE
) ))
2012 if ((flags
& FR_WHOLEWORD
) && isalnumW(wLastChar
))
2016 if (nMatched
== nLen
)
2018 ME_DisplayItem
*pPrevItem
= pCurItem
;
2019 int nPrevEnd
= nCurEnd
;
2023 /* Check to see if previous character is a whitespace */
2024 if (flags
& FR_WHOLEWORD
)
2026 if (nPrevEnd
- nMatched
== 0)
2028 pPrevItem
= ME_FindItemBack(pCurItem
, diRun
);
2030 nPrevEnd
= pPrevItem
->member
.run
.len
+ nMatched
;
2034 wPrevChar
= *get_text( &pPrevItem
->member
.run
, nPrevEnd
- nMatched
- 1 );
2038 if (isalnumW(wPrevChar
))
2042 nStart
= pCurPara
->member
.para
.nCharOfs
2043 + pCurItem
->member
.run
.nCharOfs
+ nCurEnd
- nMatched
;
2046 chrgText
->cpMin
= nStart
;
2047 chrgText
->cpMax
= nStart
+ nLen
;
2049 TRACE("found at %d-%d\n", nStart
, nStart
+ nLen
);
2052 if (nCurEnd
- nMatched
== 0)
2054 ME_PrevRun(&pCurPara
, &pCurItem
, TRUE
);
2055 /* Don't care about pCurItem becoming NULL here; it's already taken
2056 * care of in the exterior loop condition */
2057 nCurEnd
= pCurItem
->member
.run
.len
+ nMatched
;
2061 wLastChar
= *get_text( &pCurItem
->member
.run
, nCurEnd
- nMatched
- 1 );
2066 if (cursor
.nOffset
< 0)
2068 ME_PrevRun(&cursor
.pPara
, &cursor
.pRun
, TRUE
);
2069 cursor
.nOffset
= cursor
.pRun
->member
.run
.len
;
2073 TRACE("not found\n");
2075 chrgText
->cpMin
= chrgText
->cpMax
= -1;
2079 static int ME_GetTextEx(ME_TextEditor
*editor
, GETTEXTEX
*ex
, LPARAM pText
)
2084 if (!ex
->cb
|| !pText
) return 0;
2086 if (ex
->flags
& ~(GT_SELECTION
| GT_USECRLF
))
2087 FIXME("GETTEXTEX flags 0x%08x not supported\n", ex
->flags
& ~(GT_SELECTION
| GT_USECRLF
));
2089 if (ex
->flags
& GT_SELECTION
)
2092 int nStartCur
= ME_GetSelectionOfs(editor
, &from
, &to
);
2093 start
= editor
->pCursors
[nStartCur
];
2098 ME_SetCursorToStart(editor
, &start
);
2101 if (ex
->codepage
== CP_UNICODE
)
2103 return ME_GetTextW(editor
, (LPWSTR
)pText
, ex
->cb
/ sizeof(WCHAR
) - 1,
2104 &start
, nChars
, ex
->flags
& GT_USECRLF
, FALSE
);
2108 /* potentially each char may be a CR, why calculate the exact value with O(N) when
2109 we can just take a bigger buffer? :)
2110 The above assumption still holds with CR/LF counters, since CR->CRLF expansion
2111 occurs only in richedit 2.0 mode, in which line breaks have only one CR
2113 int crlfmul
= (ex
->flags
& GT_USECRLF
) ? 2 : 1;
2118 buflen
= min(crlfmul
* nChars
, ex
->cb
- 1);
2119 buffer
= heap_alloc((buflen
+ 1) * sizeof(WCHAR
));
2121 nChars
= ME_GetTextW(editor
, buffer
, buflen
, &start
, nChars
, ex
->flags
& GT_USECRLF
, FALSE
);
2122 rc
= WideCharToMultiByte(ex
->codepage
, 0, buffer
, nChars
+ 1,
2123 (LPSTR
)pText
, ex
->cb
, ex
->lpDefaultChar
, ex
->lpUsedDefChar
);
2124 if (rc
) rc
--; /* do not count 0 terminator */
2131 static int ME_GetTextRange(ME_TextEditor
*editor
, WCHAR
*strText
,
2132 const ME_Cursor
*start
, int nLen
, BOOL unicode
)
2134 if (!strText
) return 0;
2136 return ME_GetTextW(editor
, strText
, INT_MAX
, start
, nLen
, FALSE
, FALSE
);
2139 WCHAR
*p
= heap_alloc((nLen
+1) * sizeof(*p
));
2141 nChars
= ME_GetTextW(editor
, p
, nLen
, start
, nLen
, FALSE
, FALSE
);
2142 WideCharToMultiByte(CP_ACP
, 0, p
, nChars
+1, (char *)strText
,
2143 nLen
+1, NULL
, NULL
);
2149 static int handle_EM_EXSETSEL( ME_TextEditor
*editor
, int to
, int from
)
2153 TRACE("%d - %d\n", to
, from
);
2155 ME_InvalidateSelection( editor
);
2156 end
= ME_SetSelection( editor
, to
, from
);
2157 ME_InvalidateSelection( editor
);
2158 ITextHost_TxShowCaret( editor
->texthost
, FALSE
);
2159 ME_ShowCaret( editor
);
2160 ME_SendSelChange( editor
);
2165 typedef struct tagME_GlobalDestStruct
2169 } ME_GlobalDestStruct
;
2171 static DWORD CALLBACK
ME_ReadFromHGLOBALUnicode(DWORD_PTR dwCookie
, LPBYTE lpBuff
, LONG cb
, LONG
*pcb
)
2173 ME_GlobalDestStruct
*pData
= (ME_GlobalDestStruct
*)dwCookie
;
2178 pDest
= (WORD
*)lpBuff
;
2179 pSrc
= GlobalLock(pData
->hData
);
2180 for (i
= 0; i
<cb
&& pSrc
[pData
->nLength
+i
]; i
++) {
2181 pDest
[i
] = pSrc
[pData
->nLength
+i
];
2183 pData
->nLength
+= i
;
2185 GlobalUnlock(pData
->hData
);
2189 static DWORD CALLBACK
ME_ReadFromHGLOBALRTF(DWORD_PTR dwCookie
, LPBYTE lpBuff
, LONG cb
, LONG
*pcb
)
2191 ME_GlobalDestStruct
*pData
= (ME_GlobalDestStruct
*)dwCookie
;
2196 pSrc
= GlobalLock(pData
->hData
);
2197 for (i
= 0; i
<cb
&& pSrc
[pData
->nLength
+i
]; i
++) {
2198 pDest
[i
] = pSrc
[pData
->nLength
+i
];
2200 pData
->nLength
+= i
;
2202 GlobalUnlock(pData
->hData
);
2206 static const WCHAR rtfW
[] = {'R','i','c','h',' ','T','e','x','t',' ','F','o','r','m','a','t',0};
2208 static HRESULT
paste_rtf(ME_TextEditor
*editor
, FORMATETC
*fmt
, STGMEDIUM
*med
)
2211 ME_GlobalDestStruct gds
;
2214 gds
.hData
= med
->u
.hGlobal
;
2216 es
.dwCookie
= (DWORD_PTR
)&gds
;
2217 es
.pfnCallback
= ME_ReadFromHGLOBALRTF
;
2218 hr
= ME_StreamIn( editor
, SF_RTF
| SFF_SELECTION
, &es
, FALSE
) == 0 ? E_FAIL
: S_OK
;
2219 ReleaseStgMedium( med
);
2223 static HRESULT
paste_text(ME_TextEditor
*editor
, FORMATETC
*fmt
, STGMEDIUM
*med
)
2226 ME_GlobalDestStruct gds
;
2229 gds
.hData
= med
->u
.hGlobal
;
2231 es
.dwCookie
= (DWORD_PTR
)&gds
;
2232 es
.pfnCallback
= ME_ReadFromHGLOBALUnicode
;
2233 hr
= ME_StreamIn( editor
, SF_TEXT
| SF_UNICODE
| SFF_SELECTION
, &es
, FALSE
) == 0 ? E_FAIL
: S_OK
;
2234 ReleaseStgMedium( med
);
2238 static HRESULT
paste_emf(ME_TextEditor
*editor
, FORMATETC
*fmt
, STGMEDIUM
*med
)
2243 hr
= insert_static_object( editor
, med
->u
.hEnhMetaFile
, NULL
, &sz
);
2246 ME_CommitUndo( editor
);
2247 ME_UpdateRepaint( editor
, FALSE
);
2250 ReleaseStgMedium( med
);
2255 static struct paste_format
2258 HRESULT (*paste
)(ME_TextEditor
*, FORMATETC
*, STGMEDIUM
*);
2262 {{ -1, NULL
, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
}, paste_rtf
, rtfW
},
2263 {{ CF_UNICODETEXT
, NULL
, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
}, paste_text
},
2264 {{ CF_ENHMETAFILE
, NULL
, DVASPECT_CONTENT
, -1, TYMED_ENHMF
}, paste_emf
},
2268 static void init_paste_formats(void)
2270 struct paste_format
*format
;
2275 for (format
= paste_formats
; format
->fmt
.cfFormat
; format
++)
2278 format
->fmt
.cfFormat
= RegisterClipboardFormatW( format
->name
);
2284 static BOOL
paste_special(ME_TextEditor
*editor
, UINT cf
, REPASTESPECIAL
*ps
, BOOL check_only
)
2288 struct paste_format
*format
;
2291 /* Protect read-only edit control from modification */
2292 if (editor
->styleFlags
& ES_READONLY
)
2295 MessageBeep(MB_ICONERROR
);
2299 init_paste_formats();
2301 if (ps
&& ps
->dwAspect
!= DVASPECT_CONTENT
)
2302 FIXME("Ignoring aspect %x\n", ps
->dwAspect
);
2304 hr
= OleGetClipboard( &data
);
2305 if (hr
!= S_OK
) return FALSE
;
2307 if (cf
== CF_TEXT
) cf
= CF_UNICODETEXT
;
2310 for (format
= paste_formats
; format
->fmt
.cfFormat
; format
++)
2312 if (cf
&& cf
!= format
->fmt
.cfFormat
) continue;
2313 hr
= IDataObject_QueryGetData( data
, &format
->fmt
);
2318 hr
= IDataObject_GetData( data
, &format
->fmt
, &med
);
2319 if (hr
!= S_OK
) goto done
;
2320 hr
= format
->paste( editor
, &format
->fmt
, &med
);
2327 IDataObject_Release( data
);
2332 static BOOL
ME_Copy(ME_TextEditor
*editor
, const ME_Cursor
*start
, int nChars
)
2334 LPDATAOBJECT dataObj
= NULL
;
2337 if (editor
->cPasswordMask
)
2338 return FALSE
; /* Copying or Cutting masked text isn't allowed */
2340 if(editor
->lpOleCallback
)
2343 range
.cpMin
= ME_GetCursorOfs(start
);
2344 range
.cpMax
= range
.cpMin
+ nChars
;
2345 hr
= IRichEditOleCallback_GetClipboardData(editor
->lpOleCallback
, &range
, RECO_COPY
, &dataObj
);
2347 if(FAILED(hr
) || !dataObj
)
2348 hr
= ME_GetDataObject(editor
, start
, nChars
, &dataObj
);
2350 hr
= OleSetClipboard(dataObj
);
2351 IDataObject_Release(dataObj
);
2353 return SUCCEEDED(hr
);
2356 static BOOL
copy_or_cut(ME_TextEditor
*editor
, BOOL cut
)
2359 int offs
, num_chars
;
2360 int start_cursor
= ME_GetSelectionOfs(editor
, &offs
, &num_chars
);
2361 ME_Cursor
*sel_start
= &editor
->pCursors
[start_cursor
];
2363 if (cut
&& (editor
->styleFlags
& ES_READONLY
))
2365 MessageBeep(MB_ICONERROR
);
2370 result
= ME_Copy(editor
, sel_start
, num_chars
);
2373 ME_InternalDeleteText(editor
, sel_start
, num_chars
, FALSE
);
2374 ME_CommitUndo(editor
);
2375 ME_UpdateRepaint(editor
, TRUE
);
2380 /* helper to send a msg filter notification */
2382 ME_FilterEvent(ME_TextEditor
*editor
, UINT msg
, WPARAM
* wParam
, LPARAM
* lParam
)
2386 if (!editor
->hWnd
|| !editor
->hwndParent
) return FALSE
;
2387 msgf
.nmhdr
.hwndFrom
= editor
->hWnd
;
2388 msgf
.nmhdr
.idFrom
= GetWindowLongW(editor
->hWnd
, GWLP_ID
);
2389 msgf
.nmhdr
.code
= EN_MSGFILTER
;
2391 msgf
.wParam
= *wParam
;
2392 msgf
.lParam
= *lParam
;
2393 if (SendMessageW(editor
->hwndParent
, WM_NOTIFY
, msgf
.nmhdr
.idFrom
, (LPARAM
)&msgf
))
2395 *wParam
= msgf
.wParam
;
2396 *lParam
= msgf
.lParam
;
2397 msgf
.wParam
= *wParam
;
2402 static void ME_UpdateSelectionLinkAttribute(ME_TextEditor
*editor
)
2404 ME_DisplayItem
*startPara
, *endPara
;
2405 ME_DisplayItem
*prev_para
;
2406 ME_Cursor
*from
, *to
;
2410 if (!editor
->AutoURLDetect_bEnable
) return;
2412 ME_GetSelection(editor
, &from
, &to
);
2414 /* Find paragraph previous to the one that contains start cursor */
2415 startPara
= from
->pPara
;
2416 prev_para
= startPara
->member
.para
.prev_para
;
2417 if (prev_para
->type
== diParagraph
) startPara
= prev_para
;
2419 /* Find paragraph that contains end cursor */
2420 endPara
= to
->pPara
->member
.para
.next_para
;
2422 start
.pPara
= startPara
;
2423 start
.pRun
= ME_FindItemFwd(startPara
, diRun
);
2425 nChars
= endPara
->member
.para
.nCharOfs
- startPara
->member
.para
.nCharOfs
;
2427 ME_UpdateLinkAttribute(editor
, &start
, nChars
);
2431 ME_KeyDown(ME_TextEditor
*editor
, WORD nKey
)
2433 BOOL ctrl_is_down
= GetKeyState(VK_CONTROL
) & 0x8000;
2434 BOOL shift_is_down
= GetKeyState(VK_SHIFT
) & 0x8000;
2436 if (editor
->bMouseCaptured
)
2438 if (nKey
!= VK_SHIFT
&& nKey
!= VK_CONTROL
&& nKey
!= VK_MENU
)
2439 editor
->nSelectionType
= stPosition
;
2447 editor
->nUDArrowX
= -1;
2453 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
2454 ME_ArrowKey(editor
, nKey
, shift_is_down
, ctrl_is_down
);
2458 editor
->nUDArrowX
= -1;
2459 /* FIXME backspace and delete aren't the same, they act different wrt paragraph style of the merged paragraph */
2460 if (editor
->styleFlags
& ES_READONLY
)
2462 if (ME_IsSelection(editor
))
2464 ME_DeleteSelection(editor
);
2465 ME_CommitUndo(editor
);
2467 else if (nKey
== VK_DELETE
)
2469 /* Delete stops group typing.
2470 * (See MSDN remarks on EM_STOPGROUPTYPING message) */
2471 ME_DeleteTextAtCursor(editor
, 1, 1);
2472 ME_CommitUndo(editor
);
2474 else if (ME_ArrowKey(editor
, VK_LEFT
, FALSE
, FALSE
))
2476 BOOL bDeletionSucceeded
;
2477 /* Backspace can be grouped for a single undo */
2478 ME_ContinueCoalescingTransaction(editor
);
2479 bDeletionSucceeded
= ME_DeleteTextAtCursor(editor
, 1, 1);
2480 if (!bDeletionSucceeded
&& !editor
->bEmulateVersion10
) { /* v4.1 */
2481 /* Deletion was prevented so the cursor is moved back to where it was.
2482 * (e.g. this happens when trying to delete cell boundaries)
2484 ME_ArrowKey(editor
, VK_RIGHT
, FALSE
, FALSE
);
2486 ME_CommitCoalescingUndo(editor
);
2490 ME_MoveCursorFromTableRowStartParagraph(editor
);
2491 ME_UpdateSelectionLinkAttribute(editor
);
2492 ME_UpdateRepaint(editor
, FALSE
);
2493 ME_SendRequestResize(editor
, FALSE
);
2496 if (editor
->bDialogMode
)
2501 if (!(editor
->styleFlags
& ES_WANTRETURN
))
2503 if (editor
->hwndParent
)
2506 dw
= SendMessageW(editor
->hwndParent
, DM_GETDEFID
, 0, 0);
2507 if (HIWORD(dw
) == DC_HASDEFID
)
2509 HWND hwDefCtrl
= GetDlgItem(editor
->hwndParent
, LOWORD(dw
));
2512 SendMessageW(editor
->hwndParent
, WM_NEXTDLGCTL
, (WPARAM
)hwDefCtrl
, TRUE
);
2513 PostMessageW(hwDefCtrl
, WM_KEYDOWN
, VK_RETURN
, 0);
2521 if (editor
->styleFlags
& ES_MULTILINE
)
2523 ME_Cursor cursor
= editor
->pCursors
[0];
2524 ME_DisplayItem
*para
= cursor
.pPara
;
2526 const WCHAR endl
= '\r';
2527 const WCHAR endlv10
[] = {'\r','\n'};
2528 ME_Style
*style
, *eop_style
;
2530 if (editor
->styleFlags
& ES_READONLY
) {
2531 MessageBeep(MB_ICONERROR
);
2535 ME_GetSelectionOfs(editor
, &from
, &to
);
2536 if (editor
->nTextLimit
> ME_GetTextLength(editor
) - (to
-from
))
2538 if (!editor
->bEmulateVersion10
) { /* v4.1 */
2539 if (para
->member
.para
.nFlags
& MEPF_ROWEND
) {
2540 /* Add a new table row after this row. */
2541 para
= ME_AppendTableRow(editor
, para
);
2542 para
= para
->member
.para
.next_para
;
2543 editor
->pCursors
[0].pPara
= para
;
2544 editor
->pCursors
[0].pRun
= ME_FindItemFwd(para
, diRun
);
2545 editor
->pCursors
[0].nOffset
= 0;
2546 editor
->pCursors
[1] = editor
->pCursors
[0];
2547 ME_CommitUndo(editor
);
2548 ME_CheckTablesForCorruption(editor
);
2549 ME_UpdateRepaint(editor
, FALSE
);
2552 else if (para
== editor
->pCursors
[1].pPara
&&
2553 cursor
.nOffset
+ cursor
.pRun
->member
.run
.nCharOfs
== 0 &&
2554 para
->member
.para
.prev_para
->member
.para
.nFlags
& MEPF_ROWSTART
&&
2555 !para
->member
.para
.prev_para
->member
.para
.nCharOfs
)
2557 /* Insert a newline before the table. */
2558 para
= para
->member
.para
.prev_para
;
2559 para
->member
.para
.nFlags
&= ~MEPF_ROWSTART
;
2560 editor
->pCursors
[0].pPara
= para
;
2561 editor
->pCursors
[0].pRun
= ME_FindItemFwd(para
, diRun
);
2562 editor
->pCursors
[1] = editor
->pCursors
[0];
2563 ME_InsertTextFromCursor(editor
, 0, &endl
, 1,
2564 editor
->pCursors
[0].pRun
->member
.run
.style
);
2565 para
= editor
->pBuffer
->pFirst
->member
.para
.next_para
;
2566 ME_SetDefaultParaFormat(editor
, ¶
->member
.para
.fmt
);
2567 para
->member
.para
.nFlags
= MEPF_REWRAP
;
2568 editor
->pCursors
[0].pPara
= para
;
2569 editor
->pCursors
[0].pRun
= ME_FindItemFwd(para
, diRun
);
2570 editor
->pCursors
[1] = editor
->pCursors
[0];
2571 para
->member
.para
.next_para
->member
.para
.nFlags
|= MEPF_ROWSTART
;
2572 ME_CommitCoalescingUndo(editor
);
2573 ME_CheckTablesForCorruption(editor
);
2574 ME_UpdateRepaint(editor
, FALSE
);
2577 } else { /* v1.0 - 3.0 */
2578 ME_DisplayItem
*para
= cursor
.pPara
;
2579 if (ME_IsInTable(para
))
2581 if (cursor
.pRun
->member
.run
.nFlags
& MERF_ENDPARA
)
2584 ME_ContinueCoalescingTransaction(editor
);
2585 para
= ME_AppendTableRow(editor
, para
);
2586 editor
->pCursors
[0].pPara
= para
;
2587 editor
->pCursors
[0].pRun
= ME_FindItemFwd(para
, diRun
);
2588 editor
->pCursors
[0].nOffset
= 0;
2589 editor
->pCursors
[1] = editor
->pCursors
[0];
2590 ME_CommitCoalescingUndo(editor
);
2591 ME_UpdateRepaint(editor
, FALSE
);
2595 ME_ContinueCoalescingTransaction(editor
);
2596 if (cursor
.pRun
->member
.run
.nCharOfs
+ cursor
.nOffset
== 0 &&
2597 !ME_IsInTable(para
->member
.para
.prev_para
))
2599 /* Insert newline before table */
2600 cursor
.pRun
= ME_FindItemBack(para
, diRun
);
2602 editor
->pCursors
[0].pRun
= cursor
.pRun
;
2603 editor
->pCursors
[0].pPara
= para
->member
.para
.prev_para
;
2605 editor
->pCursors
[0].nOffset
= 0;
2606 editor
->pCursors
[1] = editor
->pCursors
[0];
2607 ME_InsertTextFromCursor(editor
, 0, &endl
, 1,
2608 editor
->pCursors
[0].pRun
->member
.run
.style
);
2610 editor
->pCursors
[1] = editor
->pCursors
[0];
2611 para
= ME_AppendTableRow(editor
, para
);
2612 editor
->pCursors
[0].pPara
= para
;
2613 editor
->pCursors
[0].pRun
= ME_FindItemFwd(para
, diRun
);
2614 editor
->pCursors
[0].nOffset
= 0;
2615 editor
->pCursors
[1] = editor
->pCursors
[0];
2617 ME_CommitCoalescingUndo(editor
);
2618 ME_UpdateRepaint(editor
, FALSE
);
2624 style
= ME_GetInsertStyle(editor
, 0);
2626 /* Normally the new eop style is the insert style, however in a list it is copied from the existing
2627 eop style (this prevents the list label style changing when the new eop is inserted).
2628 No extra ref is taken here on eop_style. */
2629 if (para
->member
.para
.fmt
.wNumbering
)
2630 eop_style
= para
->member
.para
.eop_run
->style
;
2633 ME_ContinueCoalescingTransaction(editor
);
2635 ME_InsertEndRowFromCursor(editor
, 0);
2637 if (!editor
->bEmulateVersion10
)
2638 ME_InsertTextFromCursor(editor
, 0, &endl
, 1, eop_style
);
2640 ME_InsertTextFromCursor(editor
, 0, endlv10
, 2, eop_style
);
2641 ME_CommitCoalescingUndo(editor
);
2644 ME_UpdateSelectionLinkAttribute(editor
);
2645 ME_UpdateRepaint(editor
, FALSE
);
2646 ME_SaveTempStyle(editor
, style
); /* set the temp insert style for the new para */
2647 ME_ReleaseStyle(style
);
2653 if (editor
->bDialogMode
&& editor
->hwndParent
)
2654 PostMessageW(editor
->hwndParent
, WM_CLOSE
, 0, 0);
2657 if (editor
->bDialogMode
&& editor
->hwndParent
)
2658 SendMessageW(editor
->hwndParent
, WM_NEXTDLGCTL
, shift_is_down
, 0);
2663 handle_EM_EXSETSEL( editor
, 0, -1 );
2669 return paste_special( editor
, 0, NULL
, FALSE
);
2674 return copy_or_cut(editor
, nKey
== 'X');
2692 if (nKey
!= VK_SHIFT
&& nKey
!= VK_CONTROL
&& nKey
&& nKey
!= VK_MENU
)
2693 editor
->nUDArrowX
= -1;
2700 chf
.cbSize
= sizeof(chf
);
2702 ME_GetSelectionCharFormat(editor
, &chf
);
2703 ME_DumpStyleToBuf(&chf
, buf
);
2704 MessageBoxA(NULL
, buf
, "Style dump", MB_OK
);
2708 ME_CheckCharOffsets(editor
);
2715 static LRESULT
ME_Char(ME_TextEditor
*editor
, WPARAM charCode
,
2716 LPARAM flags
, BOOL unicode
)
2720 if (editor
->bMouseCaptured
)
2724 wstr
= (WCHAR
)charCode
;
2727 CHAR charA
= charCode
;
2728 MultiByteToWideChar(CP_ACP
, 0, &charA
, 1, &wstr
, 1);
2731 if (editor
->styleFlags
& ES_READONLY
) {
2732 MessageBeep(MB_ICONERROR
);
2733 return 0; /* FIXME really 0 ? */
2736 if ((unsigned)wstr
>= ' ' || wstr
== '\t')
2738 ME_Cursor cursor
= editor
->pCursors
[0];
2739 ME_DisplayItem
*para
= cursor
.pPara
;
2741 BOOL ctrl_is_down
= GetKeyState(VK_CONTROL
) & 0x8000;
2742 ME_GetSelectionOfs(editor
, &from
, &to
);
2744 /* v4.1 allows tabs to be inserted with ctrl key down */
2745 !(ctrl_is_down
&& !editor
->bEmulateVersion10
))
2747 ME_DisplayItem
*para
;
2748 BOOL bSelectedRow
= FALSE
;
2750 para
= cursor
.pPara
;
2751 if (ME_IsSelection(editor
) &&
2752 cursor
.pRun
->member
.run
.nCharOfs
+ cursor
.nOffset
== 0 &&
2753 to
== ME_GetCursorOfs(&editor
->pCursors
[0]) &&
2754 para
->member
.para
.prev_para
->type
== diParagraph
)
2756 para
= para
->member
.para
.prev_para
;
2757 bSelectedRow
= TRUE
;
2759 if (ME_IsInTable(para
))
2761 ME_TabPressedInTable(editor
, bSelectedRow
);
2762 ME_CommitUndo(editor
);
2765 } else if (!editor
->bEmulateVersion10
) { /* v4.1 */
2766 if (para
->member
.para
.nFlags
& MEPF_ROWEND
) {
2768 para
= para
->member
.para
.next_para
;
2769 if (para
->member
.para
.nFlags
& MEPF_ROWSTART
)
2770 para
= para
->member
.para
.next_para
;
2771 editor
->pCursors
[0].pPara
= para
;
2772 editor
->pCursors
[0].pRun
= ME_FindItemFwd(para
, diRun
);
2773 editor
->pCursors
[0].nOffset
= 0;
2774 editor
->pCursors
[1] = editor
->pCursors
[0];
2777 } else { /* v1.0 - 3.0 */
2778 if (ME_IsInTable(cursor
.pRun
) &&
2779 cursor
.pRun
->member
.run
.nFlags
& MERF_ENDPARA
&&
2782 /* Text should not be inserted at the end of the table. */
2787 /* FIXME maybe it would make sense to call EM_REPLACESEL instead ? */
2788 /* WM_CHAR is restricted to nTextLimit */
2789 if(editor
->nTextLimit
> ME_GetTextLength(editor
) - (to
-from
))
2791 ME_Style
*style
= ME_GetInsertStyle(editor
, 0);
2792 ME_ContinueCoalescingTransaction(editor
);
2793 ME_InsertTextFromCursor(editor
, 0, &wstr
, 1, style
);
2794 ME_ReleaseStyle(style
);
2795 ME_CommitCoalescingUndo(editor
);
2796 ITextHost_TxSetCursor(editor
->texthost
, NULL
, FALSE
);
2799 ME_UpdateSelectionLinkAttribute(editor
);
2800 ME_UpdateRepaint(editor
, FALSE
);
2805 /* Process the message and calculate the new click count.
2807 * returns: The click count if it is mouse down event, else returns 0. */
2808 static int ME_CalculateClickCount(ME_TextEditor
*editor
, UINT msg
, WPARAM wParam
,
2811 static int clickNum
= 0;
2812 if (msg
< WM_MOUSEFIRST
|| msg
> WM_MOUSELAST
)
2815 if ((msg
== WM_LBUTTONDBLCLK
) ||
2816 (msg
== WM_RBUTTONDBLCLK
) ||
2817 (msg
== WM_MBUTTONDBLCLK
) ||
2818 (msg
== WM_XBUTTONDBLCLK
))
2820 msg
-= (WM_LBUTTONDBLCLK
- WM_LBUTTONDOWN
);
2823 if ((msg
== WM_LBUTTONDOWN
) ||
2824 (msg
== WM_RBUTTONDOWN
) ||
2825 (msg
== WM_MBUTTONDOWN
) ||
2826 (msg
== WM_XBUTTONDOWN
))
2828 static MSG prevClickMsg
;
2830 /* Compare the editor instead of the hwnd so that the this
2831 * can still be done for windowless richedit controls. */
2832 clickMsg
.hwnd
= (HWND
)editor
;
2833 clickMsg
.message
= msg
;
2834 clickMsg
.wParam
= wParam
;
2835 clickMsg
.lParam
= lParam
;
2836 clickMsg
.time
= GetMessageTime();
2837 clickMsg
.pt
.x
= (short)LOWORD(lParam
);
2838 clickMsg
.pt
.y
= (short)HIWORD(lParam
);
2839 if ((clickNum
!= 0) &&
2840 (clickMsg
.message
== prevClickMsg
.message
) &&
2841 (clickMsg
.hwnd
== prevClickMsg
.hwnd
) &&
2842 (clickMsg
.wParam
== prevClickMsg
.wParam
) &&
2843 (clickMsg
.time
- prevClickMsg
.time
< GetDoubleClickTime()) &&
2844 (abs(clickMsg
.pt
.x
- prevClickMsg
.pt
.x
) < GetSystemMetrics(SM_CXDOUBLECLK
)/2) &&
2845 (abs(clickMsg
.pt
.y
- prevClickMsg
.pt
.y
) < GetSystemMetrics(SM_CYDOUBLECLK
)/2))
2851 prevClickMsg
= clickMsg
;
2858 static BOOL
is_link( ME_Run
*run
)
2860 return (run
->style
->fmt
.dwMask
& CFM_LINK
) && (run
->style
->fmt
.dwEffects
& CFE_LINK
);
2863 static BOOL
ME_SetCursor(ME_TextEditor
*editor
)
2869 DWORD messagePos
= GetMessagePos();
2870 pt
.x
= (short)LOWORD(messagePos
);
2871 pt
.y
= (short)HIWORD(messagePos
);
2875 sbi
.cbSize
= sizeof(sbi
);
2876 GetScrollBarInfo(editor
->hWnd
, OBJID_HSCROLL
, &sbi
);
2877 if (!(sbi
.rgstate
[0] & (STATE_SYSTEM_INVISIBLE
|STATE_SYSTEM_OFFSCREEN
)) &&
2878 PtInRect(&sbi
.rcScrollBar
, pt
))
2880 ITextHost_TxSetCursor(editor
->texthost
,
2881 LoadCursorW(NULL
, (WCHAR
*)IDC_ARROW
), FALSE
);
2884 sbi
.cbSize
= sizeof(sbi
);
2885 GetScrollBarInfo(editor
->hWnd
, OBJID_VSCROLL
, &sbi
);
2886 if (!(sbi
.rgstate
[0] & (STATE_SYSTEM_INVISIBLE
|STATE_SYSTEM_OFFSCREEN
)) &&
2887 PtInRect(&sbi
.rcScrollBar
, pt
))
2889 ITextHost_TxSetCursor(editor
->texthost
,
2890 LoadCursorW(NULL
, (WCHAR
*)IDC_ARROW
), FALSE
);
2894 ITextHost_TxScreenToClient(editor
->texthost
, &pt
);
2896 if (editor
->nSelectionType
== stLine
&& editor
->bMouseCaptured
) {
2897 ITextHost_TxSetCursor(editor
->texthost
, hLeft
, FALSE
);
2900 if (!editor
->bEmulateVersion10
/* v4.1 */ &&
2901 pt
.y
< editor
->rcFormat
.top
&&
2902 pt
.x
< editor
->rcFormat
.left
)
2904 ITextHost_TxSetCursor(editor
->texthost
, hLeft
, FALSE
);
2907 if (pt
.y
< editor
->rcFormat
.top
|| pt
.y
> editor
->rcFormat
.bottom
)
2909 if (editor
->bEmulateVersion10
) /* v1.0 - 3.0 */
2910 ITextHost_TxSetCursor(editor
->texthost
,
2911 LoadCursorW(NULL
, (WCHAR
*)IDC_ARROW
), FALSE
);
2913 ITextHost_TxSetCursor(editor
->texthost
,
2914 LoadCursorW(NULL
, (WCHAR
*)IDC_IBEAM
), TRUE
);
2917 if (pt
.x
< editor
->rcFormat
.left
)
2919 ITextHost_TxSetCursor(editor
->texthost
, hLeft
, FALSE
);
2922 ME_CharFromPos(editor
, pt
.x
, pt
.y
, &cursor
, &isExact
);
2927 run
= &cursor
.pRun
->member
.run
;
2930 ITextHost_TxSetCursor(editor
->texthost
,
2931 LoadCursorW(NULL
, (WCHAR
*)IDC_HAND
),
2936 if (ME_IsSelection(editor
))
2938 int selStart
, selEnd
;
2939 int offset
= ME_GetCursorOfs(&cursor
);
2941 ME_GetSelectionOfs(editor
, &selStart
, &selEnd
);
2942 if (selStart
<= offset
&& selEnd
>= offset
) {
2943 ITextHost_TxSetCursor(editor
->texthost
,
2944 LoadCursorW(NULL
, (WCHAR
*)IDC_ARROW
),
2950 ITextHost_TxSetCursor(editor
->texthost
,
2951 LoadCursorW(NULL
, (WCHAR
*)IDC_IBEAM
), TRUE
);
2955 static void ME_SetDefaultFormatRect(ME_TextEditor
*editor
)
2957 ITextHost_TxGetClientRect(editor
->texthost
, &editor
->rcFormat
);
2958 editor
->rcFormat
.top
+= editor
->exStyleFlags
& WS_EX_CLIENTEDGE
? 1 : 0;
2959 editor
->rcFormat
.left
+= 1 + editor
->selofs
;
2960 editor
->rcFormat
.right
-= 1;
2963 static LONG
ME_GetSelectionType(ME_TextEditor
*editor
)
2965 LONG sel_type
= SEL_EMPTY
;
2968 ME_GetSelectionOfs(editor
, &start
, &end
);
2970 sel_type
= SEL_EMPTY
;
2973 LONG object_count
= 0, character_count
= 0;
2976 for (i
= 0; i
< end
- start
; i
++)
2980 ME_CursorFromCharOfs(editor
, start
+ i
, &cursor
);
2981 if (cursor
.pRun
->member
.run
.reobj
)
2985 if (character_count
>= 2 && object_count
>= 2)
2986 return (SEL_TEXT
| SEL_MULTICHAR
| SEL_OBJECT
| SEL_MULTIOBJECT
);
2988 if (character_count
)
2990 sel_type
|= SEL_TEXT
;
2991 if (character_count
>= 2)
2992 sel_type
|= SEL_MULTICHAR
;
2996 sel_type
|= SEL_OBJECT
;
2997 if (object_count
>= 2)
2998 sel_type
|= SEL_MULTIOBJECT
;
3004 static BOOL
ME_ShowContextMenu(ME_TextEditor
*editor
, int x
, int y
)
3010 if(!editor
->lpOleCallback
|| !editor
->hWnd
)
3012 ME_GetSelectionOfs(editor
, &selrange
.cpMin
, &selrange
.cpMax
);
3013 seltype
= ME_GetSelectionType(editor
);
3014 if(SUCCEEDED(IRichEditOleCallback_GetContextMenu(editor
->lpOleCallback
, seltype
, NULL
, &selrange
, &menu
)))
3016 TrackPopupMenu(menu
, TPM_LEFTALIGN
| TPM_RIGHTBUTTON
, x
, y
, 0, editor
->hwndParent
, NULL
);
3022 ME_TextEditor
*ME_MakeEditor(ITextHost
*texthost
, BOOL bEmulateVersion10
)
3024 ME_TextEditor
*ed
= heap_alloc(sizeof(*ed
));
3030 ed
->hwndParent
= NULL
;
3031 ed
->sizeWindow
.cx
= ed
->sizeWindow
.cy
= 0;
3032 ed
->texthost
= texthost
;
3034 ed
->bEmulateVersion10
= bEmulateVersion10
;
3036 ed
->exStyleFlags
= 0;
3037 ITextHost_TxGetPropertyBits(texthost
,
3038 (TXTBIT_RICHTEXT
|TXTBIT_MULTILINE
|
3039 TXTBIT_READONLY
|TXTBIT_USEPASSWORD
|
3040 TXTBIT_HIDESELECTION
|TXTBIT_SAVESELECTION
|
3041 TXTBIT_AUTOWORDSEL
|TXTBIT_VERTICAL
|
3042 TXTBIT_WORDWRAP
|TXTBIT_DISABLEDRAG
),
3044 ITextHost_TxGetScrollBars(texthost
, &ed
->styleFlags
);
3045 ed
->styleFlags
&= (WS_VSCROLL
|WS_HSCROLL
|ES_AUTOVSCROLL
|
3046 ES_AUTOHSCROLL
|ES_DISABLENOSCROLL
);
3047 ed
->pBuffer
= ME_MakeText();
3048 ed
->nZoomNumerator
= ed
->nZoomDenominator
= 0;
3049 ed
->nAvailWidth
= 0; /* wrap to client area */
3050 list_init( &ed
->style_list
);
3051 ME_MakeFirstParagraph(ed
);
3052 /* The four cursors are for:
3053 * 0 - The position where the caret is shown
3054 * 1 - The anchored end of the selection (for normal selection)
3055 * 2 & 3 - The anchored start and end respectively for word, line,
3056 * or paragraph selection.
3059 ed
->pCursors
= heap_alloc(ed
->nCursors
* sizeof(*ed
->pCursors
));
3060 ME_SetCursorToStart(ed
, &ed
->pCursors
[0]);
3061 ed
->pCursors
[1] = ed
->pCursors
[0];
3062 ed
->pCursors
[2] = ed
->pCursors
[0];
3063 ed
->pCursors
[3] = ed
->pCursors
[1];
3064 ed
->nLastTotalLength
= ed
->nTotalLength
= 0;
3065 ed
->nLastTotalWidth
= ed
->nTotalWidth
= 0;
3067 ed
->rgbBackColor
= -1;
3068 ed
->hbrBackground
= GetSysColorBrush(COLOR_WINDOW
);
3069 ed
->bCaretAtEnd
= FALSE
;
3071 ed
->nModifyStep
= 0;
3072 ed
->nTextLimit
= TEXT_LIMIT_DEFAULT
;
3073 list_init( &ed
->undo_stack
);
3074 list_init( &ed
->redo_stack
);
3075 ed
->nUndoStackSize
= 0;
3076 ed
->nUndoLimit
= STACK_SIZE_DEFAULT
;
3077 ed
->nUndoMode
= umAddToUndo
;
3078 ed
->nParagraphs
= 1;
3079 ed
->nLastSelStart
= ed
->nLastSelEnd
= 0;
3080 ed
->pLastSelStartPara
= ed
->pLastSelEndPara
= ed
->pCursors
[0].pPara
;
3081 ed
->bHideSelection
= FALSE
;
3082 ed
->pfnWordBreak
= NULL
;
3083 ed
->lpOleCallback
= NULL
;
3084 ed
->mode
= TM_MULTILEVELUNDO
| TM_MULTICODEPAGE
;
3085 ed
->mode
|= (props
& TXTBIT_RICHTEXT
) ? TM_RICHTEXT
: TM_PLAINTEXT
;
3086 ed
->AutoURLDetect_bEnable
= FALSE
;
3087 ed
->bHaveFocus
= FALSE
;
3088 ed
->bDialogMode
= FALSE
;
3089 ed
->bMouseCaptured
= FALSE
;
3090 for (i
=0; i
<HFONT_CACHE_SIZE
; i
++)
3092 ed
->pFontCache
[i
].nRefs
= 0;
3093 ed
->pFontCache
[i
].nAge
= 0;
3094 ed
->pFontCache
[i
].hFont
= NULL
;
3097 ME_CheckCharOffsets(ed
);
3098 SetRectEmpty(&ed
->rcFormat
);
3099 ed
->bDefaultFormatRect
= TRUE
;
3100 ITextHost_TxGetSelectionBarWidth(ed
->texthost
, &selbarwidth
);
3102 /* FIXME: Convert selbarwidth from HIMETRIC to pixels */
3103 ed
->selofs
= SELECTIONBAR_WIDTH
;
3104 ed
->styleFlags
|= ES_SELECTIONBAR
;
3108 ed
->nSelectionType
= stPosition
;
3110 ed
->cPasswordMask
= 0;
3111 if (props
& TXTBIT_USEPASSWORD
)
3112 ITextHost_TxGetPasswordChar(texthost
, &ed
->cPasswordMask
);
3114 if (props
& TXTBIT_AUTOWORDSEL
)
3115 ed
->styleFlags
|= ECO_AUTOWORDSELECTION
;
3116 if (props
& TXTBIT_MULTILINE
) {
3117 ed
->styleFlags
|= ES_MULTILINE
;
3118 ed
->bWordWrap
= (props
& TXTBIT_WORDWRAP
) != 0;
3120 ed
->bWordWrap
= FALSE
;
3122 if (props
& TXTBIT_READONLY
)
3123 ed
->styleFlags
|= ES_READONLY
;
3124 if (!(props
& TXTBIT_HIDESELECTION
))
3125 ed
->styleFlags
|= ES_NOHIDESEL
;
3126 if (props
& TXTBIT_SAVESELECTION
)
3127 ed
->styleFlags
|= ES_SAVESEL
;
3128 if (props
& TXTBIT_VERTICAL
)
3129 ed
->styleFlags
|= ES_VERTICAL
;
3130 if (props
& TXTBIT_DISABLEDRAG
)
3131 ed
->styleFlags
|= ES_NOOLEDRAGDROP
;
3133 ed
->notified_cr
.cpMin
= ed
->notified_cr
.cpMax
= 0;
3135 /* Default scrollbar information */
3136 ed
->vert_si
.cbSize
= sizeof(SCROLLINFO
);
3137 ed
->vert_si
.nMin
= 0;
3138 ed
->vert_si
.nMax
= 0;
3139 ed
->vert_si
.nPage
= 0;
3140 ed
->vert_si
.nPos
= 0;
3142 ed
->horz_si
.cbSize
= sizeof(SCROLLINFO
);
3143 ed
->horz_si
.nMin
= 0;
3144 ed
->horz_si
.nMax
= 0;
3145 ed
->horz_si
.nPage
= 0;
3146 ed
->horz_si
.nPos
= 0;
3148 ed
->wheel_remain
= 0;
3150 list_init( &ed
->reobj_list
);
3151 OleInitialize(NULL
);
3156 void ME_DestroyEditor(ME_TextEditor
*editor
)
3158 ME_DisplayItem
*pFirst
= editor
->pBuffer
->pFirst
;
3159 ME_DisplayItem
*p
= pFirst
, *pNext
= NULL
;
3160 ME_Style
*s
, *cursor2
;
3163 ME_ClearTempStyle(editor
);
3164 ME_EmptyUndoStack(editor
);
3167 ME_DestroyDisplayItem(p
);
3171 LIST_FOR_EACH_ENTRY_SAFE( s
, cursor2
, &editor
->style_list
, ME_Style
, entry
)
3172 ME_DestroyStyle( s
);
3174 ME_ReleaseStyle(editor
->pBuffer
->pDefaultStyle
);
3175 for (i
=0; i
<HFONT_CACHE_SIZE
; i
++)
3177 if (editor
->pFontCache
[i
].hFont
)
3178 DeleteObject(editor
->pFontCache
[i
].hFont
);
3180 if (editor
->rgbBackColor
!= -1)
3181 DeleteObject(editor
->hbrBackground
);
3182 if(editor
->lpOleCallback
)
3183 IRichEditOleCallback_Release(editor
->lpOleCallback
);
3184 ITextHost_Release(editor
->texthost
);
3187 IRichEditOle_Release(editor
->reOle
);
3188 editor
->reOle
= NULL
;
3192 heap_free(editor
->pBuffer
);
3193 heap_free(editor
->pCursors
);
3197 BOOL WINAPI
DllMain(HINSTANCE hinstDLL
, DWORD fdwReason
, LPVOID lpvReserved
)
3202 case DLL_PROCESS_ATTACH
:
3203 DisableThreadLibraryCalls(hinstDLL
);
3204 me_heap
= HeapCreate (0, 0x10000, 0);
3205 if (!ME_RegisterEditorClass(hinstDLL
)) return FALSE
;
3206 hLeft
= LoadCursorW(hinstDLL
, MAKEINTRESOURCEW(OCR_REVERSE
));
3210 case DLL_PROCESS_DETACH
:
3211 if (lpvReserved
) break;
3212 UnregisterClassW(RICHEDIT_CLASS20W
, 0);
3213 UnregisterClassW(MSFTEDIT_CLASS
, 0);
3214 UnregisterClassA(RICHEDIT_CLASS20A
, 0);
3215 UnregisterClassA("RichEdit50A", 0);
3216 if (ME_ListBoxRegistered
)
3217 UnregisterClassW(REListBox20W
, 0);
3218 if (ME_ComboBoxRegistered
)
3219 UnregisterClassW(REComboBox20W
, 0);
3221 HeapDestroy (me_heap
);
3228 static inline int get_default_line_height( ME_TextEditor
*editor
)
3232 if (editor
->pBuffer
&& editor
->pBuffer
->pDefaultStyle
)
3233 height
= editor
->pBuffer
->pDefaultStyle
->tm
.tmHeight
;
3234 if (height
<= 0) height
= 24;
3239 static inline int calc_wheel_change( int *remain
, int amount_per_click
)
3241 int change
= amount_per_click
* (float)*remain
/ WHEEL_DELTA
;
3242 *remain
-= WHEEL_DELTA
* change
/ amount_per_click
;
3246 static const char * const edit_messages
[] = {
3275 "EM_SETPASSWORDCHAR",
3276 "EM_EMPTYUNDOBUFFER",
3277 "EM_GETFIRSTVISIBLELINE",
3279 "EM_SETWORDBREAKPROC",
3280 "EM_GETWORDBREAKPROC",
3281 "EM_GETPASSWORDCHAR",
3291 static const char * const richedit_messages
[] = {
3296 "EM_EXLINEFROMCHAR",
3302 "EM_GETOLEINTERFACE",
3312 "EM_SETOLECALLBACK",
3314 "EM_SETTARGETDEVICE",
3322 "EM_GETWORDBREAKPROCEX",
3323 "EM_SETWORDBREAKPROCEX",
3325 "EM_UNKNOWN_USER_83",
3330 "EM_STOPGROUPTYPING",
3334 "EM_GETAUTOURLDETECT",
3337 "EM_GETTEXTLENGTHEX",
3340 "EM_UNKNOWN_USER_98",
3341 "EM_UNKNOWN_USER_99",
3342 "EM_SETPUNCTUATION",
3343 "EM_GETPUNCTUATION",
3344 "EM_SETWORDWRAPMODE",
3345 "EM_GETWORDWRAPMODE",
3351 "EM_UNKNOWN_USER_109",
3352 "EM_UNKNOWN_USER_110",
3353 "EM_UNKNOWN_USER_111",
3354 "EM_UNKNOWN_USER_112",
3355 "EM_UNKNOWN_USER_113",
3356 "EM_UNKNOWN_USER_114",
3357 "EM_UNKNOWN_USER_115",
3358 "EM_UNKNOWN_USER_116",
3359 "EM_UNKNOWN_USER_117",
3360 "EM_UNKNOWN_USER_118",
3361 "EM_UNKNOWN_USER_119",
3362 "EM_SETLANGOPTIONS",
3363 "EM_GETLANGOPTIONS",
3364 "EM_GETIMECOMPMODE",
3368 "EM_SETIMEMODEBIAS",
3373 get_msg_name(UINT msg
)
3375 if (msg
>= EM_GETSEL
&& msg
<= EM_CHARFROMPOS
)
3376 return edit_messages
[msg
- EM_GETSEL
];
3377 if (msg
>= EM_CANPASTE
&& msg
<= EM_GETIMEMODEBIAS
)
3378 return richedit_messages
[msg
- EM_CANPASTE
];
3382 static void ME_LinkNotify(ME_TextEditor
*editor
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
3386 ME_Cursor cursor
; /* The start of the clicked text. */
3389 x
= (short)LOWORD(lParam
);
3390 y
= (short)HIWORD(lParam
);
3391 ME_CharFromPos(editor
, x
, y
, &cursor
, &isExact
);
3392 if (!isExact
) return;
3394 if (is_link( &cursor
.pRun
->member
.run
))
3395 { /* The clicked run has CFE_LINK set */
3398 info
.nmhdr
.hwndFrom
= NULL
;
3399 info
.nmhdr
.idFrom
= 0;
3400 info
.nmhdr
.code
= EN_LINK
;
3402 info
.wParam
= wParam
;
3403 info
.lParam
= lParam
;
3406 /* find the first contiguous run with CFE_LINK set */
3407 info
.chrg
.cpMin
= ME_GetCursorOfs(&cursor
);
3409 while (ME_PrevRun( NULL
, &di
, FALSE
) && is_link( &di
->member
.run
))
3410 info
.chrg
.cpMin
-= di
->member
.run
.len
;
3412 /* find the last contiguous run with CFE_LINK set */
3413 info
.chrg
.cpMax
= ME_GetCursorOfs(&cursor
) + cursor
.pRun
->member
.run
.len
;
3415 while (ME_NextRun( NULL
, &di
, FALSE
) && is_link( &di
->member
.run
))
3416 info
.chrg
.cpMax
+= di
->member
.run
.len
;
3418 ITextHost_TxNotify(editor
->texthost
, info
.nmhdr
.code
, &info
);
3422 void ME_ReplaceSel(ME_TextEditor
*editor
, BOOL can_undo
, const WCHAR
*str
, int len
)
3424 int from
, to
, nStartCursor
;
3427 nStartCursor
= ME_GetSelectionOfs(editor
, &from
, &to
);
3428 style
= ME_GetSelectionInsertStyle(editor
);
3429 ME_InternalDeleteText(editor
, &editor
->pCursors
[nStartCursor
], to
-from
, FALSE
);
3430 ME_InsertTextFromCursor(editor
, 0, str
, len
, style
);
3431 ME_ReleaseStyle(style
);
3432 /* drop temporary style if line end */
3434 * FIXME question: does abc\n mean: put abc,
3435 * clear temp style, put \n? (would require a change)
3437 if (len
>0 && str
[len
-1] == '\n')
3438 ME_ClearTempStyle(editor
);
3439 ME_CommitUndo(editor
);
3440 ME_UpdateSelectionLinkAttribute(editor
);
3442 ME_EmptyUndoStack(editor
);
3443 ME_UpdateRepaint(editor
, FALSE
);
3446 static void ME_SetText(ME_TextEditor
*editor
, void *text
, BOOL unicode
)
3448 LONG codepage
= unicode
? CP_UNICODE
: CP_ACP
;
3451 LPWSTR wszText
= ME_ToUnicode(codepage
, text
, &textLen
);
3452 ME_InsertTextFromCursor(editor
, 0, wszText
, textLen
, editor
->pBuffer
->pDefaultStyle
);
3453 ME_EndToUnicode(codepage
, wszText
);
3456 static LRESULT
ME_WmCreate(ME_TextEditor
*editor
, LPARAM lParam
, BOOL unicode
)
3458 CREATESTRUCTW
*createW
= (CREATESTRUCTW
*)lParam
;
3459 CREATESTRUCTA
*createA
= (CREATESTRUCTA
*)lParam
;
3464 text
= unicode
? (void*)createW
->lpszName
: (void*)createA
->lpszName
;
3466 ME_SetDefaultFormatRect(editor
);
3468 max
= (editor
->styleFlags
& ES_DISABLENOSCROLL
) ? 1 : 0;
3469 if (~editor
->styleFlags
& ES_DISABLENOSCROLL
|| editor
->styleFlags
& WS_VSCROLL
)
3470 ITextHost_TxSetScrollRange(editor
->texthost
, SB_VERT
, 0, max
, TRUE
);
3472 if (~editor
->styleFlags
& ES_DISABLENOSCROLL
|| editor
->styleFlags
& WS_HSCROLL
)
3473 ITextHost_TxSetScrollRange(editor
->texthost
, SB_HORZ
, 0, max
, TRUE
);
3475 if (editor
->styleFlags
& ES_DISABLENOSCROLL
)
3477 if (editor
->styleFlags
& WS_VSCROLL
)
3479 ITextHost_TxEnableScrollBar(editor
->texthost
, SB_VERT
, ESB_DISABLE_BOTH
);
3480 ITextHost_TxShowScrollBar(editor
->texthost
, SB_VERT
, TRUE
);
3482 if (editor
->styleFlags
& WS_HSCROLL
)
3484 ITextHost_TxEnableScrollBar(editor
->texthost
, SB_HORZ
, ESB_DISABLE_BOTH
);
3485 ITextHost_TxShowScrollBar(editor
->texthost
, SB_HORZ
, TRUE
);
3491 ME_SetText(editor
, text
, unicode
);
3492 ME_SetCursorToStart(editor
, &editor
->pCursors
[0]);
3493 ME_SetCursorToStart(editor
, &editor
->pCursors
[1]);
3496 ME_CommitUndo(editor
);
3497 ME_WrapMarkedParagraphs(editor
);
3498 ME_MoveCaret(editor
);
3503 #define UNSUPPORTED_MSG(e) \
3505 FIXME(#e ": stub\n"); \
3506 *phresult = S_FALSE; \
3509 /* Handle messages for windowless and windowed richedit controls.
3511 * The LRESULT that is returned is a return value for window procs,
3512 * and the phresult parameter is the COM return code needed by the
3513 * text services interface. */
3514 LRESULT
ME_HandleMessage(ME_TextEditor
*editor
, UINT msg
, WPARAM wParam
,
3515 LPARAM lParam
, BOOL unicode
, HRESULT
* phresult
)
3521 UNSUPPORTED_MSG(EM_DISPLAYBAND
)
3522 UNSUPPORTED_MSG(EM_FINDWORDBREAK
)
3523 UNSUPPORTED_MSG(EM_FMTLINES
)
3524 UNSUPPORTED_MSG(EM_FORMATRANGE
)
3525 UNSUPPORTED_MSG(EM_GETBIDIOPTIONS
)
3526 UNSUPPORTED_MSG(EM_GETEDITSTYLE
)
3527 UNSUPPORTED_MSG(EM_GETIMECOMPMODE
)
3528 UNSUPPORTED_MSG(EM_GETIMESTATUS
)
3529 UNSUPPORTED_MSG(EM_SETIMESTATUS
)
3530 UNSUPPORTED_MSG(EM_GETLANGOPTIONS
)
3531 UNSUPPORTED_MSG(EM_GETREDONAME
)
3532 UNSUPPORTED_MSG(EM_GETTYPOGRAPHYOPTIONS
)
3533 UNSUPPORTED_MSG(EM_GETUNDONAME
)
3534 UNSUPPORTED_MSG(EM_GETWORDBREAKPROCEX
)
3535 UNSUPPORTED_MSG(EM_SETBIDIOPTIONS
)
3536 UNSUPPORTED_MSG(EM_SETEDITSTYLE
)
3537 UNSUPPORTED_MSG(EM_SETLANGOPTIONS
)
3538 UNSUPPORTED_MSG(EM_SETMARGINS
)
3539 UNSUPPORTED_MSG(EM_SETPALETTE
)
3540 UNSUPPORTED_MSG(EM_SETTABSTOPS
)
3541 UNSUPPORTED_MSG(EM_SETTYPOGRAPHYOPTIONS
)
3542 UNSUPPORTED_MSG(EM_SETWORDBREAKPROCEX
)
3544 /* Messages specific to Richedit controls */
3547 return ME_StreamIn(editor
, wParam
, (EDITSTREAM
*)lParam
, TRUE
);
3549 return ME_StreamOut(editor
, wParam
, (EDITSTREAM
*)lParam
);
3552 UINT code
= DLGC_WANTCHARS
|DLGC_WANTTAB
|DLGC_WANTARROWS
;
3555 editor
->bDialogMode
= TRUE
;
3556 if (editor
->styleFlags
& ES_MULTILINE
)
3557 code
|= DLGC_WANTMESSAGE
;
3558 if (!(editor
->styleFlags
& ES_SAVESEL
))
3559 code
|= DLGC_HASSETSEL
;
3562 case EM_EMPTYUNDOBUFFER
:
3563 ME_EmptyUndoStack(editor
);
3567 /* Note: wParam/lParam can be NULL */
3569 PUINT pfrom
= wParam
? (PUINT
)wParam
: &from
;
3570 PUINT pto
= lParam
? (PUINT
)lParam
: &to
;
3571 ME_GetSelectionOfs(editor
, (int *)pfrom
, (int *)pto
);
3572 if ((*pfrom
|*pto
) & 0xFFFF0000)
3574 return MAKELONG(*pfrom
,*pto
);
3578 CHARRANGE
*pRange
= (CHARRANGE
*)lParam
;
3579 ME_GetSelectionOfs(editor
, &pRange
->cpMin
, &pRange
->cpMax
);
3580 TRACE("EM_EXGETSEL = (%d,%d)\n", pRange
->cpMin
, pRange
->cpMax
);
3583 case EM_SETUNDOLIMIT
:
3585 if ((int)wParam
< 0)
3586 editor
->nUndoLimit
= STACK_SIZE_DEFAULT
;
3588 editor
->nUndoLimit
= min(wParam
, STACK_SIZE_MAX
);
3589 /* Setting a max stack size keeps wine from getting killed
3590 for hogging memory. Windows allocates all this memory at once, so
3591 no program would realistically set a value above our maximum. */
3592 return editor
->nUndoLimit
;
3595 return !list_empty( &editor
->undo_stack
);
3597 return !list_empty( &editor
->redo_stack
);
3598 case WM_UNDO
: /* FIXME: actually not the same */
3600 return ME_Undo(editor
);
3602 return ME_Redo(editor
);
3605 /* these flags are equivalent to the ES_* counterparts */
3606 DWORD mask
= ECO_VERTICAL
| ECO_AUTOHSCROLL
| ECO_AUTOVSCROLL
|
3607 ECO_NOHIDESEL
| ECO_READONLY
| ECO_WANTRETURN
| ECO_SELECTIONBAR
;
3608 DWORD settings
= editor
->styleFlags
& mask
;
3612 case EM_SETFONTSIZE
:
3615 LONG tmp_size
, size
;
3616 BOOL is_increase
= ((LONG
)wParam
> 0);
3618 if (editor
->mode
& TM_PLAINTEXT
)
3621 cf
.cbSize
= sizeof(cf
);
3622 cf
.dwMask
= CFM_SIZE
;
3623 ME_GetSelectionCharFormat(editor
, &cf
);
3624 tmp_size
= (cf
.yHeight
/ 20) + wParam
;
3628 else if (tmp_size
> 12 && tmp_size
< 28 && tmp_size
% 2)
3629 size
= tmp_size
+ (is_increase
? 1 : -1);
3630 else if (tmp_size
> 28 && tmp_size
< 36)
3631 size
= is_increase
? 36 : 28;
3632 else if (tmp_size
> 36 && tmp_size
< 48)
3633 size
= is_increase
? 48 : 36;
3634 else if (tmp_size
> 48 && tmp_size
< 72)
3635 size
= is_increase
? 72 : 48;
3636 else if (tmp_size
> 72 && tmp_size
< 80)
3637 size
= is_increase
? 80 : 72;
3638 else if (tmp_size
> 80 && tmp_size
< 1638)
3639 size
= 10 * (is_increase
? (tmp_size
/ 10 + 1) : (tmp_size
/ 10));
3640 else if (tmp_size
>= 1638)
3645 cf
.yHeight
= size
* 20; /* convert twips to points */
3646 ME_SetSelectionCharFormat(editor
, &cf
);
3647 ME_CommitUndo(editor
);
3648 ME_WrapMarkedParagraphs(editor
);
3649 ME_UpdateScrollBar(editor
);
3656 /* these flags are equivalent to ES_* counterparts, except for
3657 * ECO_AUTOWORDSELECTION that doesn't have an ES_* counterpart,
3658 * but is still stored in editor->styleFlags. */
3659 const DWORD mask
= ECO_VERTICAL
| ECO_AUTOHSCROLL
| ECO_AUTOVSCROLL
|
3660 ECO_NOHIDESEL
| ECO_READONLY
| ECO_WANTRETURN
|
3661 ECO_SELECTIONBAR
| ECO_AUTOWORDSELECTION
;
3662 DWORD settings
= mask
& editor
->styleFlags
;
3663 DWORD oldSettings
= settings
;
3664 DWORD changedSettings
;
3680 changedSettings
= oldSettings
^ settings
;
3682 if (changedSettings
) {
3683 editor
->styleFlags
= (editor
->styleFlags
& ~mask
) | (settings
& mask
);
3685 if (changedSettings
& ECO_SELECTIONBAR
)
3687 ITextHost_TxInvalidateRect(editor
->texthost
, &editor
->rcFormat
, TRUE
);
3688 if (settings
& ECO_SELECTIONBAR
) {
3689 assert(!editor
->selofs
);
3690 editor
->selofs
= SELECTIONBAR_WIDTH
;
3691 editor
->rcFormat
.left
+= editor
->selofs
;
3693 editor
->rcFormat
.left
-= editor
->selofs
;
3696 ME_RewrapRepaint(editor
);
3699 if ((changedSettings
& settings
& ES_NOHIDESEL
) && !editor
->bHaveFocus
)
3700 ME_InvalidateSelection( editor
);
3702 if (changedSettings
& settings
& ECO_VERTICAL
)
3703 FIXME("ECO_VERTICAL not implemented yet!\n");
3704 if (changedSettings
& settings
& ECO_AUTOHSCROLL
)
3705 FIXME("ECO_AUTOHSCROLL not implemented yet!\n");
3706 if (changedSettings
& settings
& ECO_AUTOVSCROLL
)
3707 FIXME("ECO_AUTOVSCROLL not implemented yet!\n");
3708 if (changedSettings
& settings
& ECO_WANTRETURN
)
3709 FIXME("ECO_WANTRETURN not implemented yet!\n");
3710 if (changedSettings
& settings
& ECO_AUTOWORDSELECTION
)
3711 FIXME("ECO_AUTOWORDSELECTION not implemented yet!\n");
3718 return handle_EM_EXSETSEL( editor
, wParam
, lParam
);
3720 case EM_SETSCROLLPOS
:
3722 POINT
*point
= (POINT
*)lParam
;
3723 ME_ScrollAbs(editor
, point
->x
, point
->y
);
3726 case EM_AUTOURLDETECT
:
3728 if (wParam
==1 || wParam
==0)
3730 editor
->AutoURLDetect_bEnable
= (BOOL
)wParam
;
3733 return E_INVALIDARG
;
3735 case EM_GETAUTOURLDETECT
:
3737 return editor
->AutoURLDetect_bEnable
;
3741 CHARRANGE range
= *(CHARRANGE
*)lParam
;
3743 return handle_EM_EXSETSEL( editor
, range
.cpMin
, range
.cpMax
);
3745 case EM_SHOWSCROLLBAR
:
3758 flags
= WS_HSCROLL
|WS_VSCROLL
;
3765 editor
->styleFlags
|= flags
;
3766 if (flags
& WS_HSCROLL
)
3767 ITextHost_TxShowScrollBar(editor
->texthost
, SB_HORZ
,
3768 editor
->nTotalWidth
> editor
->sizeWindow
.cx
);
3769 if (flags
& WS_VSCROLL
)
3770 ITextHost_TxShowScrollBar(editor
->texthost
, SB_VERT
,
3771 editor
->nTotalLength
> editor
->sizeWindow
.cy
);
3773 editor
->styleFlags
&= ~flags
;
3774 ITextHost_TxShowScrollBar(editor
->texthost
, wParam
, FALSE
);
3781 SETTEXTEX
*pStruct
= (SETTEXTEX
*)wParam
;
3784 BOOL bRtf
, bUnicode
, bSelection
, bUTF8
;
3785 int oldModify
= editor
->nModifyStep
;
3786 static const char utf8_bom
[] = {0xef, 0xbb, 0xbf};
3788 if (!pStruct
) return 0;
3790 /* If we detect ascii rtf at the start of the string,
3791 * we know it isn't unicode. */
3792 bRtf
= (lParam
&& (!strncmp((char *)lParam
, "{\\rtf", 5) ||
3793 !strncmp((char *)lParam
, "{\\urtf", 6)));
3794 bUnicode
= !bRtf
&& pStruct
->codepage
== CP_UNICODE
;
3795 bUTF8
= (lParam
&& (!strncmp((char *)lParam
, utf8_bom
, 3)));
3797 TRACE("EM_SETTEXTEX - %s, flags %d, cp %d\n",
3798 bUnicode
? debugstr_w((LPCWSTR
)lParam
) : debugstr_a((LPCSTR
)lParam
),
3799 pStruct
->flags
, pStruct
->codepage
);
3801 bSelection
= (pStruct
->flags
& ST_SELECTION
) != 0;
3803 int nStartCursor
= ME_GetSelectionOfs(editor
, &from
, &to
);
3804 style
= ME_GetSelectionInsertStyle(editor
);
3805 ME_InternalDeleteText(editor
, &editor
->pCursors
[nStartCursor
], to
- from
, FALSE
);
3808 ME_SetCursorToStart(editor
, &start
);
3809 ME_InternalDeleteText(editor
, &start
, ME_GetTextLength(editor
), FALSE
);
3810 style
= editor
->pBuffer
->pDefaultStyle
;
3814 ME_StreamInRTFString(editor
, bSelection
, (char *)lParam
);
3816 /* FIXME: The length returned doesn't include the rtf control
3817 * characters, only the actual text. */
3818 len
= lParam
? strlen((char *)lParam
) : 0;
3821 if (bUTF8
&& !bUnicode
) {
3822 wszText
= ME_ToUnicode(CP_UTF8
, (void *)(lParam
+3), &len
);
3823 ME_InsertTextFromCursor(editor
, 0, wszText
, len
, style
);
3824 ME_EndToUnicode(CP_UTF8
, wszText
);
3826 wszText
= ME_ToUnicode(pStruct
->codepage
, (void *)lParam
, &len
);
3827 ME_InsertTextFromCursor(editor
, 0, wszText
, len
, style
);
3828 ME_EndToUnicode(pStruct
->codepage
, wszText
);
3833 ME_ReleaseStyle(style
);
3834 ME_UpdateSelectionLinkAttribute(editor
);
3838 ME_SetCursorToStart(editor
, &cursor
);
3839 ME_UpdateLinkAttribute(editor
, &cursor
, INT_MAX
);
3841 ME_CommitUndo(editor
);
3842 if (!(pStruct
->flags
& ST_KEEPUNDO
))
3844 editor
->nModifyStep
= oldModify
;
3845 ME_EmptyUndoStack(editor
);
3847 ME_UpdateRepaint(editor
, FALSE
);
3850 case EM_SELECTIONTYPE
:
3851 return ME_GetSelectionType(editor
);
3852 case EM_SETBKGNDCOLOR
:
3855 if (editor
->rgbBackColor
!= -1) {
3856 DeleteObject(editor
->hbrBackground
);
3857 lColor
= editor
->rgbBackColor
;
3859 else lColor
= ITextHost_TxGetSysColor(editor
->texthost
, COLOR_WINDOW
);
3863 editor
->rgbBackColor
= -1;
3864 editor
->hbrBackground
= GetSysColorBrush(COLOR_WINDOW
);
3868 editor
->rgbBackColor
= lParam
;
3869 editor
->hbrBackground
= CreateSolidBrush(editor
->rgbBackColor
);
3871 ITextHost_TxInvalidateRect(editor
->texthost
, NULL
, TRUE
);
3875 return editor
->nModifyStep
== 0 ? 0 : -1;
3879 editor
->nModifyStep
= 1;
3881 editor
->nModifyStep
= 0;
3885 case EM_SETREADONLY
:
3888 editor
->styleFlags
|= ES_READONLY
;
3890 editor
->styleFlags
&= ~ES_READONLY
;
3893 case EM_SETEVENTMASK
:
3895 DWORD nOldMask
= editor
->nEventMask
;
3897 editor
->nEventMask
= lParam
;
3900 case EM_GETEVENTMASK
:
3901 return editor
->nEventMask
;
3902 case EM_SETCHARFORMAT
:
3905 BOOL bRepaint
= TRUE
;
3906 if (!cfany_to_cf2w(&p
, (CHARFORMAT2W
*)lParam
))
3908 if (wParam
& SCF_ALL
) {
3909 if (editor
->mode
& TM_PLAINTEXT
) {
3910 ME_SetDefaultCharFormat(editor
, &p
);
3913 ME_SetCursorToStart(editor
, &start
);
3914 ME_SetCharFormat(editor
, &start
, NULL
, &p
);
3915 editor
->nModifyStep
= 1;
3917 } else if (wParam
& SCF_SELECTION
) {
3918 if (editor
->mode
& TM_PLAINTEXT
)
3920 if (wParam
& SCF_WORD
) {
3922 ME_Cursor end
= editor
->pCursors
[0];
3923 ME_MoveCursorWords(editor
, &end
, +1);
3925 ME_MoveCursorWords(editor
, &start
, -1);
3926 ME_SetCharFormat(editor
, &start
, &end
, &p
);
3928 bRepaint
= ME_IsSelection(editor
);
3929 ME_SetSelectionCharFormat(editor
, &p
);
3930 if (bRepaint
) editor
->nModifyStep
= 1;
3931 } else { /* SCF_DEFAULT */
3932 ME_SetDefaultCharFormat(editor
, &p
);
3934 ME_CommitUndo(editor
);
3937 ME_WrapMarkedParagraphs(editor
);
3938 ME_UpdateScrollBar(editor
);
3943 case EM_GETCHARFORMAT
:
3945 CHARFORMAT2W tmp
, *dst
= (CHARFORMAT2W
*)lParam
;
3946 if (dst
->cbSize
!= sizeof(CHARFORMATA
) &&
3947 dst
->cbSize
!= sizeof(CHARFORMATW
) &&
3948 dst
->cbSize
!= sizeof(CHARFORMAT2A
) &&
3949 dst
->cbSize
!= sizeof(CHARFORMAT2W
))
3951 tmp
.cbSize
= sizeof(tmp
);
3953 ME_GetDefaultCharFormat(editor
, &tmp
);
3955 ME_GetSelectionCharFormat(editor
, &tmp
);
3956 cf2w_to_cfany(dst
, &tmp
);
3959 case EM_SETPARAFORMAT
:
3961 BOOL result
= ME_SetSelectionParaFormat(editor
, (PARAFORMAT2
*)lParam
);
3962 ME_WrapMarkedParagraphs(editor
);
3963 ME_UpdateScrollBar(editor
);
3965 ME_CommitUndo(editor
);
3968 case EM_GETPARAFORMAT
:
3969 ME_GetSelectionParaFormat(editor
, (PARAFORMAT2
*)lParam
);
3970 return ((PARAFORMAT2
*)lParam
)->dwMask
;
3971 case EM_GETFIRSTVISIBLELINE
:
3973 ME_DisplayItem
*p
= editor
->pBuffer
->pFirst
;
3974 int y
= editor
->vert_si
.nPos
;
3979 p
= ME_FindItemFwd(p
, diStartRowOrParagraphOrEnd
);
3980 if (p
->type
== diTextEnd
)
3982 if (p
->type
== diParagraph
) {
3983 ypara
= p
->member
.para
.pt
.y
;
3986 ystart
= ypara
+ p
->member
.row
.pt
.y
;
3987 yend
= ystart
+ p
->member
.row
.nHeight
;
3995 case EM_HIDESELECTION
:
3997 editor
->bHideSelection
= (wParam
!= 0);
3998 ME_InvalidateSelection(editor
);
4003 if (!(editor
->styleFlags
& ES_MULTILINE
))
4005 ME_ScrollDown( editor
, lParam
* get_default_line_height( editor
) );
4011 int nStartCursor
= ME_GetSelectionOfs(editor
, &from
, &to
);
4012 ME_InternalDeleteText(editor
, &editor
->pCursors
[nStartCursor
], to
-from
, FALSE
);
4013 ME_CommitUndo(editor
);
4014 ME_UpdateRepaint(editor
, TRUE
);
4020 LONG codepage
= unicode
? CP_UNICODE
: CP_ACP
;
4021 LPWSTR wszText
= ME_ToUnicode(codepage
, (void *)lParam
, &len
);
4023 TRACE("EM_REPLACESEL - %s\n", debugstr_w(wszText
));
4025 ME_ReplaceSel(editor
, !!wParam
, wszText
, len
);
4026 ME_EndToUnicode(codepage
, wszText
);
4029 case EM_SCROLLCARET
:
4030 ME_EnsureVisible(editor
, &editor
->pCursors
[0]);
4037 BOOL bRepaint
= LOWORD(lParam
);
4040 wParam
= (WPARAM
)GetStockObject(SYSTEM_FONT
);
4042 if (!GetObjectW((HGDIOBJ
)wParam
, sizeof(LOGFONTW
), &lf
))
4045 hDC
= ITextHost_TxGetDC(editor
->texthost
);
4046 ME_CharFormatFromLogFont(hDC
, &lf
, &fmt
);
4047 ITextHost_TxReleaseDC(editor
->texthost
, hDC
);
4048 if (editor
->mode
& TM_RICHTEXT
) {
4050 ME_SetCursorToStart(editor
, &start
);
4051 ME_SetCharFormat(editor
, &start
, NULL
, &fmt
);
4053 ME_SetDefaultCharFormat(editor
, &fmt
);
4055 ME_CommitUndo(editor
);
4056 ME_MarkAllForWrapping(editor
);
4057 ME_WrapMarkedParagraphs(editor
);
4058 ME_UpdateScrollBar(editor
);
4066 ME_SetCursorToStart(editor
, &cursor
);
4067 ME_InternalDeleteText(editor
, &cursor
, ME_GetTextLength(editor
), FALSE
);
4070 TRACE("WM_SETTEXT lParam==%lx\n",lParam
);
4071 if (!strncmp((char *)lParam
, "{\\rtf", 5) ||
4072 !strncmp((char *)lParam
, "{\\urtf", 6))
4074 /* Undocumented: WM_SETTEXT supports RTF text */
4075 ME_StreamInRTFString(editor
, 0, (char *)lParam
);
4078 ME_SetText(editor
, (void*)lParam
, unicode
);
4081 TRACE("WM_SETTEXT - NULL\n");
4082 ME_SetCursorToStart(editor
, &cursor
);
4083 ME_UpdateLinkAttribute(editor
, &cursor
, INT_MAX
);
4084 ME_SetSelection(editor
, 0, 0);
4085 editor
->nModifyStep
= 0;
4086 ME_CommitUndo(editor
);
4087 ME_EmptyUndoStack(editor
);
4088 ME_UpdateRepaint(editor
, FALSE
);
4092 return paste_special( editor
, 0, NULL
, TRUE
);
4094 case WM_MBUTTONDOWN
:
4098 case EM_PASTESPECIAL
:
4099 paste_special( editor
, wParam
, (REPASTESPECIAL
*)lParam
, FALSE
);
4103 copy_or_cut(editor
, msg
== WM_CUT
);
4105 case WM_GETTEXTLENGTH
:
4107 GETTEXTLENGTHEX how
;
4109 /* CR/LF conversion required in 2.0 mode, verbatim in 1.0 mode */
4110 how
.flags
= GTL_CLOSE
| (editor
->bEmulateVersion10
? 0 : GTL_USECRLF
) | GTL_NUMCHARS
;
4111 how
.codepage
= unicode
? CP_UNICODE
: CP_ACP
;
4112 return ME_GetTextLengthEx(editor
, &how
);
4114 case EM_GETTEXTLENGTHEX
:
4115 return ME_GetTextLengthEx(editor
, (GETTEXTLENGTHEX
*)wParam
);
4119 ex
.cb
= wParam
* (unicode
? sizeof(WCHAR
) : sizeof(CHAR
));
4120 ex
.flags
= GT_USECRLF
;
4121 ex
.codepage
= unicode
? CP_UNICODE
: CP_ACP
;
4122 ex
.lpDefaultChar
= NULL
;
4123 ex
.lpUsedDefChar
= NULL
;
4124 return ME_GetTextEx(editor
, &ex
, lParam
);
4127 return ME_GetTextEx(editor
, (GETTEXTEX
*)wParam
, lParam
);
4130 int nFrom
, nTo
, nStartCur
= ME_GetSelectionOfs(editor
, &nFrom
, &nTo
);
4131 ME_Cursor
*from
= &editor
->pCursors
[nStartCur
];
4132 return ME_GetTextRange(editor
, (WCHAR
*)lParam
, from
,
4133 nTo
- nFrom
, unicode
);
4135 case EM_GETSCROLLPOS
:
4137 POINT
*point
= (POINT
*)lParam
;
4138 point
->x
= editor
->horz_si
.nPos
;
4139 point
->y
= editor
->vert_si
.nPos
;
4140 /* 16-bit scaled value is returned as stored in scrollinfo */
4141 if (editor
->horz_si
.nMax
> 0xffff)
4142 point
->x
= MulDiv(point
->x
, 0xffff, editor
->horz_si
.nMax
);
4143 if (editor
->vert_si
.nMax
> 0xffff)
4144 point
->y
= MulDiv(point
->y
, 0xffff, editor
->vert_si
.nMax
);
4147 case EM_GETTEXTRANGE
:
4149 TEXTRANGEW
*rng
= (TEXTRANGEW
*)lParam
;
4151 int nStart
= rng
->chrg
.cpMin
;
4152 int nEnd
= rng
->chrg
.cpMax
;
4153 int textlength
= ME_GetTextLength(editor
);
4155 TRACE("EM_GETTEXTRANGE min=%d max=%d unicode=%d textlength=%d\n",
4156 rng
->chrg
.cpMin
, rng
->chrg
.cpMax
, unicode
, textlength
);
4157 if (nStart
< 0) return 0;
4158 if ((nStart
== 0 && nEnd
== -1) || nEnd
> textlength
)
4160 if (nStart
>= nEnd
) return 0;
4162 ME_CursorFromCharOfs(editor
, nStart
, &start
);
4163 return ME_GetTextRange(editor
, rng
->lpstrText
, &start
, nEnd
- nStart
, unicode
);
4167 ME_DisplayItem
*run
;
4168 const unsigned int nMaxChars
= *(WORD
*) lParam
;
4169 unsigned int nCharsLeft
= nMaxChars
;
4170 char *dest
= (char *) lParam
;
4171 BOOL wroteNull
= FALSE
;
4173 TRACE("EM_GETLINE: row=%d, nMaxChars=%d (%s)\n", (int) wParam
, nMaxChars
,
4174 unicode
? "Unicode" : "Ansi");
4176 run
= ME_FindRowWithNumber(editor
, wParam
);
4180 while (nCharsLeft
&& (run
= ME_FindItemFwd(run
, diRunOrStartRow
))
4181 && run
->type
== diRun
)
4183 WCHAR
*str
= get_text( &run
->member
.run
, 0 );
4186 nCopy
= min(nCharsLeft
, run
->member
.run
.len
);
4189 memcpy(dest
, str
, nCopy
* sizeof(WCHAR
));
4191 nCopy
= WideCharToMultiByte(CP_ACP
, 0, str
, nCopy
, dest
,
4192 nCharsLeft
, NULL
, NULL
);
4193 dest
+= nCopy
* (unicode
? sizeof(WCHAR
) : 1);
4194 nCharsLeft
-= nCopy
;
4197 /* append line termination, space allowing */
4201 *((WCHAR
*)dest
) = '\0';
4208 TRACE("EM_GETLINE: got %u characters\n", nMaxChars
- nCharsLeft
);
4209 return nMaxChars
- nCharsLeft
- (wroteNull
? 1 : 0);
4211 case EM_GETLINECOUNT
:
4213 ME_DisplayItem
*item
= editor
->pBuffer
->pFirst
->next
;
4216 ME_DisplayItem
*prev_para
= NULL
, *last_para
= NULL
;
4218 while (item
!= editor
->pBuffer
->pLast
)
4220 assert(item
->type
== diParagraph
);
4221 prev_para
= ME_FindItemBack(item
, diRun
);
4223 assert(prev_para
->member
.run
.nFlags
& MERF_ENDPARA
);
4225 nRows
+= item
->member
.para
.nRows
;
4226 item
= item
->member
.para
.next_para
;
4228 last_para
= ME_FindItemBack(item
, diRun
);
4230 assert(last_para
->member
.run
.nFlags
& MERF_ENDPARA
);
4231 if (editor
->bEmulateVersion10
&& prev_para
&&
4232 last_para
->member
.run
.nCharOfs
== 0 &&
4233 prev_para
->member
.run
.len
== 1 &&
4234 *get_text( &prev_para
->member
.run
, 0 ) == '\r')
4236 /* In 1.0 emulation, the last solitary \r at the very end of the text
4237 (if one exists) is NOT a line break.
4238 FIXME: this is an ugly hack. This should have a more regular model. */
4242 TRACE("EM_GETLINECOUNT: nRows==%d\n", nRows
);
4243 return max(1, nRows
);
4245 case EM_LINEFROMCHAR
:
4248 return ME_RowNumberFromCharOfs(editor
, ME_GetCursorOfs(&editor
->pCursors
[1]));
4250 return ME_RowNumberFromCharOfs(editor
, wParam
);
4252 case EM_EXLINEFROMCHAR
:
4255 return ME_RowNumberFromCharOfs(editor
, ME_GetCursorOfs(&editor
->pCursors
[1]));
4257 return ME_RowNumberFromCharOfs(editor
, lParam
);
4261 ME_DisplayItem
*item
, *para
;
4265 item
= ME_FindItemBack(editor
->pCursors
[0].pRun
, diStartRow
);
4267 item
= ME_FindRowWithNumber(editor
, wParam
);
4270 para
= ME_GetParagraph(item
);
4271 item
= ME_FindItemFwd(item
, diRun
);
4272 nCharOfs
= para
->member
.para
.nCharOfs
+ item
->member
.run
.nCharOfs
;
4273 TRACE("EM_LINEINDEX: nCharOfs==%d\n", nCharOfs
);
4278 ME_DisplayItem
*item
, *item_end
;
4279 int nChars
= 0, nThisLineOfs
= 0, nNextLineOfs
= 0;
4280 ME_DisplayItem
*para
, *run
;
4282 if (wParam
> ME_GetTextLength(editor
))
4286 FIXME("EM_LINELENGTH: returning number of unselected characters on lines with selection unsupported.\n");
4289 ME_RunOfsFromCharOfs(editor
, wParam
, ¶
, &run
, NULL
);
4290 item
= ME_RowStart(run
);
4291 nThisLineOfs
= ME_CharOfsFromRunOfs(editor
, para
, ME_FindItemFwd(item
, diRun
), 0);
4292 item_end
= ME_FindItemFwd(item
, diStartRowOrParagraphOrEnd
);
4293 if (item_end
->type
== diStartRow
) {
4294 nNextLineOfs
= ME_CharOfsFromRunOfs(editor
, para
, ME_FindItemFwd(item_end
, diRun
), 0);
4296 ME_DisplayItem
*endRun
= ME_FindItemBack(item_end
, diRun
);
4297 assert(endRun
&& endRun
->member
.run
.nFlags
& MERF_ENDPARA
);
4298 nNextLineOfs
= item_end
->member
.para
.nCharOfs
- endRun
->member
.run
.len
;
4300 nChars
= nNextLineOfs
- nThisLineOfs
;
4301 TRACE("EM_LINELENGTH(%ld)==%d\n",wParam
, nChars
);
4304 case EM_EXLIMITTEXT
:
4306 if ((int)lParam
< 0)
4309 editor
->nTextLimit
= 65536;
4311 editor
->nTextLimit
= (int) lParam
;
4317 editor
->nTextLimit
= 65536;
4319 editor
->nTextLimit
= (int) wParam
;
4322 case EM_GETLIMITTEXT
:
4324 return editor
->nTextLimit
;
4330 FINDTEXTA
*ft
= (FINDTEXTA
*)lParam
;
4331 int nChars
= MultiByteToWideChar(CP_ACP
, 0, ft
->lpstrText
, -1, NULL
, 0);
4334 if ((tmp
= heap_alloc(nChars
* sizeof(*tmp
))) != NULL
)
4335 MultiByteToWideChar(CP_ACP
, 0, ft
->lpstrText
, -1, tmp
, nChars
);
4336 r
= ME_FindText(editor
, wParam
, &ft
->chrg
, tmp
, NULL
);
4339 FINDTEXTW
*ft
= (FINDTEXTW
*)lParam
;
4340 r
= ME_FindText(editor
, wParam
, &ft
->chrg
, ft
->lpstrText
, NULL
);
4348 FINDTEXTEXA
*ex
= (FINDTEXTEXA
*)lParam
;
4349 int nChars
= MultiByteToWideChar(CP_ACP
, 0, ex
->lpstrText
, -1, NULL
, 0);
4352 if ((tmp
= heap_alloc(nChars
* sizeof(*tmp
))) != NULL
)
4353 MultiByteToWideChar(CP_ACP
, 0, ex
->lpstrText
, -1, tmp
, nChars
);
4354 r
= ME_FindText(editor
, wParam
, &ex
->chrg
, tmp
, &ex
->chrgText
);
4357 FINDTEXTEXW
*ex
= (FINDTEXTEXW
*)lParam
;
4358 r
= ME_FindText(editor
, wParam
, &ex
->chrg
, ex
->lpstrText
, &ex
->chrgText
);
4364 FINDTEXTW
*ft
= (FINDTEXTW
*)lParam
;
4365 return ME_FindText(editor
, wParam
, &ft
->chrg
, ft
->lpstrText
, NULL
);
4367 case EM_FINDTEXTEXW
:
4369 FINDTEXTEXW
*ex
= (FINDTEXTEXW
*)lParam
;
4370 return ME_FindText(editor
, wParam
, &ex
->chrg
, ex
->lpstrText
, &ex
->chrgText
);
4373 if (!wParam
|| !lParam
)
4375 *(int *)wParam
= editor
->nZoomNumerator
;
4376 *(int *)lParam
= editor
->nZoomDenominator
;
4379 return ME_SetZoom(editor
, wParam
, lParam
);
4380 case EM_CHARFROMPOS
:
4383 if (ME_CharFromPos(editor
, ((POINTL
*)lParam
)->x
, ((POINTL
*)lParam
)->y
,
4385 return ME_GetCursorOfs(&cursor
);
4389 case EM_POSFROMCHAR
:
4391 ME_DisplayItem
*pPara
, *pRun
;
4392 int nCharOfs
, nOffset
, nLength
;
4396 /* detect which API version we're dealing with */
4397 if (wParam
>= 0x40000)
4399 nLength
= ME_GetTextLength(editor
);
4400 nCharOfs
= min(nCharOfs
, nLength
);
4401 nCharOfs
= max(nCharOfs
, 0);
4403 ME_RunOfsFromCharOfs(editor
, nCharOfs
, &pPara
, &pRun
, &nOffset
);
4404 assert(pRun
->type
== diRun
);
4405 pt
.y
= pRun
->member
.run
.pt
.y
;
4406 pt
.x
= pRun
->member
.run
.pt
.x
+ ME_PointFromChar(editor
, &pRun
->member
.run
, nOffset
, TRUE
);
4407 pt
.y
+= pPara
->member
.para
.pt
.y
+ editor
->rcFormat
.top
;
4408 pt
.x
+= editor
->rcFormat
.left
;
4410 pt
.x
-= editor
->horz_si
.nPos
;
4411 pt
.y
-= editor
->vert_si
.nPos
;
4413 if (wParam
>= 0x40000) {
4414 *(POINTL
*)wParam
= pt
;
4416 return (wParam
>= 0x40000) ? 0 : MAKELONG( pt
.x
, pt
.y
);
4419 return ME_WmCreate(editor
, lParam
, unicode
);
4421 ME_DestroyEditor(editor
);
4426 if (wParam
== (WPARAM
)editor
->hWnd
&& GetCursorPos(&cursor_pos
) &&
4427 ScreenToClient(editor
->hWnd
, &cursor_pos
))
4428 ME_LinkNotify(editor
, msg
, 0, MAKELPARAM(cursor_pos
.x
, cursor_pos
.y
));
4429 return ME_SetCursor(editor
);
4431 case WM_LBUTTONDBLCLK
:
4432 case WM_LBUTTONDOWN
:
4434 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
4435 if ((editor
->nEventMask
& ENM_MOUSEEVENTS
) &&
4436 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4438 ITextHost_TxSetFocus(editor
->texthost
);
4439 ME_LButtonDown(editor
, (short)LOWORD(lParam
), (short)HIWORD(lParam
),
4440 ME_CalculateClickCount(editor
, msg
, wParam
, lParam
));
4441 ITextHost_TxSetCapture(editor
->texthost
, TRUE
);
4442 editor
->bMouseCaptured
= TRUE
;
4443 ME_LinkNotify(editor
, msg
, wParam
, lParam
);
4444 if (!ME_SetCursor(editor
)) goto do_default
;
4448 if ((editor
->nEventMask
& ENM_MOUSEEVENTS
) &&
4449 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4451 if (editor
->bMouseCaptured
)
4452 ME_MouseMove(editor
, (short)LOWORD(lParam
), (short)HIWORD(lParam
));
4454 ME_LinkNotify(editor
, msg
, wParam
, lParam
);
4455 /* Set cursor if mouse is captured, since WM_SETCURSOR won't be received. */
4456 if (editor
->bMouseCaptured
)
4457 ME_SetCursor(editor
);
4460 if (editor
->bMouseCaptured
) {
4461 ITextHost_TxSetCapture(editor
->texthost
, FALSE
);
4462 editor
->bMouseCaptured
= FALSE
;
4464 if (editor
->nSelectionType
== stDocument
)
4465 editor
->nSelectionType
= stPosition
;
4466 if ((editor
->nEventMask
& ENM_MOUSEEVENTS
) &&
4467 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4471 ME_SetCursor(editor
);
4472 ME_LinkNotify(editor
, msg
, wParam
, lParam
);
4476 case WM_RBUTTONDOWN
:
4477 case WM_RBUTTONDBLCLK
:
4478 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
4479 if ((editor
->nEventMask
& ENM_MOUSEEVENTS
) &&
4480 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4482 ME_LinkNotify(editor
, msg
, wParam
, lParam
);
4484 case WM_CONTEXTMENU
:
4485 if (!ME_ShowContextMenu(editor
, (short)LOWORD(lParam
), (short)HIWORD(lParam
)))
4489 editor
->bHaveFocus
= TRUE
;
4490 ME_ShowCaret(editor
);
4491 ME_SendOldNotify(editor
, EN_SETFOCUS
);
4492 if (!editor
->bHideSelection
&& !(editor
->styleFlags
& ES_NOHIDESEL
))
4493 ME_InvalidateSelection( editor
);
4496 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
4497 editor
->bHaveFocus
= FALSE
;
4498 editor
->wheel_remain
= 0;
4499 ME_HideCaret(editor
);
4500 ME_SendOldNotify(editor
, EN_KILLFOCUS
);
4501 if (!editor
->bHideSelection
&& !(editor
->styleFlags
& ES_NOHIDESEL
))
4502 ME_InvalidateSelection( editor
);
4505 TRACE("editor wnd command = %d\n", LOWORD(wParam
));
4508 if ((editor
->nEventMask
& ENM_KEYEVENTS
) &&
4509 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4513 if ((editor
->nEventMask
& ENM_KEYEVENTS
) &&
4514 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4516 if (ME_KeyDown(editor
, LOWORD(wParam
)))
4520 if ((editor
->nEventMask
& ENM_KEYEVENTS
) &&
4521 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4523 return ME_Char(editor
, wParam
, lParam
, unicode
);
4527 if(wParam
== UNICODE_NOCHAR
) return TRUE
;
4528 if(wParam
<= 0x000fffff)
4530 if(wParam
> 0xffff) /* convert to surrogates */
4533 ME_Char(editor
, (wParam
>> 10) + 0xd800, 0, TRUE
);
4534 ME_Char(editor
, (wParam
& 0x03ff) + 0xdc00, 0, TRUE
);
4536 ME_Char(editor
, wParam
, 0, TRUE
);
4542 case EM_STOPGROUPTYPING
:
4543 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
4547 const int scrollUnit
= 7;
4549 switch(LOWORD(wParam
))
4552 ME_ScrollAbs(editor
, 0, 0);
4555 ME_ScrollAbs(editor
,
4556 editor
->horz_si
.nMax
- (int)editor
->horz_si
.nPage
,
4557 editor
->vert_si
.nMax
- (int)editor
->vert_si
.nPage
);
4560 ME_ScrollLeft(editor
, scrollUnit
);
4563 ME_ScrollRight(editor
, scrollUnit
);
4566 ME_ScrollLeft(editor
, editor
->sizeWindow
.cx
);
4569 ME_ScrollRight(editor
, editor
->sizeWindow
.cx
);
4572 case SB_THUMBPOSITION
:
4574 int pos
= HIWORD(wParam
);
4575 if (editor
->horz_si
.nMax
> 0xffff)
4576 pos
= MulDiv(pos
, editor
->horz_si
.nMax
, 0xffff);
4577 ME_HScrollAbs(editor
, pos
);
4583 case EM_SCROLL
: /* fall through */
4587 int lineHeight
= get_default_line_height( editor
);
4589 origNPos
= editor
->vert_si
.nPos
;
4591 switch(LOWORD(wParam
))
4594 ME_ScrollAbs(editor
, 0, 0);
4597 ME_ScrollAbs(editor
,
4598 editor
->horz_si
.nMax
- (int)editor
->horz_si
.nPage
,
4599 editor
->vert_si
.nMax
- (int)editor
->vert_si
.nPage
);
4602 ME_ScrollUp(editor
,lineHeight
);
4605 ME_ScrollDown(editor
,lineHeight
);
4608 ME_ScrollUp(editor
,editor
->sizeWindow
.cy
);
4611 ME_ScrollDown(editor
,editor
->sizeWindow
.cy
);
4614 case SB_THUMBPOSITION
:
4616 int pos
= HIWORD(wParam
);
4617 if (editor
->vert_si
.nMax
> 0xffff)
4618 pos
= MulDiv(pos
, editor
->vert_si
.nMax
, 0xffff);
4619 ME_VScrollAbs(editor
, pos
);
4623 if (msg
== EM_SCROLL
)
4624 return 0x00010000 | (((editor
->vert_si
.nPos
- origNPos
)/lineHeight
) & 0xffff);
4632 if ((editor
->nEventMask
& ENM_MOUSEEVENTS
) &&
4633 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4636 ctrl_is_down
= GetKeyState(VK_CONTROL
) & 0x8000;
4638 delta
= GET_WHEEL_DELTA_WPARAM(wParam
);
4640 /* if scrolling changes direction, ignore left overs */
4641 if ((delta
< 0 && editor
->wheel_remain
< 0) ||
4642 (delta
> 0 && editor
->wheel_remain
> 0))
4643 editor
->wheel_remain
+= delta
;
4645 editor
->wheel_remain
= delta
;
4647 if (editor
->wheel_remain
)
4651 if (!editor
->nZoomNumerator
|| !editor
->nZoomDenominator
)
4655 numerator
= editor
->nZoomNumerator
* 100 / editor
->nZoomDenominator
;
4657 numerator
+= calc_wheel_change( &editor
->wheel_remain
, 10 );
4658 if (numerator
>= 10 && numerator
<= 500)
4659 ME_SetZoom(editor
, numerator
, 100);
4664 SystemParametersInfoW( SPI_GETWHEELSCROLLLINES
, 0, &max_lines
, 0 );
4666 lines
= calc_wheel_change( &editor
->wheel_remain
, (int)max_lines
);
4668 ME_ScrollDown( editor
, -lines
* get_default_line_height( editor
) );
4675 *((RECT
*)lParam
) = editor
->rcFormat
;
4676 if (editor
->bDefaultFormatRect
)
4677 ((RECT
*)lParam
)->left
-= editor
->selofs
;
4687 RECT
*rc
= (RECT
*)lParam
;
4689 border
= editor
->exStyleFlags
& WS_EX_CLIENTEDGE
? 1 : 0;
4690 ITextHost_TxGetClientRect(editor
->texthost
, &clientRect
);
4693 editor
->rcFormat
.top
= max(0, rc
->top
- border
);
4694 editor
->rcFormat
.left
= max(0, rc
->left
- border
);
4695 editor
->rcFormat
.bottom
= min(clientRect
.bottom
, rc
->bottom
);
4696 editor
->rcFormat
.right
= min(clientRect
.right
, rc
->right
+ border
);
4697 } else if (wParam
== 1) {
4698 /* MSDN incorrectly says a wParam value of 1 causes the
4699 * lParam rect to be used as a relative offset,
4700 * however, the tests show it just prevents min/max bound
4702 editor
->rcFormat
.top
= rc
->top
- border
;
4703 editor
->rcFormat
.left
= rc
->left
- border
;
4704 editor
->rcFormat
.bottom
= rc
->bottom
;
4705 editor
->rcFormat
.right
= rc
->right
+ border
;
4709 editor
->bDefaultFormatRect
= FALSE
;
4713 ME_SetDefaultFormatRect(editor
);
4714 editor
->bDefaultFormatRect
= TRUE
;
4716 ME_MarkAllForWrapping(editor
);
4717 ME_WrapMarkedParagraphs(editor
);
4718 ME_UpdateScrollBar(editor
);
4719 if (msg
!= EM_SETRECTNP
)
4723 case EM_REQUESTRESIZE
:
4724 ME_SendRequestResize(editor
, TRUE
);
4728 case WM_WINDOWPOSCHANGED
:
4731 WINDOWPOS
*winpos
= (WINDOWPOS
*)lParam
;
4733 if (winpos
->flags
& SWP_NOCLIENTSIZE
) goto do_default
;
4734 ITextHost_TxGetClientRect(editor
->texthost
, &clientRect
);
4735 if (editor
->bDefaultFormatRect
) {
4736 ME_SetDefaultFormatRect(editor
);
4738 editor
->rcFormat
.right
+= clientRect
.right
- editor
->prevClientRect
.right
;
4739 editor
->rcFormat
.bottom
+= clientRect
.bottom
- editor
->prevClientRect
.bottom
;
4741 editor
->prevClientRect
= clientRect
;
4742 ME_RewrapRepaint(editor
);
4745 /* IME messages to make richedit controls IME aware */
4746 case WM_IME_SETCONTEXT
:
4747 case WM_IME_CONTROL
:
4749 case WM_IME_COMPOSITIONFULL
:
4751 case WM_IME_STARTCOMPOSITION
:
4753 editor
->imeStartIndex
=ME_GetCursorOfs(&editor
->pCursors
[0]);
4754 ME_DeleteSelection(editor
);
4755 ME_CommitUndo(editor
);
4756 ME_UpdateRepaint(editor
, FALSE
);
4759 case WM_IME_COMPOSITION
:
4763 ME_Style
*style
= ME_GetInsertStyle(editor
, 0);
4764 hIMC
= ITextHost_TxImmGetContext(editor
->texthost
);
4765 ME_DeleteSelection(editor
);
4766 ME_SaveTempStyle(editor
, style
);
4767 if (lParam
& (GCS_RESULTSTR
|GCS_COMPSTR
))
4769 LPWSTR lpCompStr
= NULL
;
4771 DWORD dwIndex
= lParam
& GCS_RESULTSTR
;
4773 dwIndex
= GCS_COMPSTR
;
4775 dwBufLen
= ImmGetCompositionStringW(hIMC
, dwIndex
, NULL
, 0);
4776 lpCompStr
= HeapAlloc(GetProcessHeap(),0,dwBufLen
+ sizeof(WCHAR
));
4777 ImmGetCompositionStringW(hIMC
, dwIndex
, lpCompStr
, dwBufLen
);
4778 lpCompStr
[dwBufLen
/sizeof(WCHAR
)] = 0;
4779 ME_InsertTextFromCursor(editor
,0,lpCompStr
,dwBufLen
/sizeof(WCHAR
),style
);
4780 HeapFree(GetProcessHeap(), 0, lpCompStr
);
4782 if (dwIndex
== GCS_COMPSTR
)
4783 ME_SetSelection(editor
,editor
->imeStartIndex
,
4784 editor
->imeStartIndex
+ dwBufLen
/sizeof(WCHAR
));
4786 ME_ReleaseStyle(style
);
4787 ME_CommitUndo(editor
);
4788 ME_UpdateRepaint(editor
, FALSE
);
4791 case WM_IME_ENDCOMPOSITION
:
4793 ME_DeleteSelection(editor
);
4794 editor
->imeStartIndex
=-1;
4797 case EM_GETOLEINTERFACE
:
4800 if (!CreateIRichEditOle(NULL
, editor
, (LPVOID
*)&editor
->reOle
))
4802 *(LPVOID
*)lParam
= editor
->reOle
;
4803 IRichEditOle_AddRef(editor
->reOle
);
4806 case EM_GETPASSWORDCHAR
:
4808 return editor
->cPasswordMask
;
4810 case EM_SETOLECALLBACK
:
4811 if(editor
->lpOleCallback
)
4812 IRichEditOleCallback_Release(editor
->lpOleCallback
);
4813 editor
->lpOleCallback
= (IRichEditOleCallback
*)lParam
;
4814 if(editor
->lpOleCallback
)
4815 IRichEditOleCallback_AddRef(editor
->lpOleCallback
);
4817 case EM_GETWORDBREAKPROC
:
4818 return (LRESULT
)editor
->pfnWordBreak
;
4819 case EM_SETWORDBREAKPROC
:
4821 EDITWORDBREAKPROCW pfnOld
= editor
->pfnWordBreak
;
4823 editor
->pfnWordBreak
= (EDITWORDBREAKPROCW
)lParam
;
4824 return (LRESULT
)pfnOld
;
4826 case EM_GETTEXTMODE
:
4827 return editor
->mode
;
4828 case EM_SETTEXTMODE
:
4833 if (ME_GetTextLength(editor
) ||
4834 !list_empty( &editor
->undo_stack
) || !list_empty( &editor
->redo_stack
))
4835 return E_UNEXPECTED
;
4837 /* Check for mutually exclusive flags in adjacent bits of wParam */
4838 if ((wParam
& (TM_RICHTEXT
| TM_MULTILEVELUNDO
| TM_MULTICODEPAGE
)) &
4839 (wParam
& (TM_PLAINTEXT
| TM_SINGLELEVELUNDO
| TM_SINGLECODEPAGE
)) << 1)
4840 return E_INVALIDARG
;
4842 if (wParam
& (TM_RICHTEXT
| TM_PLAINTEXT
))
4844 mask
|= TM_RICHTEXT
| TM_PLAINTEXT
;
4845 changes
|= wParam
& (TM_RICHTEXT
| TM_PLAINTEXT
);
4846 if (wParam
& TM_PLAINTEXT
) {
4847 /* Clear selection since it should be possible to select the
4848 * end of text run for rich text */
4849 ME_InvalidateSelection(editor
);
4850 ME_SetCursorToStart(editor
, &editor
->pCursors
[0]);
4851 editor
->pCursors
[1] = editor
->pCursors
[0];
4852 /* plain text can only have the default style. */
4853 ME_ClearTempStyle(editor
);
4854 ME_AddRefStyle(editor
->pBuffer
->pDefaultStyle
);
4855 ME_ReleaseStyle(editor
->pCursors
[0].pRun
->member
.run
.style
);
4856 editor
->pCursors
[0].pRun
->member
.run
.style
= editor
->pBuffer
->pDefaultStyle
;
4859 /* FIXME: Currently no support for undo level and code page options */
4860 editor
->mode
= (editor
->mode
& ~mask
) | changes
;
4863 case EM_SETPASSWORDCHAR
:
4865 editor
->cPasswordMask
= wParam
;
4866 ME_RewrapRepaint(editor
);
4869 case EM_SETTARGETDEVICE
:
4872 BOOL
new = (lParam
== 0 && (editor
->styleFlags
& ES_MULTILINE
));
4873 if (editor
->nAvailWidth
|| editor
->bWordWrap
!= new)
4875 editor
->bWordWrap
= new;
4876 editor
->nAvailWidth
= 0; /* wrap to client area */
4877 ME_RewrapRepaint(editor
);
4880 int width
= max(0, lParam
);
4881 if ((editor
->styleFlags
& ES_MULTILINE
) &&
4882 (!editor
->bWordWrap
|| editor
->nAvailWidth
!= width
))
4884 editor
->nAvailWidth
= width
;
4885 editor
->bWordWrap
= TRUE
;
4886 ME_RewrapRepaint(editor
);
4888 FIXME("EM_SETTARGETDEVICE doesn't use non-NULL target devices\n");
4893 *phresult
= S_FALSE
;
4899 static BOOL
create_windowed_editor(HWND hwnd
, CREATESTRUCTW
*create
, BOOL emulate_10
)
4901 ITextHost
*host
= ME_CreateTextHost( hwnd
, create
, emulate_10
);
4902 ME_TextEditor
*editor
;
4904 if (!host
) return FALSE
;
4906 editor
= ME_MakeEditor( host
, emulate_10
);
4909 ITextHost_Release( host
);
4913 editor
->exStyleFlags
= GetWindowLongW( hwnd
, GWL_EXSTYLE
);
4914 editor
->styleFlags
|= GetWindowLongW( hwnd
, GWL_STYLE
) & ES_WANTRETURN
;
4915 editor
->hWnd
= hwnd
; /* FIXME: Remove editor's dependence on hWnd */
4916 editor
->hwndParent
= create
->hwndParent
;
4918 SetWindowLongPtrW( hwnd
, 0, (LONG_PTR
)editor
);
4923 static LRESULT
RichEditWndProc_common(HWND hWnd
, UINT msg
, WPARAM wParam
,
4924 LPARAM lParam
, BOOL unicode
)
4926 ME_TextEditor
*editor
;
4928 LRESULT lresult
= 0;
4930 TRACE("enter hwnd %p msg %04x (%s) %lx %lx, unicode %d\n",
4931 hWnd
, msg
, get_msg_name(msg
), wParam
, lParam
, unicode
);
4933 editor
= (ME_TextEditor
*)GetWindowLongPtrW(hWnd
, 0);
4936 if (msg
== WM_NCCREATE
)
4938 CREATESTRUCTW
*pcs
= (CREATESTRUCTW
*)lParam
;
4940 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd
, pcs
->style
);
4941 return create_windowed_editor( hWnd
, pcs
, FALSE
);
4945 return DefWindowProcW(hWnd
, msg
, wParam
, lParam
);
4957 hDC
= BeginPaint(editor
->hWnd
, &ps
);
4958 if (!editor
->bEmulateVersion10
|| (editor
->nEventMask
& ENM_UPDATE
))
4959 ME_SendOldNotify(editor
, EN_UPDATE
);
4960 /* Erase area outside of the formatting rectangle */
4961 if (ps
.rcPaint
.top
< editor
->rcFormat
.top
)
4964 rc
.bottom
= editor
->rcFormat
.top
;
4965 FillRect(hDC
, &rc
, editor
->hbrBackground
);
4966 ps
.rcPaint
.top
= editor
->rcFormat
.top
;
4968 if (ps
.rcPaint
.bottom
> editor
->rcFormat
.bottom
) {
4970 rc
.top
= editor
->rcFormat
.bottom
;
4971 FillRect(hDC
, &rc
, editor
->hbrBackground
);
4972 ps
.rcPaint
.bottom
= editor
->rcFormat
.bottom
;
4974 if (ps
.rcPaint
.left
< editor
->rcFormat
.left
) {
4976 rc
.right
= editor
->rcFormat
.left
;
4977 FillRect(hDC
, &rc
, editor
->hbrBackground
);
4978 ps
.rcPaint
.left
= editor
->rcFormat
.left
;
4980 if (ps
.rcPaint
.right
> editor
->rcFormat
.right
) {
4982 rc
.left
= editor
->rcFormat
.right
;
4983 FillRect(hDC
, &rc
, editor
->hbrBackground
);
4984 ps
.rcPaint
.right
= editor
->rcFormat
.right
;
4987 ME_PaintContent(editor
, hDC
, &ps
.rcPaint
);
4988 EndPaint(editor
->hWnd
, &ps
);
4993 HDC hDC
= (HDC
)wParam
;
4996 if (GetUpdateRect(editor
->hWnd
, &rc
, TRUE
))
4997 FillRect(hDC
, &rc
, editor
->hbrBackground
);
5003 const DWORD mask
= ECO_VERTICAL
| ECO_AUTOHSCROLL
| ECO_AUTOVSCROLL
|
5004 ECO_NOHIDESEL
| ECO_READONLY
| ECO_WANTRETURN
|
5006 lresult
= ME_HandleMessage(editor
, msg
, wParam
, lParam
, unicode
, &hresult
);
5007 dwStyle
= GetWindowLongW(hWnd
, GWL_STYLE
);
5008 dwStyle
= (dwStyle
& ~mask
) | (lresult
& mask
);
5009 SetWindowLongW(hWnd
, GWL_STYLE
, dwStyle
);
5012 case EM_SETREADONLY
:
5015 lresult
= ME_HandleMessage(editor
, msg
, wParam
, lParam
, unicode
, &hresult
);
5016 dwStyle
= GetWindowLongW(hWnd
, GWL_STYLE
);
5017 dwStyle
&= ~ES_READONLY
;
5019 dwStyle
|= ES_READONLY
;
5020 SetWindowLongW(hWnd
, GWL_STYLE
, dwStyle
);
5024 lresult
= ME_HandleMessage(editor
, msg
, wParam
, lParam
, unicode
, &hresult
);
5027 if (hresult
== S_FALSE
)
5028 lresult
= DefWindowProcW(hWnd
, msg
, wParam
, lParam
);
5030 TRACE("exit hwnd %p msg %04x (%s) %lx %lx, unicode %d -> %lu\n",
5031 hWnd
, msg
, get_msg_name(msg
), wParam
, lParam
, unicode
, lresult
);
5036 static LRESULT WINAPI
RichEditWndProcW(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
5038 BOOL unicode
= TRUE
;
5040 /* Under Win9x RichEdit20W returns ANSI strings, see the tests. */
5041 if (msg
== WM_GETTEXT
&& (GetVersion() & 0x80000000))
5044 return RichEditWndProc_common(hWnd
, msg
, wParam
, lParam
, unicode
);
5047 static LRESULT WINAPI
RichEditWndProcA(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
5049 return RichEditWndProc_common(hWnd
, msg
, wParam
, lParam
, FALSE
);
5052 /******************************************************************
5053 * RichEditANSIWndProc (RICHED20.10)
5055 LRESULT WINAPI
RichEditANSIWndProc(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
5057 return RichEditWndProcA(hWnd
, msg
, wParam
, lParam
);
5060 /******************************************************************
5061 * RichEdit10ANSIWndProc (RICHED20.9)
5063 LRESULT WINAPI
RichEdit10ANSIWndProc(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
5065 if (msg
== WM_NCCREATE
&& !GetWindowLongPtrW(hWnd
, 0))
5067 CREATESTRUCTW
*pcs
= (CREATESTRUCTW
*)lParam
;
5069 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd
, pcs
->style
);
5070 return create_windowed_editor( hWnd
, pcs
, TRUE
);
5072 return RichEditANSIWndProc(hWnd
, msg
, wParam
, lParam
);
5075 void ME_SendOldNotify(ME_TextEditor
*editor
, int nCode
)
5077 ITextHost_TxNotify(editor
->texthost
, nCode
, NULL
);
5080 /* Fill buffer with srcChars unicode characters from the start cursor.
5082 * buffer: destination buffer
5083 * buflen: length of buffer in characters excluding the NULL terminator.
5084 * start: start of editor text to copy into buffer.
5085 * srcChars: Number of characters to use from the editor text.
5086 * bCRLF: if true, replaces all end of lines with \r\n pairs.
5088 * returns the number of characters written excluding the NULL terminator.
5090 * The written text is always NULL terminated.
5092 int ME_GetTextW(ME_TextEditor
*editor
, WCHAR
*buffer
, int buflen
,
5093 const ME_Cursor
*start
, int srcChars
, BOOL bCRLF
,
5096 ME_DisplayItem
*pRun
, *pNextRun
;
5097 const WCHAR
*pStart
= buffer
;
5098 const WCHAR cr_lf
[] = {'\r', '\n', 0};
5102 /* bCRLF flag is only honored in 2.0 and up. 1.0 must always return text verbatim */
5103 if (editor
->bEmulateVersion10
) bCRLF
= FALSE
;
5107 pNextRun
= ME_FindItemFwd(pRun
, diRun
);
5109 nLen
= pRun
->member
.run
.len
- start
->nOffset
;
5110 str
= get_text( &pRun
->member
.run
, start
->nOffset
);
5112 while (srcChars
&& buflen
&& pNextRun
)
5114 int nFlags
= pRun
->member
.run
.nFlags
;
5116 if (bCRLF
&& nFlags
& MERF_ENDPARA
&& ~nFlags
& MERF_ENDCELL
)
5118 if (buflen
== 1) break;
5119 /* FIXME: native fails to reduce srcChars here for WM_GETTEXT or
5120 * EM_GETTEXTEX, however, this is done for copying text which
5121 * also uses this function. */
5122 srcChars
-= min(nLen
, srcChars
);
5126 nLen
= min(nLen
, srcChars
);
5130 nLen
= min(nLen
, buflen
);
5133 CopyMemory(buffer
, str
, sizeof(WCHAR
) * nLen
);
5138 pNextRun
= ME_FindItemFwd(pRun
, diRun
);
5140 nLen
= pRun
->member
.run
.len
;
5141 str
= get_text( &pRun
->member
.run
, 0 );
5143 /* append '\r' to the last paragraph. */
5144 if (pRun
->next
->type
== diTextEnd
&& bEOP
)
5150 return buffer
- pStart
;
5153 static BOOL
ME_RegisterEditorClass(HINSTANCE hInstance
)
5158 wcW
.style
= CS_DBLCLKS
| CS_HREDRAW
| CS_VREDRAW
| CS_GLOBALCLASS
;
5159 wcW
.lpfnWndProc
= RichEditWndProcW
;
5161 wcW
.cbWndExtra
= sizeof(ME_TextEditor
*);
5162 wcW
.hInstance
= NULL
; /* hInstance would register DLL-local class */
5164 wcW
.hCursor
= LoadCursorW(NULL
, (LPWSTR
)IDC_IBEAM
);
5165 wcW
.hbrBackground
= GetStockObject(NULL_BRUSH
);
5166 wcW
.lpszMenuName
= NULL
;
5168 if (is_version_nt())
5170 wcW
.lpszClassName
= RICHEDIT_CLASS20W
;
5171 if (!RegisterClassW(&wcW
)) return FALSE
;
5172 wcW
.lpszClassName
= MSFTEDIT_CLASS
;
5173 if (!RegisterClassW(&wcW
)) return FALSE
;
5177 /* WNDCLASSA/W have the same layout */
5178 wcW
.lpszClassName
= (LPCWSTR
)"RichEdit20W";
5179 if (!RegisterClassA((WNDCLASSA
*)&wcW
)) return FALSE
;
5180 wcW
.lpszClassName
= (LPCWSTR
)"RichEdit50W";
5181 if (!RegisterClassA((WNDCLASSA
*)&wcW
)) return FALSE
;
5184 wcA
.style
= CS_DBLCLKS
| CS_HREDRAW
| CS_VREDRAW
| CS_GLOBALCLASS
;
5185 wcA
.lpfnWndProc
= RichEditWndProcA
;
5187 wcA
.cbWndExtra
= sizeof(ME_TextEditor
*);
5188 wcA
.hInstance
= NULL
; /* hInstance would register DLL-local class */
5190 wcA
.hCursor
= LoadCursorW(NULL
, (LPWSTR
)IDC_IBEAM
);
5191 wcA
.hbrBackground
= GetStockObject(NULL_BRUSH
);
5192 wcA
.lpszMenuName
= NULL
;
5193 wcA
.lpszClassName
= RICHEDIT_CLASS20A
;
5194 if (!RegisterClassA(&wcA
)) return FALSE
;
5195 wcA
.lpszClassName
= "RichEdit50A";
5196 if (!RegisterClassA(&wcA
)) return FALSE
;
5201 static LRESULT WINAPI
REComboWndProc(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
) {
5202 /* FIXME: Not implemented */
5203 TRACE("hWnd %p msg %04x (%s) %08lx %08lx\n",
5204 hWnd
, msg
, get_msg_name(msg
), wParam
, lParam
);
5205 return DefWindowProcW(hWnd
, msg
, wParam
, lParam
);
5208 static LRESULT WINAPI
REListWndProc(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
) {
5209 /* FIXME: Not implemented */
5210 TRACE("hWnd %p msg %04x (%s) %08lx %08lx\n",
5211 hWnd
, msg
, get_msg_name(msg
), wParam
, lParam
);
5212 return DefWindowProcW(hWnd
, msg
, wParam
, lParam
);
5215 /******************************************************************
5216 * REExtendedRegisterClass (RICHED20.8)
5218 * FIXME undocumented
5219 * Need to check for errors and implement controls and callbacks
5221 LRESULT WINAPI
REExtendedRegisterClass(void)
5226 FIXME("semi stub\n");
5230 wcW
.hInstance
= NULL
;
5233 wcW
.hbrBackground
= (HBRUSH
)(COLOR_WINDOW
+1);
5234 wcW
.lpszMenuName
= NULL
;
5236 if (!ME_ListBoxRegistered
)
5238 wcW
.style
= CS_PARENTDC
| CS_DBLCLKS
| CS_GLOBALCLASS
;
5239 wcW
.lpfnWndProc
= REListWndProc
;
5240 wcW
.lpszClassName
= REListBox20W
;
5241 if (RegisterClassW(&wcW
)) ME_ListBoxRegistered
= TRUE
;
5244 if (!ME_ComboBoxRegistered
)
5246 wcW
.style
= CS_PARENTDC
| CS_DBLCLKS
| CS_GLOBALCLASS
| CS_VREDRAW
| CS_HREDRAW
;
5247 wcW
.lpfnWndProc
= REComboWndProc
;
5248 wcW
.lpszClassName
= REComboBox20W
;
5249 if (RegisterClassW(&wcW
)) ME_ComboBoxRegistered
= TRUE
;
5253 if (ME_ListBoxRegistered
)
5255 if (ME_ComboBoxRegistered
)
5261 static int wchar_comp( const void *key
, const void *elem
)
5263 return *(const WCHAR
*)key
- *(const WCHAR
*)elem
;
5266 /* neutral characters end the url if the next non-neutral character is a space character,
5267 otherwise they are included in the url. */
5268 static BOOL
isurlneutral( WCHAR c
)
5270 /* NB this list is sorted */
5271 static const WCHAR neutral_chars
[] = {'!','\"','\'','(',')',',','-','.',':',';','<','>','?','[',']','{','}'};
5273 /* Some shortcuts */
5274 if (isalnum( c
)) return FALSE
;
5275 if (c
> neutral_chars
[ARRAY_SIZE( neutral_chars
) - 1]) return FALSE
;
5277 return !!bsearch( &c
, neutral_chars
, ARRAY_SIZE( neutral_chars
), sizeof(c
), wchar_comp
);
5281 * This proc takes a selection, and scans it forward in order to select the span
5282 * of a possible URL candidate. A possible URL candidate must start with isalnum
5283 * or one of the following special characters: *|/\+%#@ and must consist entirely
5284 * of the characters allowed to start the URL, plus : (colon) which may occur
5285 * at most once, and not at either end.
5287 static BOOL
ME_FindNextURLCandidate(ME_TextEditor
*editor
,
5288 const ME_Cursor
*start
,
5290 ME_Cursor
*candidate_min
,
5291 ME_Cursor
*candidate_max
)
5293 ME_Cursor cursor
= *start
, neutral_end
, space_end
;
5294 BOOL candidateStarted
= FALSE
, quoted
= FALSE
;
5299 WCHAR
*str
= get_text( &cursor
.pRun
->member
.run
, 0 );
5300 int run_len
= cursor
.pRun
->member
.run
.len
;
5302 nChars
-= run_len
- cursor
.nOffset
;
5304 /* Find start of candidate */
5305 if (!candidateStarted
)
5307 while (cursor
.nOffset
< run_len
)
5309 c
= str
[cursor
.nOffset
];
5310 if (!isspaceW( c
) && !isurlneutral( c
))
5312 *candidate_min
= cursor
;
5313 candidateStarted
= TRUE
;
5314 neutral_end
.pPara
= NULL
;
5315 space_end
.pPara
= NULL
;
5319 quoted
= (c
== '<');
5324 /* Find end of candidate */
5325 if (candidateStarted
)
5327 while (cursor
.nOffset
< run_len
)
5329 c
= str
[cursor
.nOffset
];
5332 if (quoted
&& c
!= '\r')
5334 if (!space_end
.pPara
)
5336 if (neutral_end
.pPara
)
5337 space_end
= neutral_end
;
5345 else if (isurlneutral( c
))
5347 if (quoted
&& c
== '>')
5349 neutral_end
.pPara
= NULL
;
5350 space_end
.pPara
= NULL
;
5353 if (!neutral_end
.pPara
)
5354 neutral_end
= cursor
;
5357 neutral_end
.pPara
= NULL
;
5364 if (!ME_NextRun(&cursor
.pPara
, &cursor
.pRun
, TRUE
))
5369 if (candidateStarted
)
5371 if (space_end
.pPara
)
5372 *candidate_max
= space_end
;
5373 else if (neutral_end
.pPara
)
5374 *candidate_max
= neutral_end
;
5376 *candidate_max
= cursor
;
5379 *candidate_max
= *candidate_min
= cursor
;
5384 * This proc evaluates the selection and returns TRUE if it can be considered an URL
5386 static BOOL
ME_IsCandidateAnURL(ME_TextEditor
*editor
, const ME_Cursor
*start
, int nChars
)
5388 #define MAX_PREFIX_LEN 9
5390 const WCHAR text
[MAX_PREFIX_LEN
];
5393 {{'p','r','o','s','p','e','r','o',':'}, 9},
5394 {{'t','e','l','n','e','t',':'}, 7},
5395 {{'g','o','p','h','e','r',':'}, 7},
5396 {{'m','a','i','l','t','o',':'}, 7},
5397 {{'h','t','t','p','s',':'}, 6},
5398 {{'f','i','l','e',':'}, 5},
5399 {{'n','e','w','s',':'}, 5},
5400 {{'w','a','i','s',':'}, 5},
5401 {{'n','n','t','p',':'}, 5},
5402 {{'h','t','t','p',':'}, 5},
5403 {{'w','w','w','.'}, 4},
5404 {{'f','t','p',':'}, 4},
5406 WCHAR bufferW
[MAX_PREFIX_LEN
+ 1];
5409 ME_GetTextW(editor
, bufferW
, MAX_PREFIX_LEN
, start
, nChars
, FALSE
, FALSE
);
5410 for (i
= 0; i
< ARRAY_SIZE(prefixes
); i
++)
5412 if (nChars
< prefixes
[i
].length
) continue;
5413 if (!memcmp(prefixes
[i
].text
, bufferW
, prefixes
[i
].length
* sizeof(WCHAR
)))
5417 #undef MAX_PREFIX_LEN
5421 * This proc walks through the indicated selection and evaluates whether each
5422 * section identified by ME_FindNextURLCandidate and in-between sections have
5423 * their proper CFE_LINK attributes set or unset. If the CFE_LINK attribute is
5424 * not what it is supposed to be, this proc sets or unsets it as appropriate.
5426 * Since this function can cause runs to be split, do not depend on the value
5427 * of the start cursor at the end of the function.
5429 * nChars may be set to INT_MAX to update to the end of the text.
5431 * Returns TRUE if at least one section was modified.
5433 static BOOL
ME_UpdateLinkAttribute(ME_TextEditor
*editor
, ME_Cursor
*start
, int nChars
)
5435 BOOL modified
= FALSE
;
5436 ME_Cursor startCur
= *start
;
5438 if (!editor
->AutoURLDetect_bEnable
) return FALSE
;
5443 ME_Cursor candidateStart
, candidateEnd
;
5445 if (ME_FindNextURLCandidate(editor
, &startCur
, nChars
,
5446 &candidateStart
, &candidateEnd
))
5448 /* Section before candidate is not an URL */
5449 int cMin
= ME_GetCursorOfs(&candidateStart
);
5450 int cMax
= ME_GetCursorOfs(&candidateEnd
);
5452 if (!ME_IsCandidateAnURL(editor
, &candidateStart
, cMax
- cMin
))
5453 candidateStart
= candidateEnd
;
5454 nChars
-= cMax
- ME_GetCursorOfs(&startCur
);
5458 /* No more candidates until end of selection */
5462 if (startCur
.pRun
!= candidateStart
.pRun
||
5463 startCur
.nOffset
!= candidateStart
.nOffset
)
5465 /* CFE_LINK effect should be consistently unset */
5466 link
.cbSize
= sizeof(link
);
5467 ME_GetCharFormat(editor
, &startCur
, &candidateStart
, &link
);
5468 if (!(link
.dwMask
& CFM_LINK
) || (link
.dwEffects
& CFE_LINK
))
5470 /* CFE_LINK must be unset from this range */
5471 memset(&link
, 0, sizeof(CHARFORMAT2W
));
5472 link
.cbSize
= sizeof(link
);
5473 link
.dwMask
= CFM_LINK
;
5475 ME_SetCharFormat(editor
, &startCur
, &candidateStart
, &link
);
5476 /* Update candidateEnd since setting character formats may split
5477 * runs, which can cause a cursor to be at an invalid offset within
5479 while (candidateEnd
.nOffset
>= candidateEnd
.pRun
->member
.run
.len
)
5481 candidateEnd
.nOffset
-= candidateEnd
.pRun
->member
.run
.len
;
5482 candidateEnd
.pRun
= ME_FindItemFwd(candidateEnd
.pRun
, diRun
);
5487 if (candidateStart
.pRun
!= candidateEnd
.pRun
||
5488 candidateStart
.nOffset
!= candidateEnd
.nOffset
)
5490 /* CFE_LINK effect should be consistently set */
5491 link
.cbSize
= sizeof(link
);
5492 ME_GetCharFormat(editor
, &candidateStart
, &candidateEnd
, &link
);
5493 if (!(link
.dwMask
& CFM_LINK
) || !(link
.dwEffects
& CFE_LINK
))
5495 /* CFE_LINK must be set on this range */
5496 memset(&link
, 0, sizeof(CHARFORMAT2W
));
5497 link
.cbSize
= sizeof(link
);
5498 link
.dwMask
= CFM_LINK
;
5499 link
.dwEffects
= CFE_LINK
;
5500 ME_SetCharFormat(editor
, &candidateStart
, &candidateEnd
, &link
);
5504 startCur
= candidateEnd
;
5505 } while (nChars
> 0);