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
);
2430 static BOOL
handle_enter(ME_TextEditor
*editor
)
2432 BOOL ctrl_is_down
= GetKeyState(VK_CONTROL
) & 0x8000;
2433 BOOL shift_is_down
= GetKeyState(VK_SHIFT
) & 0x8000;
2435 if (editor
->bDialogMode
)
2440 if (!(editor
->styleFlags
& ES_WANTRETURN
))
2442 if (editor
->hwndParent
)
2445 dw
= SendMessageW(editor
->hwndParent
, DM_GETDEFID
, 0, 0);
2446 if (HIWORD(dw
) == DC_HASDEFID
)
2448 HWND hwDefCtrl
= GetDlgItem(editor
->hwndParent
, LOWORD(dw
));
2451 SendMessageW(editor
->hwndParent
, WM_NEXTDLGCTL
, (WPARAM
)hwDefCtrl
, TRUE
);
2452 PostMessageW(hwDefCtrl
, WM_KEYDOWN
, VK_RETURN
, 0);
2460 if (editor
->styleFlags
& ES_MULTILINE
)
2462 static const WCHAR endl
= '\r';
2463 static const WCHAR endlv10
[] = {'\r','\n'};
2464 ME_Cursor cursor
= editor
->pCursors
[0];
2465 ME_DisplayItem
*para
= cursor
.pPara
;
2467 ME_Style
*style
, *eop_style
;
2469 if (editor
->styleFlags
& ES_READONLY
)
2471 MessageBeep(MB_ICONERROR
);
2475 ME_GetSelectionOfs(editor
, &from
, &to
);
2476 if (editor
->nTextLimit
> ME_GetTextLength(editor
) - (to
-from
))
2478 if (!editor
->bEmulateVersion10
) /* v4.1 */
2480 if (para
->member
.para
.nFlags
& MEPF_ROWEND
)
2482 /* Add a new table row after this row. */
2483 para
= ME_AppendTableRow(editor
, para
);
2484 para
= para
->member
.para
.next_para
;
2485 editor
->pCursors
[0].pPara
= para
;
2486 editor
->pCursors
[0].pRun
= ME_FindItemFwd(para
, diRun
);
2487 editor
->pCursors
[0].nOffset
= 0;
2488 editor
->pCursors
[1] = editor
->pCursors
[0];
2489 ME_CommitUndo(editor
);
2490 ME_CheckTablesForCorruption(editor
);
2491 ME_UpdateRepaint(editor
, FALSE
);
2494 else if (para
== editor
->pCursors
[1].pPara
&&
2495 cursor
.nOffset
+ cursor
.pRun
->member
.run
.nCharOfs
== 0 &&
2496 para
->member
.para
.prev_para
->member
.para
.nFlags
& MEPF_ROWSTART
&&
2497 !para
->member
.para
.prev_para
->member
.para
.nCharOfs
)
2499 /* Insert a newline before the table. */
2500 para
= para
->member
.para
.prev_para
;
2501 para
->member
.para
.nFlags
&= ~MEPF_ROWSTART
;
2502 editor
->pCursors
[0].pPara
= para
;
2503 editor
->pCursors
[0].pRun
= ME_FindItemFwd(para
, diRun
);
2504 editor
->pCursors
[1] = editor
->pCursors
[0];
2505 ME_InsertTextFromCursor(editor
, 0, &endl
, 1,
2506 editor
->pCursors
[0].pRun
->member
.run
.style
);
2507 para
= editor
->pBuffer
->pFirst
->member
.para
.next_para
;
2508 ME_SetDefaultParaFormat(editor
, ¶
->member
.para
.fmt
);
2509 para
->member
.para
.nFlags
= 0;
2510 mark_para_rewrap(editor
, para
);
2511 editor
->pCursors
[0].pPara
= para
;
2512 editor
->pCursors
[0].pRun
= ME_FindItemFwd(para
, diRun
);
2513 editor
->pCursors
[1] = editor
->pCursors
[0];
2514 para
->member
.para
.next_para
->member
.para
.nFlags
|= MEPF_ROWSTART
;
2515 ME_CommitCoalescingUndo(editor
);
2516 ME_CheckTablesForCorruption(editor
);
2517 ME_UpdateRepaint(editor
, FALSE
);
2521 else /* v1.0 - 3.0 */
2523 ME_DisplayItem
*para
= cursor
.pPara
;
2524 if (ME_IsInTable(para
))
2526 if (cursor
.pRun
->member
.run
.nFlags
& MERF_ENDPARA
)
2530 ME_ContinueCoalescingTransaction(editor
);
2531 para
= ME_AppendTableRow(editor
, para
);
2532 editor
->pCursors
[0].pPara
= para
;
2533 editor
->pCursors
[0].pRun
= ME_FindItemFwd(para
, diRun
);
2534 editor
->pCursors
[0].nOffset
= 0;
2535 editor
->pCursors
[1] = editor
->pCursors
[0];
2536 ME_CommitCoalescingUndo(editor
);
2537 ME_UpdateRepaint(editor
, FALSE
);
2543 ME_ContinueCoalescingTransaction(editor
);
2544 if (cursor
.pRun
->member
.run
.nCharOfs
+ cursor
.nOffset
== 0 &&
2545 !ME_IsInTable(para
->member
.para
.prev_para
))
2547 /* Insert newline before table */
2548 cursor
.pRun
= ME_FindItemBack(para
, diRun
);
2551 editor
->pCursors
[0].pRun
= cursor
.pRun
;
2552 editor
->pCursors
[0].pPara
= para
->member
.para
.prev_para
;
2554 editor
->pCursors
[0].nOffset
= 0;
2555 editor
->pCursors
[1] = editor
->pCursors
[0];
2556 ME_InsertTextFromCursor(editor
, 0, &endl
, 1,
2557 editor
->pCursors
[0].pRun
->member
.run
.style
);
2561 editor
->pCursors
[1] = editor
->pCursors
[0];
2562 para
= ME_AppendTableRow(editor
, para
);
2563 editor
->pCursors
[0].pPara
= para
;
2564 editor
->pCursors
[0].pRun
= ME_FindItemFwd(para
, diRun
);
2565 editor
->pCursors
[0].nOffset
= 0;
2566 editor
->pCursors
[1] = editor
->pCursors
[0];
2568 ME_CommitCoalescingUndo(editor
);
2569 ME_UpdateRepaint(editor
, FALSE
);
2575 style
= ME_GetInsertStyle(editor
, 0);
2577 /* Normally the new eop style is the insert style, however in a list it is copied from the existing
2578 eop style (this prevents the list label style changing when the new eop is inserted).
2579 No extra ref is taken here on eop_style. */
2580 if (para
->member
.para
.fmt
.wNumbering
)
2581 eop_style
= para
->member
.para
.eop_run
->style
;
2584 ME_ContinueCoalescingTransaction(editor
);
2586 ME_InsertEndRowFromCursor(editor
, 0);
2588 if (!editor
->bEmulateVersion10
)
2589 ME_InsertTextFromCursor(editor
, 0, &endl
, 1, eop_style
);
2591 ME_InsertTextFromCursor(editor
, 0, endlv10
, 2, eop_style
);
2592 ME_CommitCoalescingUndo(editor
);
2595 ME_UpdateSelectionLinkAttribute(editor
);
2596 ME_UpdateRepaint(editor
, FALSE
);
2597 ME_SaveTempStyle(editor
, style
); /* set the temp insert style for the new para */
2598 ME_ReleaseStyle(style
);
2606 ME_KeyDown(ME_TextEditor
*editor
, WORD nKey
)
2608 BOOL ctrl_is_down
= GetKeyState(VK_CONTROL
) & 0x8000;
2609 BOOL shift_is_down
= GetKeyState(VK_SHIFT
) & 0x8000;
2611 if (editor
->bMouseCaptured
)
2613 if (nKey
!= VK_SHIFT
&& nKey
!= VK_CONTROL
&& nKey
!= VK_MENU
)
2614 editor
->nSelectionType
= stPosition
;
2622 editor
->nUDArrowX
= -1;
2628 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
2629 ME_ArrowKey(editor
, nKey
, shift_is_down
, ctrl_is_down
);
2633 editor
->nUDArrowX
= -1;
2634 /* FIXME backspace and delete aren't the same, they act different wrt paragraph style of the merged paragraph */
2635 if (editor
->styleFlags
& ES_READONLY
)
2637 if (ME_IsSelection(editor
))
2639 ME_DeleteSelection(editor
);
2640 ME_CommitUndo(editor
);
2642 else if (nKey
== VK_DELETE
)
2644 /* Delete stops group typing.
2645 * (See MSDN remarks on EM_STOPGROUPTYPING message) */
2646 ME_DeleteTextAtCursor(editor
, 1, 1);
2647 ME_CommitUndo(editor
);
2649 else if (ME_ArrowKey(editor
, VK_LEFT
, FALSE
, FALSE
))
2651 BOOL bDeletionSucceeded
;
2652 /* Backspace can be grouped for a single undo */
2653 ME_ContinueCoalescingTransaction(editor
);
2654 bDeletionSucceeded
= ME_DeleteTextAtCursor(editor
, 1, 1);
2655 if (!bDeletionSucceeded
&& !editor
->bEmulateVersion10
) { /* v4.1 */
2656 /* Deletion was prevented so the cursor is moved back to where it was.
2657 * (e.g. this happens when trying to delete cell boundaries)
2659 ME_ArrowKey(editor
, VK_RIGHT
, FALSE
, FALSE
);
2661 ME_CommitCoalescingUndo(editor
);
2665 ME_MoveCursorFromTableRowStartParagraph(editor
);
2666 ME_UpdateSelectionLinkAttribute(editor
);
2667 ME_UpdateRepaint(editor
, FALSE
);
2668 ME_SendRequestResize(editor
, FALSE
);
2671 return handle_enter(editor
);
2673 if (editor
->bDialogMode
&& editor
->hwndParent
)
2674 PostMessageW(editor
->hwndParent
, WM_CLOSE
, 0, 0);
2677 if (editor
->bDialogMode
&& editor
->hwndParent
)
2678 SendMessageW(editor
->hwndParent
, WM_NEXTDLGCTL
, shift_is_down
, 0);
2683 handle_EM_EXSETSEL( editor
, 0, -1 );
2689 return paste_special( editor
, 0, NULL
, FALSE
);
2694 return copy_or_cut(editor
, nKey
== 'X');
2712 if (nKey
!= VK_SHIFT
&& nKey
!= VK_CONTROL
&& nKey
&& nKey
!= VK_MENU
)
2713 editor
->nUDArrowX
= -1;
2720 chf
.cbSize
= sizeof(chf
);
2722 ME_GetSelectionCharFormat(editor
, &chf
);
2723 ME_DumpStyleToBuf(&chf
, buf
);
2724 MessageBoxA(NULL
, buf
, "Style dump", MB_OK
);
2728 ME_CheckCharOffsets(editor
);
2735 static LRESULT
ME_Char(ME_TextEditor
*editor
, WPARAM charCode
,
2736 LPARAM flags
, BOOL unicode
)
2740 if (editor
->bMouseCaptured
)
2744 wstr
= (WCHAR
)charCode
;
2747 CHAR charA
= charCode
;
2748 MultiByteToWideChar(CP_ACP
, 0, &charA
, 1, &wstr
, 1);
2751 if (editor
->styleFlags
& ES_READONLY
) {
2752 MessageBeep(MB_ICONERROR
);
2753 return 0; /* FIXME really 0 ? */
2756 if ((unsigned)wstr
>= ' ' || wstr
== '\t')
2758 ME_Cursor cursor
= editor
->pCursors
[0];
2759 ME_DisplayItem
*para
= cursor
.pPara
;
2761 BOOL ctrl_is_down
= GetKeyState(VK_CONTROL
) & 0x8000;
2762 ME_GetSelectionOfs(editor
, &from
, &to
);
2764 /* v4.1 allows tabs to be inserted with ctrl key down */
2765 !(ctrl_is_down
&& !editor
->bEmulateVersion10
))
2767 ME_DisplayItem
*para
;
2768 BOOL bSelectedRow
= FALSE
;
2770 para
= cursor
.pPara
;
2771 if (ME_IsSelection(editor
) &&
2772 cursor
.pRun
->member
.run
.nCharOfs
+ cursor
.nOffset
== 0 &&
2773 to
== ME_GetCursorOfs(&editor
->pCursors
[0]) &&
2774 para
->member
.para
.prev_para
->type
== diParagraph
)
2776 para
= para
->member
.para
.prev_para
;
2777 bSelectedRow
= TRUE
;
2779 if (ME_IsInTable(para
))
2781 ME_TabPressedInTable(editor
, bSelectedRow
);
2782 ME_CommitUndo(editor
);
2785 } else if (!editor
->bEmulateVersion10
) { /* v4.1 */
2786 if (para
->member
.para
.nFlags
& MEPF_ROWEND
) {
2788 para
= para
->member
.para
.next_para
;
2789 if (para
->member
.para
.nFlags
& MEPF_ROWSTART
)
2790 para
= para
->member
.para
.next_para
;
2791 editor
->pCursors
[0].pPara
= para
;
2792 editor
->pCursors
[0].pRun
= ME_FindItemFwd(para
, diRun
);
2793 editor
->pCursors
[0].nOffset
= 0;
2794 editor
->pCursors
[1] = editor
->pCursors
[0];
2797 } else { /* v1.0 - 3.0 */
2798 if (ME_IsInTable(cursor
.pRun
) &&
2799 cursor
.pRun
->member
.run
.nFlags
& MERF_ENDPARA
&&
2802 /* Text should not be inserted at the end of the table. */
2807 /* FIXME maybe it would make sense to call EM_REPLACESEL instead ? */
2808 /* WM_CHAR is restricted to nTextLimit */
2809 if(editor
->nTextLimit
> ME_GetTextLength(editor
) - (to
-from
))
2811 ME_Style
*style
= ME_GetInsertStyle(editor
, 0);
2812 ME_ContinueCoalescingTransaction(editor
);
2813 ME_InsertTextFromCursor(editor
, 0, &wstr
, 1, style
);
2814 ME_ReleaseStyle(style
);
2815 ME_CommitCoalescingUndo(editor
);
2816 ITextHost_TxSetCursor(editor
->texthost
, NULL
, FALSE
);
2819 ME_UpdateSelectionLinkAttribute(editor
);
2820 ME_UpdateRepaint(editor
, FALSE
);
2825 /* Process the message and calculate the new click count.
2827 * returns: The click count if it is mouse down event, else returns 0. */
2828 static int ME_CalculateClickCount(ME_TextEditor
*editor
, UINT msg
, WPARAM wParam
,
2831 static int clickNum
= 0;
2832 if (msg
< WM_MOUSEFIRST
|| msg
> WM_MOUSELAST
)
2835 if ((msg
== WM_LBUTTONDBLCLK
) ||
2836 (msg
== WM_RBUTTONDBLCLK
) ||
2837 (msg
== WM_MBUTTONDBLCLK
) ||
2838 (msg
== WM_XBUTTONDBLCLK
))
2840 msg
-= (WM_LBUTTONDBLCLK
- WM_LBUTTONDOWN
);
2843 if ((msg
== WM_LBUTTONDOWN
) ||
2844 (msg
== WM_RBUTTONDOWN
) ||
2845 (msg
== WM_MBUTTONDOWN
) ||
2846 (msg
== WM_XBUTTONDOWN
))
2848 static MSG prevClickMsg
;
2850 /* Compare the editor instead of the hwnd so that the this
2851 * can still be done for windowless richedit controls. */
2852 clickMsg
.hwnd
= (HWND
)editor
;
2853 clickMsg
.message
= msg
;
2854 clickMsg
.wParam
= wParam
;
2855 clickMsg
.lParam
= lParam
;
2856 clickMsg
.time
= GetMessageTime();
2857 clickMsg
.pt
.x
= (short)LOWORD(lParam
);
2858 clickMsg
.pt
.y
= (short)HIWORD(lParam
);
2859 if ((clickNum
!= 0) &&
2860 (clickMsg
.message
== prevClickMsg
.message
) &&
2861 (clickMsg
.hwnd
== prevClickMsg
.hwnd
) &&
2862 (clickMsg
.wParam
== prevClickMsg
.wParam
) &&
2863 (clickMsg
.time
- prevClickMsg
.time
< GetDoubleClickTime()) &&
2864 (abs(clickMsg
.pt
.x
- prevClickMsg
.pt
.x
) < GetSystemMetrics(SM_CXDOUBLECLK
)/2) &&
2865 (abs(clickMsg
.pt
.y
- prevClickMsg
.pt
.y
) < GetSystemMetrics(SM_CYDOUBLECLK
)/2))
2871 prevClickMsg
= clickMsg
;
2878 static BOOL
is_link( ME_Run
*run
)
2880 return (run
->style
->fmt
.dwMask
& CFM_LINK
) && (run
->style
->fmt
.dwEffects
& CFE_LINK
);
2883 static BOOL
ME_SetCursor(ME_TextEditor
*editor
)
2889 DWORD messagePos
= GetMessagePos();
2890 pt
.x
= (short)LOWORD(messagePos
);
2891 pt
.y
= (short)HIWORD(messagePos
);
2895 sbi
.cbSize
= sizeof(sbi
);
2896 GetScrollBarInfo(editor
->hWnd
, OBJID_HSCROLL
, &sbi
);
2897 if (!(sbi
.rgstate
[0] & (STATE_SYSTEM_INVISIBLE
|STATE_SYSTEM_OFFSCREEN
)) &&
2898 PtInRect(&sbi
.rcScrollBar
, pt
))
2900 ITextHost_TxSetCursor(editor
->texthost
,
2901 LoadCursorW(NULL
, (WCHAR
*)IDC_ARROW
), FALSE
);
2904 sbi
.cbSize
= sizeof(sbi
);
2905 GetScrollBarInfo(editor
->hWnd
, OBJID_VSCROLL
, &sbi
);
2906 if (!(sbi
.rgstate
[0] & (STATE_SYSTEM_INVISIBLE
|STATE_SYSTEM_OFFSCREEN
)) &&
2907 PtInRect(&sbi
.rcScrollBar
, pt
))
2909 ITextHost_TxSetCursor(editor
->texthost
,
2910 LoadCursorW(NULL
, (WCHAR
*)IDC_ARROW
), FALSE
);
2914 ITextHost_TxScreenToClient(editor
->texthost
, &pt
);
2916 if (editor
->nSelectionType
== stLine
&& editor
->bMouseCaptured
) {
2917 ITextHost_TxSetCursor(editor
->texthost
, hLeft
, FALSE
);
2920 if (!editor
->bEmulateVersion10
/* v4.1 */ &&
2921 pt
.y
< editor
->rcFormat
.top
&&
2922 pt
.x
< editor
->rcFormat
.left
)
2924 ITextHost_TxSetCursor(editor
->texthost
, hLeft
, FALSE
);
2927 if (pt
.y
< editor
->rcFormat
.top
|| pt
.y
> editor
->rcFormat
.bottom
)
2929 if (editor
->bEmulateVersion10
) /* v1.0 - 3.0 */
2930 ITextHost_TxSetCursor(editor
->texthost
,
2931 LoadCursorW(NULL
, (WCHAR
*)IDC_ARROW
), FALSE
);
2933 ITextHost_TxSetCursor(editor
->texthost
,
2934 LoadCursorW(NULL
, (WCHAR
*)IDC_IBEAM
), TRUE
);
2937 if (pt
.x
< editor
->rcFormat
.left
)
2939 ITextHost_TxSetCursor(editor
->texthost
, hLeft
, FALSE
);
2942 ME_CharFromPos(editor
, pt
.x
, pt
.y
, &cursor
, &isExact
);
2947 run
= &cursor
.pRun
->member
.run
;
2950 ITextHost_TxSetCursor(editor
->texthost
,
2951 LoadCursorW(NULL
, (WCHAR
*)IDC_HAND
),
2956 if (ME_IsSelection(editor
))
2958 int selStart
, selEnd
;
2959 int offset
= ME_GetCursorOfs(&cursor
);
2961 ME_GetSelectionOfs(editor
, &selStart
, &selEnd
);
2962 if (selStart
<= offset
&& selEnd
>= offset
) {
2963 ITextHost_TxSetCursor(editor
->texthost
,
2964 LoadCursorW(NULL
, (WCHAR
*)IDC_ARROW
),
2970 ITextHost_TxSetCursor(editor
->texthost
,
2971 LoadCursorW(NULL
, (WCHAR
*)IDC_IBEAM
), TRUE
);
2975 static void ME_SetDefaultFormatRect(ME_TextEditor
*editor
)
2977 ITextHost_TxGetClientRect(editor
->texthost
, &editor
->rcFormat
);
2978 editor
->rcFormat
.top
+= editor
->exStyleFlags
& WS_EX_CLIENTEDGE
? 1 : 0;
2979 editor
->rcFormat
.left
+= 1 + editor
->selofs
;
2980 editor
->rcFormat
.right
-= 1;
2983 static LONG
ME_GetSelectionType(ME_TextEditor
*editor
)
2985 LONG sel_type
= SEL_EMPTY
;
2988 ME_GetSelectionOfs(editor
, &start
, &end
);
2990 sel_type
= SEL_EMPTY
;
2993 LONG object_count
= 0, character_count
= 0;
2996 for (i
= 0; i
< end
- start
; i
++)
3000 ME_CursorFromCharOfs(editor
, start
+ i
, &cursor
);
3001 if (cursor
.pRun
->member
.run
.reobj
)
3005 if (character_count
>= 2 && object_count
>= 2)
3006 return (SEL_TEXT
| SEL_MULTICHAR
| SEL_OBJECT
| SEL_MULTIOBJECT
);
3008 if (character_count
)
3010 sel_type
|= SEL_TEXT
;
3011 if (character_count
>= 2)
3012 sel_type
|= SEL_MULTICHAR
;
3016 sel_type
|= SEL_OBJECT
;
3017 if (object_count
>= 2)
3018 sel_type
|= SEL_MULTIOBJECT
;
3024 static BOOL
ME_ShowContextMenu(ME_TextEditor
*editor
, int x
, int y
)
3030 if(!editor
->lpOleCallback
|| !editor
->hWnd
)
3032 ME_GetSelectionOfs(editor
, &selrange
.cpMin
, &selrange
.cpMax
);
3033 seltype
= ME_GetSelectionType(editor
);
3034 if(SUCCEEDED(IRichEditOleCallback_GetContextMenu(editor
->lpOleCallback
, seltype
, NULL
, &selrange
, &menu
)))
3036 TrackPopupMenu(menu
, TPM_LEFTALIGN
| TPM_RIGHTBUTTON
, x
, y
, 0, editor
->hwndParent
, NULL
);
3042 ME_TextEditor
*ME_MakeEditor(ITextHost
*texthost
, BOOL bEmulateVersion10
)
3044 ME_TextEditor
*ed
= heap_alloc(sizeof(*ed
));
3050 ed
->hwndParent
= NULL
;
3051 ed
->sizeWindow
.cx
= ed
->sizeWindow
.cy
= 0;
3052 ed
->texthost
= texthost
;
3054 ed
->bEmulateVersion10
= bEmulateVersion10
;
3056 ed
->exStyleFlags
= 0;
3057 ed
->first_marked_para
= NULL
;
3059 ITextHost_TxGetPropertyBits(texthost
,
3060 (TXTBIT_RICHTEXT
|TXTBIT_MULTILINE
|
3061 TXTBIT_READONLY
|TXTBIT_USEPASSWORD
|
3062 TXTBIT_HIDESELECTION
|TXTBIT_SAVESELECTION
|
3063 TXTBIT_AUTOWORDSEL
|TXTBIT_VERTICAL
|
3064 TXTBIT_WORDWRAP
|TXTBIT_DISABLEDRAG
),
3066 ITextHost_TxGetScrollBars(texthost
, &ed
->styleFlags
);
3067 ed
->styleFlags
&= (WS_VSCROLL
|WS_HSCROLL
|ES_AUTOVSCROLL
|
3068 ES_AUTOHSCROLL
|ES_DISABLENOSCROLL
);
3069 ed
->pBuffer
= ME_MakeText();
3070 ed
->nZoomNumerator
= ed
->nZoomDenominator
= 0;
3071 ed
->nAvailWidth
= 0; /* wrap to client area */
3072 list_init( &ed
->style_list
);
3073 ME_MakeFirstParagraph(ed
);
3074 /* The four cursors are for:
3075 * 0 - The position where the caret is shown
3076 * 1 - The anchored end of the selection (for normal selection)
3077 * 2 & 3 - The anchored start and end respectively for word, line,
3078 * or paragraph selection.
3081 ed
->pCursors
= heap_alloc(ed
->nCursors
* sizeof(*ed
->pCursors
));
3082 ME_SetCursorToStart(ed
, &ed
->pCursors
[0]);
3083 ed
->pCursors
[1] = ed
->pCursors
[0];
3084 ed
->pCursors
[2] = ed
->pCursors
[0];
3085 ed
->pCursors
[3] = ed
->pCursors
[1];
3086 ed
->nLastTotalLength
= ed
->nTotalLength
= 0;
3087 ed
->nLastTotalWidth
= ed
->nTotalWidth
= 0;
3089 ed
->rgbBackColor
= -1;
3090 ed
->hbrBackground
= GetSysColorBrush(COLOR_WINDOW
);
3091 ed
->bCaretAtEnd
= FALSE
;
3093 ed
->nModifyStep
= 0;
3094 ed
->nTextLimit
= TEXT_LIMIT_DEFAULT
;
3095 list_init( &ed
->undo_stack
);
3096 list_init( &ed
->redo_stack
);
3097 ed
->nUndoStackSize
= 0;
3098 ed
->nUndoLimit
= STACK_SIZE_DEFAULT
;
3099 ed
->nUndoMode
= umAddToUndo
;
3100 ed
->nParagraphs
= 1;
3101 ed
->nLastSelStart
= ed
->nLastSelEnd
= 0;
3102 ed
->pLastSelStartPara
= ed
->pLastSelEndPara
= ed
->pCursors
[0].pPara
;
3103 ed
->bHideSelection
= FALSE
;
3104 ed
->pfnWordBreak
= NULL
;
3105 ed
->lpOleCallback
= NULL
;
3106 ed
->mode
= TM_MULTILEVELUNDO
| TM_MULTICODEPAGE
;
3107 ed
->mode
|= (props
& TXTBIT_RICHTEXT
) ? TM_RICHTEXT
: TM_PLAINTEXT
;
3108 ed
->AutoURLDetect_bEnable
= FALSE
;
3109 ed
->bHaveFocus
= FALSE
;
3110 ed
->bDialogMode
= FALSE
;
3111 ed
->bMouseCaptured
= FALSE
;
3112 for (i
=0; i
<HFONT_CACHE_SIZE
; i
++)
3114 ed
->pFontCache
[i
].nRefs
= 0;
3115 ed
->pFontCache
[i
].nAge
= 0;
3116 ed
->pFontCache
[i
].hFont
= NULL
;
3119 ME_CheckCharOffsets(ed
);
3120 SetRectEmpty(&ed
->rcFormat
);
3121 ed
->bDefaultFormatRect
= TRUE
;
3122 ITextHost_TxGetSelectionBarWidth(ed
->texthost
, &selbarwidth
);
3124 /* FIXME: Convert selbarwidth from HIMETRIC to pixels */
3125 ed
->selofs
= SELECTIONBAR_WIDTH
;
3126 ed
->styleFlags
|= ES_SELECTIONBAR
;
3130 ed
->nSelectionType
= stPosition
;
3132 ed
->cPasswordMask
= 0;
3133 if (props
& TXTBIT_USEPASSWORD
)
3134 ITextHost_TxGetPasswordChar(texthost
, &ed
->cPasswordMask
);
3136 if (props
& TXTBIT_AUTOWORDSEL
)
3137 ed
->styleFlags
|= ECO_AUTOWORDSELECTION
;
3138 if (props
& TXTBIT_MULTILINE
) {
3139 ed
->styleFlags
|= ES_MULTILINE
;
3140 ed
->bWordWrap
= (props
& TXTBIT_WORDWRAP
) != 0;
3142 ed
->bWordWrap
= FALSE
;
3144 if (props
& TXTBIT_READONLY
)
3145 ed
->styleFlags
|= ES_READONLY
;
3146 if (!(props
& TXTBIT_HIDESELECTION
))
3147 ed
->styleFlags
|= ES_NOHIDESEL
;
3148 if (props
& TXTBIT_SAVESELECTION
)
3149 ed
->styleFlags
|= ES_SAVESEL
;
3150 if (props
& TXTBIT_VERTICAL
)
3151 ed
->styleFlags
|= ES_VERTICAL
;
3152 if (props
& TXTBIT_DISABLEDRAG
)
3153 ed
->styleFlags
|= ES_NOOLEDRAGDROP
;
3155 ed
->notified_cr
.cpMin
= ed
->notified_cr
.cpMax
= 0;
3157 /* Default scrollbar information */
3158 ed
->vert_si
.cbSize
= sizeof(SCROLLINFO
);
3159 ed
->vert_si
.nMin
= 0;
3160 ed
->vert_si
.nMax
= 0;
3161 ed
->vert_si
.nPage
= 0;
3162 ed
->vert_si
.nPos
= 0;
3164 ed
->horz_si
.cbSize
= sizeof(SCROLLINFO
);
3165 ed
->horz_si
.nMin
= 0;
3166 ed
->horz_si
.nMax
= 0;
3167 ed
->horz_si
.nPage
= 0;
3168 ed
->horz_si
.nPos
= 0;
3170 ed
->wheel_remain
= 0;
3172 list_init( &ed
->reobj_list
);
3173 OleInitialize(NULL
);
3178 void ME_DestroyEditor(ME_TextEditor
*editor
)
3180 ME_DisplayItem
*pFirst
= editor
->pBuffer
->pFirst
;
3181 ME_DisplayItem
*p
= pFirst
, *pNext
= NULL
;
3182 ME_Style
*s
, *cursor2
;
3185 ME_ClearTempStyle(editor
);
3186 ME_EmptyUndoStack(editor
);
3189 if (p
->type
== diParagraph
)
3190 destroy_para(editor
, p
);
3192 ME_DestroyDisplayItem(p
);
3196 LIST_FOR_EACH_ENTRY_SAFE( s
, cursor2
, &editor
->style_list
, ME_Style
, entry
)
3197 ME_DestroyStyle( s
);
3199 ME_ReleaseStyle(editor
->pBuffer
->pDefaultStyle
);
3200 for (i
=0; i
<HFONT_CACHE_SIZE
; i
++)
3202 if (editor
->pFontCache
[i
].hFont
)
3203 DeleteObject(editor
->pFontCache
[i
].hFont
);
3205 if (editor
->rgbBackColor
!= -1)
3206 DeleteObject(editor
->hbrBackground
);
3207 if(editor
->lpOleCallback
)
3208 IRichEditOleCallback_Release(editor
->lpOleCallback
);
3209 ITextHost_Release(editor
->texthost
);
3212 IRichEditOle_Release(editor
->reOle
);
3213 editor
->reOle
= NULL
;
3217 heap_free(editor
->pBuffer
);
3218 heap_free(editor
->pCursors
);
3222 BOOL WINAPI
DllMain(HINSTANCE hinstDLL
, DWORD fdwReason
, LPVOID lpvReserved
)
3227 case DLL_PROCESS_ATTACH
:
3228 DisableThreadLibraryCalls(hinstDLL
);
3229 me_heap
= HeapCreate (0, 0x10000, 0);
3230 if (!ME_RegisterEditorClass(hinstDLL
)) return FALSE
;
3231 hLeft
= LoadCursorW(hinstDLL
, MAKEINTRESOURCEW(OCR_REVERSE
));
3235 case DLL_PROCESS_DETACH
:
3236 if (lpvReserved
) break;
3237 UnregisterClassW(RICHEDIT_CLASS20W
, 0);
3238 UnregisterClassW(MSFTEDIT_CLASS
, 0);
3239 UnregisterClassA(RICHEDIT_CLASS20A
, 0);
3240 UnregisterClassA("RichEdit50A", 0);
3241 if (ME_ListBoxRegistered
)
3242 UnregisterClassW(REListBox20W
, 0);
3243 if (ME_ComboBoxRegistered
)
3244 UnregisterClassW(REComboBox20W
, 0);
3246 HeapDestroy (me_heap
);
3253 static inline int get_default_line_height( ME_TextEditor
*editor
)
3257 if (editor
->pBuffer
&& editor
->pBuffer
->pDefaultStyle
)
3258 height
= editor
->pBuffer
->pDefaultStyle
->tm
.tmHeight
;
3259 if (height
<= 0) height
= 24;
3264 static inline int calc_wheel_change( int *remain
, int amount_per_click
)
3266 int change
= amount_per_click
* (float)*remain
/ WHEEL_DELTA
;
3267 *remain
-= WHEEL_DELTA
* change
/ amount_per_click
;
3271 static const char * const edit_messages
[] = {
3300 "EM_SETPASSWORDCHAR",
3301 "EM_EMPTYUNDOBUFFER",
3302 "EM_GETFIRSTVISIBLELINE",
3304 "EM_SETWORDBREAKPROC",
3305 "EM_GETWORDBREAKPROC",
3306 "EM_GETPASSWORDCHAR",
3316 static const char * const richedit_messages
[] = {
3321 "EM_EXLINEFROMCHAR",
3327 "EM_GETOLEINTERFACE",
3337 "EM_SETOLECALLBACK",
3339 "EM_SETTARGETDEVICE",
3347 "EM_GETWORDBREAKPROCEX",
3348 "EM_SETWORDBREAKPROCEX",
3350 "EM_UNKNOWN_USER_83",
3355 "EM_STOPGROUPTYPING",
3359 "EM_GETAUTOURLDETECT",
3362 "EM_GETTEXTLENGTHEX",
3365 "EM_UNKNOWN_USER_98",
3366 "EM_UNKNOWN_USER_99",
3367 "EM_SETPUNCTUATION",
3368 "EM_GETPUNCTUATION",
3369 "EM_SETWORDWRAPMODE",
3370 "EM_GETWORDWRAPMODE",
3376 "EM_UNKNOWN_USER_109",
3377 "EM_UNKNOWN_USER_110",
3378 "EM_UNKNOWN_USER_111",
3379 "EM_UNKNOWN_USER_112",
3380 "EM_UNKNOWN_USER_113",
3381 "EM_UNKNOWN_USER_114",
3382 "EM_UNKNOWN_USER_115",
3383 "EM_UNKNOWN_USER_116",
3384 "EM_UNKNOWN_USER_117",
3385 "EM_UNKNOWN_USER_118",
3386 "EM_UNKNOWN_USER_119",
3387 "EM_SETLANGOPTIONS",
3388 "EM_GETLANGOPTIONS",
3389 "EM_GETIMECOMPMODE",
3393 "EM_SETIMEMODEBIAS",
3398 get_msg_name(UINT msg
)
3400 if (msg
>= EM_GETSEL
&& msg
<= EM_CHARFROMPOS
)
3401 return edit_messages
[msg
- EM_GETSEL
];
3402 if (msg
>= EM_CANPASTE
&& msg
<= EM_GETIMEMODEBIAS
)
3403 return richedit_messages
[msg
- EM_CANPASTE
];
3407 static void ME_LinkNotify(ME_TextEditor
*editor
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
3411 ME_Cursor cursor
; /* The start of the clicked text. */
3414 x
= (short)LOWORD(lParam
);
3415 y
= (short)HIWORD(lParam
);
3416 ME_CharFromPos(editor
, x
, y
, &cursor
, &isExact
);
3417 if (!isExact
) return;
3419 if (is_link( &cursor
.pRun
->member
.run
))
3420 { /* The clicked run has CFE_LINK set */
3423 info
.nmhdr
.hwndFrom
= NULL
;
3424 info
.nmhdr
.idFrom
= 0;
3425 info
.nmhdr
.code
= EN_LINK
;
3427 info
.wParam
= wParam
;
3428 info
.lParam
= lParam
;
3431 /* find the first contiguous run with CFE_LINK set */
3432 info
.chrg
.cpMin
= ME_GetCursorOfs(&cursor
);
3434 while (ME_PrevRun( NULL
, &di
, FALSE
) && is_link( &di
->member
.run
))
3435 info
.chrg
.cpMin
-= di
->member
.run
.len
;
3437 /* find the last contiguous run with CFE_LINK set */
3438 info
.chrg
.cpMax
= ME_GetCursorOfs(&cursor
) + cursor
.pRun
->member
.run
.len
;
3440 while (ME_NextRun( NULL
, &di
, FALSE
) && is_link( &di
->member
.run
))
3441 info
.chrg
.cpMax
+= di
->member
.run
.len
;
3443 ITextHost_TxNotify(editor
->texthost
, info
.nmhdr
.code
, &info
);
3447 void ME_ReplaceSel(ME_TextEditor
*editor
, BOOL can_undo
, const WCHAR
*str
, int len
)
3449 int from
, to
, nStartCursor
;
3452 nStartCursor
= ME_GetSelectionOfs(editor
, &from
, &to
);
3453 style
= ME_GetSelectionInsertStyle(editor
);
3454 ME_InternalDeleteText(editor
, &editor
->pCursors
[nStartCursor
], to
-from
, FALSE
);
3455 ME_InsertTextFromCursor(editor
, 0, str
, len
, style
);
3456 ME_ReleaseStyle(style
);
3457 /* drop temporary style if line end */
3459 * FIXME question: does abc\n mean: put abc,
3460 * clear temp style, put \n? (would require a change)
3462 if (len
>0 && str
[len
-1] == '\n')
3463 ME_ClearTempStyle(editor
);
3464 ME_CommitUndo(editor
);
3465 ME_UpdateSelectionLinkAttribute(editor
);
3467 ME_EmptyUndoStack(editor
);
3468 ME_UpdateRepaint(editor
, FALSE
);
3471 static void ME_SetText(ME_TextEditor
*editor
, void *text
, BOOL unicode
)
3473 LONG codepage
= unicode
? CP_UNICODE
: CP_ACP
;
3476 LPWSTR wszText
= ME_ToUnicode(codepage
, text
, &textLen
);
3477 ME_InsertTextFromCursor(editor
, 0, wszText
, textLen
, editor
->pBuffer
->pDefaultStyle
);
3478 ME_EndToUnicode(codepage
, wszText
);
3481 static LRESULT
ME_WmCreate(ME_TextEditor
*editor
, LPARAM lParam
, BOOL unicode
)
3483 CREATESTRUCTW
*createW
= (CREATESTRUCTW
*)lParam
;
3484 CREATESTRUCTA
*createA
= (CREATESTRUCTA
*)lParam
;
3489 text
= unicode
? (void*)createW
->lpszName
: (void*)createA
->lpszName
;
3491 ME_SetDefaultFormatRect(editor
);
3493 max
= (editor
->styleFlags
& ES_DISABLENOSCROLL
) ? 1 : 0;
3494 if (~editor
->styleFlags
& ES_DISABLENOSCROLL
|| editor
->styleFlags
& WS_VSCROLL
)
3495 ITextHost_TxSetScrollRange(editor
->texthost
, SB_VERT
, 0, max
, TRUE
);
3497 if (~editor
->styleFlags
& ES_DISABLENOSCROLL
|| editor
->styleFlags
& WS_HSCROLL
)
3498 ITextHost_TxSetScrollRange(editor
->texthost
, SB_HORZ
, 0, max
, TRUE
);
3500 if (editor
->styleFlags
& ES_DISABLENOSCROLL
)
3502 if (editor
->styleFlags
& WS_VSCROLL
)
3504 ITextHost_TxEnableScrollBar(editor
->texthost
, SB_VERT
, ESB_DISABLE_BOTH
);
3505 ITextHost_TxShowScrollBar(editor
->texthost
, SB_VERT
, TRUE
);
3507 if (editor
->styleFlags
& WS_HSCROLL
)
3509 ITextHost_TxEnableScrollBar(editor
->texthost
, SB_HORZ
, ESB_DISABLE_BOTH
);
3510 ITextHost_TxShowScrollBar(editor
->texthost
, SB_HORZ
, TRUE
);
3516 ME_SetText(editor
, text
, unicode
);
3517 ME_SetCursorToStart(editor
, &editor
->pCursors
[0]);
3518 ME_SetCursorToStart(editor
, &editor
->pCursors
[1]);
3521 ME_CommitUndo(editor
);
3522 ME_WrapMarkedParagraphs(editor
);
3523 ME_MoveCaret(editor
);
3528 #define UNSUPPORTED_MSG(e) \
3530 FIXME(#e ": stub\n"); \
3531 *phresult = S_FALSE; \
3534 /* Handle messages for windowless and windowed richedit controls.
3536 * The LRESULT that is returned is a return value for window procs,
3537 * and the phresult parameter is the COM return code needed by the
3538 * text services interface. */
3539 LRESULT
ME_HandleMessage(ME_TextEditor
*editor
, UINT msg
, WPARAM wParam
,
3540 LPARAM lParam
, BOOL unicode
, HRESULT
* phresult
)
3546 UNSUPPORTED_MSG(EM_DISPLAYBAND
)
3547 UNSUPPORTED_MSG(EM_FINDWORDBREAK
)
3548 UNSUPPORTED_MSG(EM_FMTLINES
)
3549 UNSUPPORTED_MSG(EM_FORMATRANGE
)
3550 UNSUPPORTED_MSG(EM_GETBIDIOPTIONS
)
3551 UNSUPPORTED_MSG(EM_GETEDITSTYLE
)
3552 UNSUPPORTED_MSG(EM_GETIMECOMPMODE
)
3553 UNSUPPORTED_MSG(EM_GETIMESTATUS
)
3554 UNSUPPORTED_MSG(EM_SETIMESTATUS
)
3555 UNSUPPORTED_MSG(EM_GETLANGOPTIONS
)
3556 UNSUPPORTED_MSG(EM_GETREDONAME
)
3557 UNSUPPORTED_MSG(EM_GETTYPOGRAPHYOPTIONS
)
3558 UNSUPPORTED_MSG(EM_GETUNDONAME
)
3559 UNSUPPORTED_MSG(EM_GETWORDBREAKPROCEX
)
3560 UNSUPPORTED_MSG(EM_SETBIDIOPTIONS
)
3561 UNSUPPORTED_MSG(EM_SETEDITSTYLE
)
3562 UNSUPPORTED_MSG(EM_SETLANGOPTIONS
)
3563 UNSUPPORTED_MSG(EM_SETMARGINS
)
3564 UNSUPPORTED_MSG(EM_SETPALETTE
)
3565 UNSUPPORTED_MSG(EM_SETTABSTOPS
)
3566 UNSUPPORTED_MSG(EM_SETTYPOGRAPHYOPTIONS
)
3567 UNSUPPORTED_MSG(EM_SETWORDBREAKPROCEX
)
3569 /* Messages specific to Richedit controls */
3572 return ME_StreamIn(editor
, wParam
, (EDITSTREAM
*)lParam
, TRUE
);
3574 return ME_StreamOut(editor
, wParam
, (EDITSTREAM
*)lParam
);
3577 UINT code
= DLGC_WANTCHARS
|DLGC_WANTTAB
|DLGC_WANTARROWS
;
3580 editor
->bDialogMode
= TRUE
;
3581 if (editor
->styleFlags
& ES_MULTILINE
)
3582 code
|= DLGC_WANTMESSAGE
;
3583 if (!(editor
->styleFlags
& ES_SAVESEL
))
3584 code
|= DLGC_HASSETSEL
;
3587 case EM_EMPTYUNDOBUFFER
:
3588 ME_EmptyUndoStack(editor
);
3592 /* Note: wParam/lParam can be NULL */
3594 PUINT pfrom
= wParam
? (PUINT
)wParam
: &from
;
3595 PUINT pto
= lParam
? (PUINT
)lParam
: &to
;
3596 ME_GetSelectionOfs(editor
, (int *)pfrom
, (int *)pto
);
3597 if ((*pfrom
|*pto
) & 0xFFFF0000)
3599 return MAKELONG(*pfrom
,*pto
);
3603 CHARRANGE
*pRange
= (CHARRANGE
*)lParam
;
3604 ME_GetSelectionOfs(editor
, &pRange
->cpMin
, &pRange
->cpMax
);
3605 TRACE("EM_EXGETSEL = (%d,%d)\n", pRange
->cpMin
, pRange
->cpMax
);
3608 case EM_SETUNDOLIMIT
:
3610 if ((int)wParam
< 0)
3611 editor
->nUndoLimit
= STACK_SIZE_DEFAULT
;
3613 editor
->nUndoLimit
= min(wParam
, STACK_SIZE_MAX
);
3614 /* Setting a max stack size keeps wine from getting killed
3615 for hogging memory. Windows allocates all this memory at once, so
3616 no program would realistically set a value above our maximum. */
3617 return editor
->nUndoLimit
;
3620 return !list_empty( &editor
->undo_stack
);
3622 return !list_empty( &editor
->redo_stack
);
3623 case WM_UNDO
: /* FIXME: actually not the same */
3625 return ME_Undo(editor
);
3627 return ME_Redo(editor
);
3630 /* these flags are equivalent to the ES_* counterparts */
3631 DWORD mask
= ECO_VERTICAL
| ECO_AUTOHSCROLL
| ECO_AUTOVSCROLL
|
3632 ECO_NOHIDESEL
| ECO_READONLY
| ECO_WANTRETURN
| ECO_SELECTIONBAR
;
3633 DWORD settings
= editor
->styleFlags
& mask
;
3637 case EM_SETFONTSIZE
:
3640 LONG tmp_size
, size
;
3641 BOOL is_increase
= ((LONG
)wParam
> 0);
3643 if (editor
->mode
& TM_PLAINTEXT
)
3646 cf
.cbSize
= sizeof(cf
);
3647 cf
.dwMask
= CFM_SIZE
;
3648 ME_GetSelectionCharFormat(editor
, &cf
);
3649 tmp_size
= (cf
.yHeight
/ 20) + wParam
;
3653 else if (tmp_size
> 12 && tmp_size
< 28 && tmp_size
% 2)
3654 size
= tmp_size
+ (is_increase
? 1 : -1);
3655 else if (tmp_size
> 28 && tmp_size
< 36)
3656 size
= is_increase
? 36 : 28;
3657 else if (tmp_size
> 36 && tmp_size
< 48)
3658 size
= is_increase
? 48 : 36;
3659 else if (tmp_size
> 48 && tmp_size
< 72)
3660 size
= is_increase
? 72 : 48;
3661 else if (tmp_size
> 72 && tmp_size
< 80)
3662 size
= is_increase
? 80 : 72;
3663 else if (tmp_size
> 80 && tmp_size
< 1638)
3664 size
= 10 * (is_increase
? (tmp_size
/ 10 + 1) : (tmp_size
/ 10));
3665 else if (tmp_size
>= 1638)
3670 cf
.yHeight
= size
* 20; /* convert twips to points */
3671 ME_SetSelectionCharFormat(editor
, &cf
);
3672 ME_CommitUndo(editor
);
3673 ME_WrapMarkedParagraphs(editor
);
3674 ME_UpdateScrollBar(editor
);
3681 /* these flags are equivalent to ES_* counterparts, except for
3682 * ECO_AUTOWORDSELECTION that doesn't have an ES_* counterpart,
3683 * but is still stored in editor->styleFlags. */
3684 const DWORD mask
= ECO_VERTICAL
| ECO_AUTOHSCROLL
| ECO_AUTOVSCROLL
|
3685 ECO_NOHIDESEL
| ECO_READONLY
| ECO_WANTRETURN
|
3686 ECO_SELECTIONBAR
| ECO_AUTOWORDSELECTION
;
3687 DWORD settings
= mask
& editor
->styleFlags
;
3688 DWORD oldSettings
= settings
;
3689 DWORD changedSettings
;
3705 changedSettings
= oldSettings
^ settings
;
3707 if (changedSettings
) {
3708 editor
->styleFlags
= (editor
->styleFlags
& ~mask
) | (settings
& mask
);
3710 if (changedSettings
& ECO_SELECTIONBAR
)
3712 ITextHost_TxInvalidateRect(editor
->texthost
, &editor
->rcFormat
, TRUE
);
3713 if (settings
& ECO_SELECTIONBAR
) {
3714 assert(!editor
->selofs
);
3715 editor
->selofs
= SELECTIONBAR_WIDTH
;
3716 editor
->rcFormat
.left
+= editor
->selofs
;
3718 editor
->rcFormat
.left
-= editor
->selofs
;
3721 ME_RewrapRepaint(editor
);
3724 if ((changedSettings
& settings
& ES_NOHIDESEL
) && !editor
->bHaveFocus
)
3725 ME_InvalidateSelection( editor
);
3727 if (changedSettings
& settings
& ECO_VERTICAL
)
3728 FIXME("ECO_VERTICAL not implemented yet!\n");
3729 if (changedSettings
& settings
& ECO_AUTOHSCROLL
)
3730 FIXME("ECO_AUTOHSCROLL not implemented yet!\n");
3731 if (changedSettings
& settings
& ECO_AUTOVSCROLL
)
3732 FIXME("ECO_AUTOVSCROLL not implemented yet!\n");
3733 if (changedSettings
& settings
& ECO_WANTRETURN
)
3734 FIXME("ECO_WANTRETURN not implemented yet!\n");
3735 if (changedSettings
& settings
& ECO_AUTOWORDSELECTION
)
3736 FIXME("ECO_AUTOWORDSELECTION not implemented yet!\n");
3743 return handle_EM_EXSETSEL( editor
, wParam
, lParam
);
3745 case EM_SETSCROLLPOS
:
3747 POINT
*point
= (POINT
*)lParam
;
3748 ME_ScrollAbs(editor
, point
->x
, point
->y
);
3751 case EM_AUTOURLDETECT
:
3753 if (wParam
==1 || wParam
==0)
3755 editor
->AutoURLDetect_bEnable
= (BOOL
)wParam
;
3758 return E_INVALIDARG
;
3760 case EM_GETAUTOURLDETECT
:
3762 return editor
->AutoURLDetect_bEnable
;
3766 CHARRANGE range
= *(CHARRANGE
*)lParam
;
3768 return handle_EM_EXSETSEL( editor
, range
.cpMin
, range
.cpMax
);
3770 case EM_SHOWSCROLLBAR
:
3783 flags
= WS_HSCROLL
|WS_VSCROLL
;
3790 editor
->styleFlags
|= flags
;
3791 if (flags
& WS_HSCROLL
)
3792 ITextHost_TxShowScrollBar(editor
->texthost
, SB_HORZ
,
3793 editor
->nTotalWidth
> editor
->sizeWindow
.cx
);
3794 if (flags
& WS_VSCROLL
)
3795 ITextHost_TxShowScrollBar(editor
->texthost
, SB_VERT
,
3796 editor
->nTotalLength
> editor
->sizeWindow
.cy
);
3798 editor
->styleFlags
&= ~flags
;
3799 ITextHost_TxShowScrollBar(editor
->texthost
, wParam
, FALSE
);
3806 SETTEXTEX
*pStruct
= (SETTEXTEX
*)wParam
;
3809 BOOL bRtf
, bUnicode
, bSelection
, bUTF8
;
3810 int oldModify
= editor
->nModifyStep
;
3811 static const char utf8_bom
[] = {0xef, 0xbb, 0xbf};
3813 if (!pStruct
) return 0;
3815 /* If we detect ascii rtf at the start of the string,
3816 * we know it isn't unicode. */
3817 bRtf
= (lParam
&& (!strncmp((char *)lParam
, "{\\rtf", 5) ||
3818 !strncmp((char *)lParam
, "{\\urtf", 6)));
3819 bUnicode
= !bRtf
&& pStruct
->codepage
== CP_UNICODE
;
3820 bUTF8
= (lParam
&& (!strncmp((char *)lParam
, utf8_bom
, 3)));
3822 TRACE("EM_SETTEXTEX - %s, flags %d, cp %d\n",
3823 bUnicode
? debugstr_w((LPCWSTR
)lParam
) : debugstr_a((LPCSTR
)lParam
),
3824 pStruct
->flags
, pStruct
->codepage
);
3826 bSelection
= (pStruct
->flags
& ST_SELECTION
) != 0;
3828 int nStartCursor
= ME_GetSelectionOfs(editor
, &from
, &to
);
3829 style
= ME_GetSelectionInsertStyle(editor
);
3830 ME_InternalDeleteText(editor
, &editor
->pCursors
[nStartCursor
], to
- from
, FALSE
);
3833 ME_SetCursorToStart(editor
, &start
);
3834 ME_InternalDeleteText(editor
, &start
, ME_GetTextLength(editor
), FALSE
);
3835 style
= editor
->pBuffer
->pDefaultStyle
;
3839 ME_StreamInRTFString(editor
, bSelection
, (char *)lParam
);
3841 /* FIXME: The length returned doesn't include the rtf control
3842 * characters, only the actual text. */
3843 len
= lParam
? strlen((char *)lParam
) : 0;
3846 if (bUTF8
&& !bUnicode
) {
3847 wszText
= ME_ToUnicode(CP_UTF8
, (void *)(lParam
+3), &len
);
3848 ME_InsertTextFromCursor(editor
, 0, wszText
, len
, style
);
3849 ME_EndToUnicode(CP_UTF8
, wszText
);
3851 wszText
= ME_ToUnicode(pStruct
->codepage
, (void *)lParam
, &len
);
3852 ME_InsertTextFromCursor(editor
, 0, wszText
, len
, style
);
3853 ME_EndToUnicode(pStruct
->codepage
, wszText
);
3858 ME_ReleaseStyle(style
);
3859 ME_UpdateSelectionLinkAttribute(editor
);
3863 ME_SetCursorToStart(editor
, &cursor
);
3864 ME_UpdateLinkAttribute(editor
, &cursor
, INT_MAX
);
3866 ME_CommitUndo(editor
);
3867 if (!(pStruct
->flags
& ST_KEEPUNDO
))
3869 editor
->nModifyStep
= oldModify
;
3870 ME_EmptyUndoStack(editor
);
3872 ME_UpdateRepaint(editor
, FALSE
);
3875 case EM_SELECTIONTYPE
:
3876 return ME_GetSelectionType(editor
);
3877 case EM_SETBKGNDCOLOR
:
3880 if (editor
->rgbBackColor
!= -1) {
3881 DeleteObject(editor
->hbrBackground
);
3882 lColor
= editor
->rgbBackColor
;
3884 else lColor
= ITextHost_TxGetSysColor(editor
->texthost
, COLOR_WINDOW
);
3888 editor
->rgbBackColor
= -1;
3889 editor
->hbrBackground
= GetSysColorBrush(COLOR_WINDOW
);
3893 editor
->rgbBackColor
= lParam
;
3894 editor
->hbrBackground
= CreateSolidBrush(editor
->rgbBackColor
);
3896 ITextHost_TxInvalidateRect(editor
->texthost
, NULL
, TRUE
);
3900 return editor
->nModifyStep
== 0 ? 0 : -1;
3904 editor
->nModifyStep
= 1;
3906 editor
->nModifyStep
= 0;
3910 case EM_SETREADONLY
:
3913 editor
->styleFlags
|= ES_READONLY
;
3915 editor
->styleFlags
&= ~ES_READONLY
;
3918 case EM_SETEVENTMASK
:
3920 DWORD nOldMask
= editor
->nEventMask
;
3922 editor
->nEventMask
= lParam
;
3925 case EM_GETEVENTMASK
:
3926 return editor
->nEventMask
;
3927 case EM_SETCHARFORMAT
:
3930 BOOL bRepaint
= TRUE
;
3931 if (!cfany_to_cf2w(&p
, (CHARFORMAT2W
*)lParam
))
3933 if (wParam
& SCF_ALL
) {
3934 if (editor
->mode
& TM_PLAINTEXT
) {
3935 ME_SetDefaultCharFormat(editor
, &p
);
3938 ME_SetCursorToStart(editor
, &start
);
3939 ME_SetCharFormat(editor
, &start
, NULL
, &p
);
3940 editor
->nModifyStep
= 1;
3942 } else if (wParam
& SCF_SELECTION
) {
3943 if (editor
->mode
& TM_PLAINTEXT
)
3945 if (wParam
& SCF_WORD
) {
3947 ME_Cursor end
= editor
->pCursors
[0];
3948 ME_MoveCursorWords(editor
, &end
, +1);
3950 ME_MoveCursorWords(editor
, &start
, -1);
3951 ME_SetCharFormat(editor
, &start
, &end
, &p
);
3953 bRepaint
= ME_IsSelection(editor
);
3954 ME_SetSelectionCharFormat(editor
, &p
);
3955 if (bRepaint
) editor
->nModifyStep
= 1;
3956 } else { /* SCF_DEFAULT */
3957 ME_SetDefaultCharFormat(editor
, &p
);
3959 ME_CommitUndo(editor
);
3962 ME_WrapMarkedParagraphs(editor
);
3963 ME_UpdateScrollBar(editor
);
3968 case EM_GETCHARFORMAT
:
3970 CHARFORMAT2W tmp
, *dst
= (CHARFORMAT2W
*)lParam
;
3971 if (dst
->cbSize
!= sizeof(CHARFORMATA
) &&
3972 dst
->cbSize
!= sizeof(CHARFORMATW
) &&
3973 dst
->cbSize
!= sizeof(CHARFORMAT2A
) &&
3974 dst
->cbSize
!= sizeof(CHARFORMAT2W
))
3976 tmp
.cbSize
= sizeof(tmp
);
3978 ME_GetDefaultCharFormat(editor
, &tmp
);
3980 ME_GetSelectionCharFormat(editor
, &tmp
);
3981 cf2w_to_cfany(dst
, &tmp
);
3984 case EM_SETPARAFORMAT
:
3986 BOOL result
= ME_SetSelectionParaFormat(editor
, (PARAFORMAT2
*)lParam
);
3987 ME_WrapMarkedParagraphs(editor
);
3988 ME_UpdateScrollBar(editor
);
3990 ME_CommitUndo(editor
);
3993 case EM_GETPARAFORMAT
:
3994 ME_GetSelectionParaFormat(editor
, (PARAFORMAT2
*)lParam
);
3995 return ((PARAFORMAT2
*)lParam
)->dwMask
;
3996 case EM_GETFIRSTVISIBLELINE
:
3998 ME_DisplayItem
*p
= editor
->pBuffer
->pFirst
;
3999 int y
= editor
->vert_si
.nPos
;
4004 p
= ME_FindItemFwd(p
, diStartRowOrParagraphOrEnd
);
4005 if (p
->type
== diTextEnd
)
4007 if (p
->type
== diParagraph
) {
4008 ypara
= p
->member
.para
.pt
.y
;
4011 ystart
= ypara
+ p
->member
.row
.pt
.y
;
4012 yend
= ystart
+ p
->member
.row
.nHeight
;
4020 case EM_HIDESELECTION
:
4022 editor
->bHideSelection
= (wParam
!= 0);
4023 ME_InvalidateSelection(editor
);
4028 if (!(editor
->styleFlags
& ES_MULTILINE
))
4030 ME_ScrollDown( editor
, lParam
* get_default_line_height( editor
) );
4036 int nStartCursor
= ME_GetSelectionOfs(editor
, &from
, &to
);
4037 ME_InternalDeleteText(editor
, &editor
->pCursors
[nStartCursor
], to
-from
, FALSE
);
4038 ME_CommitUndo(editor
);
4039 ME_UpdateRepaint(editor
, TRUE
);
4045 LONG codepage
= unicode
? CP_UNICODE
: CP_ACP
;
4046 LPWSTR wszText
= ME_ToUnicode(codepage
, (void *)lParam
, &len
);
4048 TRACE("EM_REPLACESEL - %s\n", debugstr_w(wszText
));
4050 ME_ReplaceSel(editor
, !!wParam
, wszText
, len
);
4051 ME_EndToUnicode(codepage
, wszText
);
4054 case EM_SCROLLCARET
:
4055 ME_EnsureVisible(editor
, &editor
->pCursors
[0]);
4062 BOOL bRepaint
= LOWORD(lParam
);
4065 wParam
= (WPARAM
)GetStockObject(SYSTEM_FONT
);
4067 if (!GetObjectW((HGDIOBJ
)wParam
, sizeof(LOGFONTW
), &lf
))
4070 hDC
= ITextHost_TxGetDC(editor
->texthost
);
4071 ME_CharFormatFromLogFont(hDC
, &lf
, &fmt
);
4072 ITextHost_TxReleaseDC(editor
->texthost
, hDC
);
4073 if (editor
->mode
& TM_RICHTEXT
) {
4075 ME_SetCursorToStart(editor
, &start
);
4076 ME_SetCharFormat(editor
, &start
, NULL
, &fmt
);
4078 ME_SetDefaultCharFormat(editor
, &fmt
);
4080 ME_CommitUndo(editor
);
4081 ME_MarkAllForWrapping(editor
);
4082 ME_WrapMarkedParagraphs(editor
);
4083 ME_UpdateScrollBar(editor
);
4091 ME_SetCursorToStart(editor
, &cursor
);
4092 ME_InternalDeleteText(editor
, &cursor
, ME_GetTextLength(editor
), FALSE
);
4095 TRACE("WM_SETTEXT lParam==%lx\n",lParam
);
4096 if (!strncmp((char *)lParam
, "{\\rtf", 5) ||
4097 !strncmp((char *)lParam
, "{\\urtf", 6))
4099 /* Undocumented: WM_SETTEXT supports RTF text */
4100 ME_StreamInRTFString(editor
, 0, (char *)lParam
);
4103 ME_SetText(editor
, (void*)lParam
, unicode
);
4106 TRACE("WM_SETTEXT - NULL\n");
4107 ME_SetCursorToStart(editor
, &cursor
);
4108 ME_UpdateLinkAttribute(editor
, &cursor
, INT_MAX
);
4109 ME_SetSelection(editor
, 0, 0);
4110 editor
->nModifyStep
= 0;
4111 ME_CommitUndo(editor
);
4112 ME_EmptyUndoStack(editor
);
4113 ME_UpdateRepaint(editor
, FALSE
);
4117 return paste_special( editor
, 0, NULL
, TRUE
);
4119 case WM_MBUTTONDOWN
:
4123 case EM_PASTESPECIAL
:
4124 paste_special( editor
, wParam
, (REPASTESPECIAL
*)lParam
, FALSE
);
4128 copy_or_cut(editor
, msg
== WM_CUT
);
4130 case WM_GETTEXTLENGTH
:
4132 GETTEXTLENGTHEX how
;
4134 /* CR/LF conversion required in 2.0 mode, verbatim in 1.0 mode */
4135 how
.flags
= GTL_CLOSE
| (editor
->bEmulateVersion10
? 0 : GTL_USECRLF
) | GTL_NUMCHARS
;
4136 how
.codepage
= unicode
? CP_UNICODE
: CP_ACP
;
4137 return ME_GetTextLengthEx(editor
, &how
);
4139 case EM_GETTEXTLENGTHEX
:
4140 return ME_GetTextLengthEx(editor
, (GETTEXTLENGTHEX
*)wParam
);
4144 ex
.cb
= wParam
* (unicode
? sizeof(WCHAR
) : sizeof(CHAR
));
4145 ex
.flags
= GT_USECRLF
;
4146 ex
.codepage
= unicode
? CP_UNICODE
: CP_ACP
;
4147 ex
.lpDefaultChar
= NULL
;
4148 ex
.lpUsedDefChar
= NULL
;
4149 return ME_GetTextEx(editor
, &ex
, lParam
);
4152 return ME_GetTextEx(editor
, (GETTEXTEX
*)wParam
, lParam
);
4155 int nFrom
, nTo
, nStartCur
= ME_GetSelectionOfs(editor
, &nFrom
, &nTo
);
4156 ME_Cursor
*from
= &editor
->pCursors
[nStartCur
];
4157 return ME_GetTextRange(editor
, (WCHAR
*)lParam
, from
,
4158 nTo
- nFrom
, unicode
);
4160 case EM_GETSCROLLPOS
:
4162 POINT
*point
= (POINT
*)lParam
;
4163 point
->x
= editor
->horz_si
.nPos
;
4164 point
->y
= editor
->vert_si
.nPos
;
4165 /* 16-bit scaled value is returned as stored in scrollinfo */
4166 if (editor
->horz_si
.nMax
> 0xffff)
4167 point
->x
= MulDiv(point
->x
, 0xffff, editor
->horz_si
.nMax
);
4168 if (editor
->vert_si
.nMax
> 0xffff)
4169 point
->y
= MulDiv(point
->y
, 0xffff, editor
->vert_si
.nMax
);
4172 case EM_GETTEXTRANGE
:
4174 TEXTRANGEW
*rng
= (TEXTRANGEW
*)lParam
;
4176 int nStart
= rng
->chrg
.cpMin
;
4177 int nEnd
= rng
->chrg
.cpMax
;
4178 int textlength
= ME_GetTextLength(editor
);
4180 TRACE("EM_GETTEXTRANGE min=%d max=%d unicode=%d textlength=%d\n",
4181 rng
->chrg
.cpMin
, rng
->chrg
.cpMax
, unicode
, textlength
);
4182 if (nStart
< 0) return 0;
4183 if ((nStart
== 0 && nEnd
== -1) || nEnd
> textlength
)
4185 if (nStart
>= nEnd
) return 0;
4187 ME_CursorFromCharOfs(editor
, nStart
, &start
);
4188 return ME_GetTextRange(editor
, rng
->lpstrText
, &start
, nEnd
- nStart
, unicode
);
4192 ME_DisplayItem
*run
;
4193 const unsigned int nMaxChars
= *(WORD
*) lParam
;
4194 unsigned int nCharsLeft
= nMaxChars
;
4195 char *dest
= (char *) lParam
;
4196 BOOL wroteNull
= FALSE
;
4198 TRACE("EM_GETLINE: row=%d, nMaxChars=%d (%s)\n", (int) wParam
, nMaxChars
,
4199 unicode
? "Unicode" : "Ansi");
4201 run
= ME_FindRowWithNumber(editor
, wParam
);
4205 while (nCharsLeft
&& (run
= ME_FindItemFwd(run
, diRunOrStartRow
))
4206 && run
->type
== diRun
)
4208 WCHAR
*str
= get_text( &run
->member
.run
, 0 );
4211 nCopy
= min(nCharsLeft
, run
->member
.run
.len
);
4214 memcpy(dest
, str
, nCopy
* sizeof(WCHAR
));
4216 nCopy
= WideCharToMultiByte(CP_ACP
, 0, str
, nCopy
, dest
,
4217 nCharsLeft
, NULL
, NULL
);
4218 dest
+= nCopy
* (unicode
? sizeof(WCHAR
) : 1);
4219 nCharsLeft
-= nCopy
;
4222 /* append line termination, space allowing */
4226 *((WCHAR
*)dest
) = '\0';
4233 TRACE("EM_GETLINE: got %u characters\n", nMaxChars
- nCharsLeft
);
4234 return nMaxChars
- nCharsLeft
- (wroteNull
? 1 : 0);
4236 case EM_GETLINECOUNT
:
4238 ME_DisplayItem
*item
= editor
->pBuffer
->pLast
;
4239 int nRows
= editor
->total_rows
;
4240 ME_DisplayItem
*prev_para
= NULL
, *last_para
= NULL
;
4242 last_para
= ME_FindItemBack(item
, diRun
);
4243 prev_para
= ME_FindItemBack(last_para
, diRun
);
4245 assert(last_para
->member
.run
.nFlags
& MERF_ENDPARA
);
4246 if (editor
->bEmulateVersion10
&& prev_para
&&
4247 last_para
->member
.run
.nCharOfs
== 0 &&
4248 prev_para
->member
.run
.len
== 1 &&
4249 *get_text( &prev_para
->member
.run
, 0 ) == '\r')
4251 /* In 1.0 emulation, the last solitary \r at the very end of the text
4252 (if one exists) is NOT a line break.
4253 FIXME: this is an ugly hack. This should have a more regular model. */
4257 TRACE("EM_GETLINECOUNT: nRows==%d\n", nRows
);
4258 return max(1, nRows
);
4260 case EM_LINEFROMCHAR
:
4263 return ME_RowNumberFromCharOfs(editor
, ME_GetCursorOfs(&editor
->pCursors
[1]));
4265 return ME_RowNumberFromCharOfs(editor
, wParam
);
4267 case EM_EXLINEFROMCHAR
:
4270 return ME_RowNumberFromCharOfs(editor
, ME_GetCursorOfs(&editor
->pCursors
[1]));
4272 return ME_RowNumberFromCharOfs(editor
, lParam
);
4276 ME_DisplayItem
*item
, *para
;
4280 item
= ME_FindItemBack(editor
->pCursors
[0].pRun
, diStartRow
);
4282 item
= ME_FindRowWithNumber(editor
, wParam
);
4285 para
= ME_GetParagraph(item
);
4286 item
= ME_FindItemFwd(item
, diRun
);
4287 nCharOfs
= para
->member
.para
.nCharOfs
+ item
->member
.run
.nCharOfs
;
4288 TRACE("EM_LINEINDEX: nCharOfs==%d\n", nCharOfs
);
4293 ME_DisplayItem
*item
, *item_end
;
4294 int nChars
= 0, nThisLineOfs
= 0, nNextLineOfs
= 0;
4295 ME_DisplayItem
*para
, *run
;
4297 if (wParam
> ME_GetTextLength(editor
))
4301 FIXME("EM_LINELENGTH: returning number of unselected characters on lines with selection unsupported.\n");
4304 ME_RunOfsFromCharOfs(editor
, wParam
, ¶
, &run
, NULL
);
4305 item
= ME_RowStart(run
);
4306 nThisLineOfs
= ME_CharOfsFromRunOfs(editor
, para
, ME_FindItemFwd(item
, diRun
), 0);
4307 item_end
= ME_FindItemFwd(item
, diStartRowOrParagraphOrEnd
);
4308 if (item_end
->type
== diStartRow
) {
4309 nNextLineOfs
= ME_CharOfsFromRunOfs(editor
, para
, ME_FindItemFwd(item_end
, diRun
), 0);
4311 ME_DisplayItem
*endRun
= ME_FindItemBack(item_end
, diRun
);
4312 assert(endRun
&& endRun
->member
.run
.nFlags
& MERF_ENDPARA
);
4313 nNextLineOfs
= item_end
->member
.para
.nCharOfs
- endRun
->member
.run
.len
;
4315 nChars
= nNextLineOfs
- nThisLineOfs
;
4316 TRACE("EM_LINELENGTH(%ld)==%d\n",wParam
, nChars
);
4319 case EM_EXLIMITTEXT
:
4321 if ((int)lParam
< 0)
4324 editor
->nTextLimit
= 65536;
4326 editor
->nTextLimit
= (int) lParam
;
4332 editor
->nTextLimit
= 65536;
4334 editor
->nTextLimit
= (int) wParam
;
4337 case EM_GETLIMITTEXT
:
4339 return editor
->nTextLimit
;
4345 FINDTEXTA
*ft
= (FINDTEXTA
*)lParam
;
4346 int nChars
= MultiByteToWideChar(CP_ACP
, 0, ft
->lpstrText
, -1, NULL
, 0);
4349 if ((tmp
= heap_alloc(nChars
* sizeof(*tmp
))) != NULL
)
4350 MultiByteToWideChar(CP_ACP
, 0, ft
->lpstrText
, -1, tmp
, nChars
);
4351 r
= ME_FindText(editor
, wParam
, &ft
->chrg
, tmp
, NULL
);
4354 FINDTEXTW
*ft
= (FINDTEXTW
*)lParam
;
4355 r
= ME_FindText(editor
, wParam
, &ft
->chrg
, ft
->lpstrText
, NULL
);
4363 FINDTEXTEXA
*ex
= (FINDTEXTEXA
*)lParam
;
4364 int nChars
= MultiByteToWideChar(CP_ACP
, 0, ex
->lpstrText
, -1, NULL
, 0);
4367 if ((tmp
= heap_alloc(nChars
* sizeof(*tmp
))) != NULL
)
4368 MultiByteToWideChar(CP_ACP
, 0, ex
->lpstrText
, -1, tmp
, nChars
);
4369 r
= ME_FindText(editor
, wParam
, &ex
->chrg
, tmp
, &ex
->chrgText
);
4372 FINDTEXTEXW
*ex
= (FINDTEXTEXW
*)lParam
;
4373 r
= ME_FindText(editor
, wParam
, &ex
->chrg
, ex
->lpstrText
, &ex
->chrgText
);
4379 FINDTEXTW
*ft
= (FINDTEXTW
*)lParam
;
4380 return ME_FindText(editor
, wParam
, &ft
->chrg
, ft
->lpstrText
, NULL
);
4382 case EM_FINDTEXTEXW
:
4384 FINDTEXTEXW
*ex
= (FINDTEXTEXW
*)lParam
;
4385 return ME_FindText(editor
, wParam
, &ex
->chrg
, ex
->lpstrText
, &ex
->chrgText
);
4388 if (!wParam
|| !lParam
)
4390 *(int *)wParam
= editor
->nZoomNumerator
;
4391 *(int *)lParam
= editor
->nZoomDenominator
;
4394 return ME_SetZoom(editor
, wParam
, lParam
);
4395 case EM_CHARFROMPOS
:
4398 if (ME_CharFromPos(editor
, ((POINTL
*)lParam
)->x
, ((POINTL
*)lParam
)->y
,
4400 return ME_GetCursorOfs(&cursor
);
4404 case EM_POSFROMCHAR
:
4406 ME_DisplayItem
*pPara
, *pRun
;
4407 int nCharOfs
, nOffset
, nLength
;
4411 /* detect which API version we're dealing with */
4412 if (wParam
>= 0x40000)
4414 nLength
= ME_GetTextLength(editor
);
4415 nCharOfs
= min(nCharOfs
, nLength
);
4416 nCharOfs
= max(nCharOfs
, 0);
4418 ME_RunOfsFromCharOfs(editor
, nCharOfs
, &pPara
, &pRun
, &nOffset
);
4419 assert(pRun
->type
== diRun
);
4420 pt
.y
= pRun
->member
.run
.pt
.y
;
4421 pt
.x
= pRun
->member
.run
.pt
.x
+ ME_PointFromChar(editor
, &pRun
->member
.run
, nOffset
, TRUE
);
4422 pt
.y
+= pPara
->member
.para
.pt
.y
+ editor
->rcFormat
.top
;
4423 pt
.x
+= editor
->rcFormat
.left
;
4425 pt
.x
-= editor
->horz_si
.nPos
;
4426 pt
.y
-= editor
->vert_si
.nPos
;
4428 if (wParam
>= 0x40000) {
4429 *(POINTL
*)wParam
= pt
;
4431 return (wParam
>= 0x40000) ? 0 : MAKELONG( pt
.x
, pt
.y
);
4434 return ME_WmCreate(editor
, lParam
, unicode
);
4436 ME_DestroyEditor(editor
);
4441 if (wParam
== (WPARAM
)editor
->hWnd
&& GetCursorPos(&cursor_pos
) &&
4442 ScreenToClient(editor
->hWnd
, &cursor_pos
))
4443 ME_LinkNotify(editor
, msg
, 0, MAKELPARAM(cursor_pos
.x
, cursor_pos
.y
));
4444 return ME_SetCursor(editor
);
4446 case WM_LBUTTONDBLCLK
:
4447 case WM_LBUTTONDOWN
:
4449 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
4450 if ((editor
->nEventMask
& ENM_MOUSEEVENTS
) &&
4451 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4453 ITextHost_TxSetFocus(editor
->texthost
);
4454 ME_LButtonDown(editor
, (short)LOWORD(lParam
), (short)HIWORD(lParam
),
4455 ME_CalculateClickCount(editor
, msg
, wParam
, lParam
));
4456 ITextHost_TxSetCapture(editor
->texthost
, TRUE
);
4457 editor
->bMouseCaptured
= TRUE
;
4458 ME_LinkNotify(editor
, msg
, wParam
, lParam
);
4459 if (!ME_SetCursor(editor
)) goto do_default
;
4463 if ((editor
->nEventMask
& ENM_MOUSEEVENTS
) &&
4464 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4466 if (editor
->bMouseCaptured
)
4467 ME_MouseMove(editor
, (short)LOWORD(lParam
), (short)HIWORD(lParam
));
4469 ME_LinkNotify(editor
, msg
, wParam
, lParam
);
4470 /* Set cursor if mouse is captured, since WM_SETCURSOR won't be received. */
4471 if (editor
->bMouseCaptured
)
4472 ME_SetCursor(editor
);
4475 if (editor
->bMouseCaptured
) {
4476 ITextHost_TxSetCapture(editor
->texthost
, FALSE
);
4477 editor
->bMouseCaptured
= FALSE
;
4479 if (editor
->nSelectionType
== stDocument
)
4480 editor
->nSelectionType
= stPosition
;
4481 if ((editor
->nEventMask
& ENM_MOUSEEVENTS
) &&
4482 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4486 ME_SetCursor(editor
);
4487 ME_LinkNotify(editor
, msg
, wParam
, lParam
);
4491 case WM_RBUTTONDOWN
:
4492 case WM_RBUTTONDBLCLK
:
4493 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
4494 if ((editor
->nEventMask
& ENM_MOUSEEVENTS
) &&
4495 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4497 ME_LinkNotify(editor
, msg
, wParam
, lParam
);
4499 case WM_CONTEXTMENU
:
4500 if (!ME_ShowContextMenu(editor
, (short)LOWORD(lParam
), (short)HIWORD(lParam
)))
4504 editor
->bHaveFocus
= TRUE
;
4505 ME_ShowCaret(editor
);
4506 ME_SendOldNotify(editor
, EN_SETFOCUS
);
4507 if (!editor
->bHideSelection
&& !(editor
->styleFlags
& ES_NOHIDESEL
))
4508 ME_InvalidateSelection( editor
);
4511 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
4512 editor
->bHaveFocus
= FALSE
;
4513 editor
->wheel_remain
= 0;
4514 ME_HideCaret(editor
);
4515 ME_SendOldNotify(editor
, EN_KILLFOCUS
);
4516 if (!editor
->bHideSelection
&& !(editor
->styleFlags
& ES_NOHIDESEL
))
4517 ME_InvalidateSelection( editor
);
4520 TRACE("editor wnd command = %d\n", LOWORD(wParam
));
4523 if ((editor
->nEventMask
& ENM_KEYEVENTS
) &&
4524 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4528 if ((editor
->nEventMask
& ENM_KEYEVENTS
) &&
4529 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4531 if (ME_KeyDown(editor
, LOWORD(wParam
)))
4535 if ((editor
->nEventMask
& ENM_KEYEVENTS
) &&
4536 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4538 return ME_Char(editor
, wParam
, lParam
, unicode
);
4542 if(wParam
== UNICODE_NOCHAR
) return TRUE
;
4543 if(wParam
<= 0x000fffff)
4545 if(wParam
> 0xffff) /* convert to surrogates */
4548 ME_Char(editor
, (wParam
>> 10) + 0xd800, 0, TRUE
);
4549 ME_Char(editor
, (wParam
& 0x03ff) + 0xdc00, 0, TRUE
);
4551 ME_Char(editor
, wParam
, 0, TRUE
);
4557 case EM_STOPGROUPTYPING
:
4558 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
4562 const int scrollUnit
= 7;
4564 switch(LOWORD(wParam
))
4567 ME_ScrollAbs(editor
, 0, 0);
4570 ME_ScrollAbs(editor
,
4571 editor
->horz_si
.nMax
- (int)editor
->horz_si
.nPage
,
4572 editor
->vert_si
.nMax
- (int)editor
->vert_si
.nPage
);
4575 ME_ScrollLeft(editor
, scrollUnit
);
4578 ME_ScrollRight(editor
, scrollUnit
);
4581 ME_ScrollLeft(editor
, editor
->sizeWindow
.cx
);
4584 ME_ScrollRight(editor
, editor
->sizeWindow
.cx
);
4587 case SB_THUMBPOSITION
:
4589 int pos
= HIWORD(wParam
);
4590 if (editor
->horz_si
.nMax
> 0xffff)
4591 pos
= MulDiv(pos
, editor
->horz_si
.nMax
, 0xffff);
4592 ME_HScrollAbs(editor
, pos
);
4598 case EM_SCROLL
: /* fall through */
4602 int lineHeight
= get_default_line_height( editor
);
4604 origNPos
= editor
->vert_si
.nPos
;
4606 switch(LOWORD(wParam
))
4609 ME_ScrollAbs(editor
, 0, 0);
4612 ME_ScrollAbs(editor
,
4613 editor
->horz_si
.nMax
- (int)editor
->horz_si
.nPage
,
4614 editor
->vert_si
.nMax
- (int)editor
->vert_si
.nPage
);
4617 ME_ScrollUp(editor
,lineHeight
);
4620 ME_ScrollDown(editor
,lineHeight
);
4623 ME_ScrollUp(editor
,editor
->sizeWindow
.cy
);
4626 ME_ScrollDown(editor
,editor
->sizeWindow
.cy
);
4629 case SB_THUMBPOSITION
:
4631 int pos
= HIWORD(wParam
);
4632 if (editor
->vert_si
.nMax
> 0xffff)
4633 pos
= MulDiv(pos
, editor
->vert_si
.nMax
, 0xffff);
4634 ME_VScrollAbs(editor
, pos
);
4638 if (msg
== EM_SCROLL
)
4639 return 0x00010000 | (((editor
->vert_si
.nPos
- origNPos
)/lineHeight
) & 0xffff);
4647 if ((editor
->nEventMask
& ENM_MOUSEEVENTS
) &&
4648 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4651 ctrl_is_down
= GetKeyState(VK_CONTROL
) & 0x8000;
4653 delta
= GET_WHEEL_DELTA_WPARAM(wParam
);
4655 /* if scrolling changes direction, ignore left overs */
4656 if ((delta
< 0 && editor
->wheel_remain
< 0) ||
4657 (delta
> 0 && editor
->wheel_remain
> 0))
4658 editor
->wheel_remain
+= delta
;
4660 editor
->wheel_remain
= delta
;
4662 if (editor
->wheel_remain
)
4666 if (!editor
->nZoomNumerator
|| !editor
->nZoomDenominator
)
4670 numerator
= editor
->nZoomNumerator
* 100 / editor
->nZoomDenominator
;
4672 numerator
+= calc_wheel_change( &editor
->wheel_remain
, 10 );
4673 if (numerator
>= 10 && numerator
<= 500)
4674 ME_SetZoom(editor
, numerator
, 100);
4679 SystemParametersInfoW( SPI_GETWHEELSCROLLLINES
, 0, &max_lines
, 0 );
4681 lines
= calc_wheel_change( &editor
->wheel_remain
, (int)max_lines
);
4683 ME_ScrollDown( editor
, -lines
* get_default_line_height( editor
) );
4690 *((RECT
*)lParam
) = editor
->rcFormat
;
4691 if (editor
->bDefaultFormatRect
)
4692 ((RECT
*)lParam
)->left
-= editor
->selofs
;
4702 RECT
*rc
= (RECT
*)lParam
;
4704 border
= editor
->exStyleFlags
& WS_EX_CLIENTEDGE
? 1 : 0;
4705 ITextHost_TxGetClientRect(editor
->texthost
, &clientRect
);
4708 editor
->rcFormat
.top
= max(0, rc
->top
- border
);
4709 editor
->rcFormat
.left
= max(0, rc
->left
- border
);
4710 editor
->rcFormat
.bottom
= min(clientRect
.bottom
, rc
->bottom
);
4711 editor
->rcFormat
.right
= min(clientRect
.right
, rc
->right
+ border
);
4712 } else if (wParam
== 1) {
4713 /* MSDN incorrectly says a wParam value of 1 causes the
4714 * lParam rect to be used as a relative offset,
4715 * however, the tests show it just prevents min/max bound
4717 editor
->rcFormat
.top
= rc
->top
- border
;
4718 editor
->rcFormat
.left
= rc
->left
- border
;
4719 editor
->rcFormat
.bottom
= rc
->bottom
;
4720 editor
->rcFormat
.right
= rc
->right
+ border
;
4724 editor
->bDefaultFormatRect
= FALSE
;
4728 ME_SetDefaultFormatRect(editor
);
4729 editor
->bDefaultFormatRect
= TRUE
;
4731 ME_MarkAllForWrapping(editor
);
4732 ME_WrapMarkedParagraphs(editor
);
4733 ME_UpdateScrollBar(editor
);
4734 if (msg
!= EM_SETRECTNP
)
4738 case EM_REQUESTRESIZE
:
4739 ME_SendRequestResize(editor
, TRUE
);
4743 case WM_WINDOWPOSCHANGED
:
4746 WINDOWPOS
*winpos
= (WINDOWPOS
*)lParam
;
4748 if (winpos
->flags
& SWP_NOCLIENTSIZE
) goto do_default
;
4749 ITextHost_TxGetClientRect(editor
->texthost
, &clientRect
);
4750 if (editor
->bDefaultFormatRect
) {
4751 ME_SetDefaultFormatRect(editor
);
4753 editor
->rcFormat
.right
+= clientRect
.right
- editor
->prevClientRect
.right
;
4754 editor
->rcFormat
.bottom
+= clientRect
.bottom
- editor
->prevClientRect
.bottom
;
4756 editor
->prevClientRect
= clientRect
;
4757 ME_RewrapRepaint(editor
);
4760 /* IME messages to make richedit controls IME aware */
4761 case WM_IME_SETCONTEXT
:
4762 case WM_IME_CONTROL
:
4764 case WM_IME_COMPOSITIONFULL
:
4766 case WM_IME_STARTCOMPOSITION
:
4768 editor
->imeStartIndex
=ME_GetCursorOfs(&editor
->pCursors
[0]);
4769 ME_DeleteSelection(editor
);
4770 ME_CommitUndo(editor
);
4771 ME_UpdateRepaint(editor
, FALSE
);
4774 case WM_IME_COMPOSITION
:
4778 ME_Style
*style
= ME_GetInsertStyle(editor
, 0);
4779 hIMC
= ITextHost_TxImmGetContext(editor
->texthost
);
4780 ME_DeleteSelection(editor
);
4781 ME_SaveTempStyle(editor
, style
);
4782 if (lParam
& (GCS_RESULTSTR
|GCS_COMPSTR
))
4784 LPWSTR lpCompStr
= NULL
;
4786 DWORD dwIndex
= lParam
& GCS_RESULTSTR
;
4788 dwIndex
= GCS_COMPSTR
;
4790 dwBufLen
= ImmGetCompositionStringW(hIMC
, dwIndex
, NULL
, 0);
4791 lpCompStr
= HeapAlloc(GetProcessHeap(),0,dwBufLen
+ sizeof(WCHAR
));
4792 ImmGetCompositionStringW(hIMC
, dwIndex
, lpCompStr
, dwBufLen
);
4793 lpCompStr
[dwBufLen
/sizeof(WCHAR
)] = 0;
4794 ME_InsertTextFromCursor(editor
,0,lpCompStr
,dwBufLen
/sizeof(WCHAR
),style
);
4795 HeapFree(GetProcessHeap(), 0, lpCompStr
);
4797 if (dwIndex
== GCS_COMPSTR
)
4798 ME_SetSelection(editor
,editor
->imeStartIndex
,
4799 editor
->imeStartIndex
+ dwBufLen
/sizeof(WCHAR
));
4801 ME_ReleaseStyle(style
);
4802 ME_CommitUndo(editor
);
4803 ME_UpdateRepaint(editor
, FALSE
);
4806 case WM_IME_ENDCOMPOSITION
:
4808 ME_DeleteSelection(editor
);
4809 editor
->imeStartIndex
=-1;
4812 case EM_GETOLEINTERFACE
:
4815 if (!CreateIRichEditOle(NULL
, editor
, (LPVOID
*)&editor
->reOle
))
4817 *(LPVOID
*)lParam
= editor
->reOle
;
4818 IRichEditOle_AddRef(editor
->reOle
);
4821 case EM_GETPASSWORDCHAR
:
4823 return editor
->cPasswordMask
;
4825 case EM_SETOLECALLBACK
:
4826 if(editor
->lpOleCallback
)
4827 IRichEditOleCallback_Release(editor
->lpOleCallback
);
4828 editor
->lpOleCallback
= (IRichEditOleCallback
*)lParam
;
4829 if(editor
->lpOleCallback
)
4830 IRichEditOleCallback_AddRef(editor
->lpOleCallback
);
4832 case EM_GETWORDBREAKPROC
:
4833 return (LRESULT
)editor
->pfnWordBreak
;
4834 case EM_SETWORDBREAKPROC
:
4836 EDITWORDBREAKPROCW pfnOld
= editor
->pfnWordBreak
;
4838 editor
->pfnWordBreak
= (EDITWORDBREAKPROCW
)lParam
;
4839 return (LRESULT
)pfnOld
;
4841 case EM_GETTEXTMODE
:
4842 return editor
->mode
;
4843 case EM_SETTEXTMODE
:
4848 if (ME_GetTextLength(editor
) ||
4849 !list_empty( &editor
->undo_stack
) || !list_empty( &editor
->redo_stack
))
4850 return E_UNEXPECTED
;
4852 /* Check for mutually exclusive flags in adjacent bits of wParam */
4853 if ((wParam
& (TM_RICHTEXT
| TM_MULTILEVELUNDO
| TM_MULTICODEPAGE
)) &
4854 (wParam
& (TM_PLAINTEXT
| TM_SINGLELEVELUNDO
| TM_SINGLECODEPAGE
)) << 1)
4855 return E_INVALIDARG
;
4857 if (wParam
& (TM_RICHTEXT
| TM_PLAINTEXT
))
4859 mask
|= TM_RICHTEXT
| TM_PLAINTEXT
;
4860 changes
|= wParam
& (TM_RICHTEXT
| TM_PLAINTEXT
);
4861 if (wParam
& TM_PLAINTEXT
) {
4862 /* Clear selection since it should be possible to select the
4863 * end of text run for rich text */
4864 ME_InvalidateSelection(editor
);
4865 ME_SetCursorToStart(editor
, &editor
->pCursors
[0]);
4866 editor
->pCursors
[1] = editor
->pCursors
[0];
4867 /* plain text can only have the default style. */
4868 ME_ClearTempStyle(editor
);
4869 ME_AddRefStyle(editor
->pBuffer
->pDefaultStyle
);
4870 ME_ReleaseStyle(editor
->pCursors
[0].pRun
->member
.run
.style
);
4871 editor
->pCursors
[0].pRun
->member
.run
.style
= editor
->pBuffer
->pDefaultStyle
;
4874 /* FIXME: Currently no support for undo level and code page options */
4875 editor
->mode
= (editor
->mode
& ~mask
) | changes
;
4878 case EM_SETPASSWORDCHAR
:
4880 editor
->cPasswordMask
= wParam
;
4881 ME_RewrapRepaint(editor
);
4884 case EM_SETTARGETDEVICE
:
4887 BOOL
new = (lParam
== 0 && (editor
->styleFlags
& ES_MULTILINE
));
4888 if (editor
->nAvailWidth
|| editor
->bWordWrap
!= new)
4890 editor
->bWordWrap
= new;
4891 editor
->nAvailWidth
= 0; /* wrap to client area */
4892 ME_RewrapRepaint(editor
);
4895 int width
= max(0, lParam
);
4896 if ((editor
->styleFlags
& ES_MULTILINE
) &&
4897 (!editor
->bWordWrap
|| editor
->nAvailWidth
!= width
))
4899 editor
->nAvailWidth
= width
;
4900 editor
->bWordWrap
= TRUE
;
4901 ME_RewrapRepaint(editor
);
4903 FIXME("EM_SETTARGETDEVICE doesn't use non-NULL target devices\n");
4908 *phresult
= S_FALSE
;
4914 static BOOL
create_windowed_editor(HWND hwnd
, CREATESTRUCTW
*create
, BOOL emulate_10
)
4916 ITextHost
*host
= ME_CreateTextHost( hwnd
, create
, emulate_10
);
4917 ME_TextEditor
*editor
;
4919 if (!host
) return FALSE
;
4921 editor
= ME_MakeEditor( host
, emulate_10
);
4924 ITextHost_Release( host
);
4928 editor
->exStyleFlags
= GetWindowLongW( hwnd
, GWL_EXSTYLE
);
4929 editor
->styleFlags
|= GetWindowLongW( hwnd
, GWL_STYLE
) & ES_WANTRETURN
;
4930 editor
->hWnd
= hwnd
; /* FIXME: Remove editor's dependence on hWnd */
4931 editor
->hwndParent
= create
->hwndParent
;
4933 SetWindowLongPtrW( hwnd
, 0, (LONG_PTR
)editor
);
4938 static LRESULT
RichEditWndProc_common(HWND hWnd
, UINT msg
, WPARAM wParam
,
4939 LPARAM lParam
, BOOL unicode
)
4941 ME_TextEditor
*editor
;
4943 LRESULT lresult
= 0;
4945 TRACE("enter hwnd %p msg %04x (%s) %lx %lx, unicode %d\n",
4946 hWnd
, msg
, get_msg_name(msg
), wParam
, lParam
, unicode
);
4948 editor
= (ME_TextEditor
*)GetWindowLongPtrW(hWnd
, 0);
4951 if (msg
== WM_NCCREATE
)
4953 CREATESTRUCTW
*pcs
= (CREATESTRUCTW
*)lParam
;
4955 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd
, pcs
->style
);
4956 return create_windowed_editor( hWnd
, pcs
, FALSE
);
4960 return DefWindowProcW(hWnd
, msg
, wParam
, lParam
);
4972 hDC
= BeginPaint(editor
->hWnd
, &ps
);
4973 if (!editor
->bEmulateVersion10
|| (editor
->nEventMask
& ENM_UPDATE
))
4974 ME_SendOldNotify(editor
, EN_UPDATE
);
4975 /* Erase area outside of the formatting rectangle */
4976 if (ps
.rcPaint
.top
< editor
->rcFormat
.top
)
4979 rc
.bottom
= editor
->rcFormat
.top
;
4980 FillRect(hDC
, &rc
, editor
->hbrBackground
);
4981 ps
.rcPaint
.top
= editor
->rcFormat
.top
;
4983 if (ps
.rcPaint
.bottom
> editor
->rcFormat
.bottom
) {
4985 rc
.top
= editor
->rcFormat
.bottom
;
4986 FillRect(hDC
, &rc
, editor
->hbrBackground
);
4987 ps
.rcPaint
.bottom
= editor
->rcFormat
.bottom
;
4989 if (ps
.rcPaint
.left
< editor
->rcFormat
.left
) {
4991 rc
.right
= editor
->rcFormat
.left
;
4992 FillRect(hDC
, &rc
, editor
->hbrBackground
);
4993 ps
.rcPaint
.left
= editor
->rcFormat
.left
;
4995 if (ps
.rcPaint
.right
> editor
->rcFormat
.right
) {
4997 rc
.left
= editor
->rcFormat
.right
;
4998 FillRect(hDC
, &rc
, editor
->hbrBackground
);
4999 ps
.rcPaint
.right
= editor
->rcFormat
.right
;
5002 ME_PaintContent(editor
, hDC
, &ps
.rcPaint
);
5003 EndPaint(editor
->hWnd
, &ps
);
5008 HDC hDC
= (HDC
)wParam
;
5011 if (GetUpdateRect(editor
->hWnd
, &rc
, TRUE
))
5012 FillRect(hDC
, &rc
, editor
->hbrBackground
);
5018 const DWORD mask
= ECO_VERTICAL
| ECO_AUTOHSCROLL
| ECO_AUTOVSCROLL
|
5019 ECO_NOHIDESEL
| ECO_READONLY
| ECO_WANTRETURN
|
5021 lresult
= ME_HandleMessage(editor
, msg
, wParam
, lParam
, unicode
, &hresult
);
5022 dwStyle
= GetWindowLongW(hWnd
, GWL_STYLE
);
5023 dwStyle
= (dwStyle
& ~mask
) | (lresult
& mask
);
5024 SetWindowLongW(hWnd
, GWL_STYLE
, dwStyle
);
5027 case EM_SETREADONLY
:
5030 lresult
= ME_HandleMessage(editor
, msg
, wParam
, lParam
, unicode
, &hresult
);
5031 dwStyle
= GetWindowLongW(hWnd
, GWL_STYLE
);
5032 dwStyle
&= ~ES_READONLY
;
5034 dwStyle
|= ES_READONLY
;
5035 SetWindowLongW(hWnd
, GWL_STYLE
, dwStyle
);
5039 lresult
= ME_HandleMessage(editor
, msg
, wParam
, lParam
, unicode
, &hresult
);
5042 if (hresult
== S_FALSE
)
5043 lresult
= DefWindowProcW(hWnd
, msg
, wParam
, lParam
);
5045 TRACE("exit hwnd %p msg %04x (%s) %lx %lx, unicode %d -> %lu\n",
5046 hWnd
, msg
, get_msg_name(msg
), wParam
, lParam
, unicode
, lresult
);
5051 static LRESULT WINAPI
RichEditWndProcW(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
5053 BOOL unicode
= TRUE
;
5055 /* Under Win9x RichEdit20W returns ANSI strings, see the tests. */
5056 if (msg
== WM_GETTEXT
&& (GetVersion() & 0x80000000))
5059 return RichEditWndProc_common(hWnd
, msg
, wParam
, lParam
, unicode
);
5062 static LRESULT WINAPI
RichEditWndProcA(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
5064 return RichEditWndProc_common(hWnd
, msg
, wParam
, lParam
, FALSE
);
5067 /******************************************************************
5068 * RichEditANSIWndProc (RICHED20.10)
5070 LRESULT WINAPI
RichEditANSIWndProc(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
5072 return RichEditWndProcA(hWnd
, msg
, wParam
, lParam
);
5075 /******************************************************************
5076 * RichEdit10ANSIWndProc (RICHED20.9)
5078 LRESULT WINAPI
RichEdit10ANSIWndProc(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
5080 if (msg
== WM_NCCREATE
&& !GetWindowLongPtrW(hWnd
, 0))
5082 CREATESTRUCTW
*pcs
= (CREATESTRUCTW
*)lParam
;
5084 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd
, pcs
->style
);
5085 return create_windowed_editor( hWnd
, pcs
, TRUE
);
5087 return RichEditANSIWndProc(hWnd
, msg
, wParam
, lParam
);
5090 void ME_SendOldNotify(ME_TextEditor
*editor
, int nCode
)
5092 ITextHost_TxNotify(editor
->texthost
, nCode
, NULL
);
5095 /* Fill buffer with srcChars unicode characters from the start cursor.
5097 * buffer: destination buffer
5098 * buflen: length of buffer in characters excluding the NULL terminator.
5099 * start: start of editor text to copy into buffer.
5100 * srcChars: Number of characters to use from the editor text.
5101 * bCRLF: if true, replaces all end of lines with \r\n pairs.
5103 * returns the number of characters written excluding the NULL terminator.
5105 * The written text is always NULL terminated.
5107 int ME_GetTextW(ME_TextEditor
*editor
, WCHAR
*buffer
, int buflen
,
5108 const ME_Cursor
*start
, int srcChars
, BOOL bCRLF
,
5111 ME_DisplayItem
*pRun
, *pNextRun
;
5112 const WCHAR
*pStart
= buffer
;
5113 const WCHAR cr_lf
[] = {'\r', '\n', 0};
5117 /* bCRLF flag is only honored in 2.0 and up. 1.0 must always return text verbatim */
5118 if (editor
->bEmulateVersion10
) bCRLF
= FALSE
;
5122 pNextRun
= ME_FindItemFwd(pRun
, diRun
);
5124 nLen
= pRun
->member
.run
.len
- start
->nOffset
;
5125 str
= get_text( &pRun
->member
.run
, start
->nOffset
);
5127 while (srcChars
&& buflen
&& pNextRun
)
5129 int nFlags
= pRun
->member
.run
.nFlags
;
5131 if (bCRLF
&& nFlags
& MERF_ENDPARA
&& ~nFlags
& MERF_ENDCELL
)
5133 if (buflen
== 1) break;
5134 /* FIXME: native fails to reduce srcChars here for WM_GETTEXT or
5135 * EM_GETTEXTEX, however, this is done for copying text which
5136 * also uses this function. */
5137 srcChars
-= min(nLen
, srcChars
);
5141 nLen
= min(nLen
, srcChars
);
5145 nLen
= min(nLen
, buflen
);
5148 CopyMemory(buffer
, str
, sizeof(WCHAR
) * nLen
);
5153 pNextRun
= ME_FindItemFwd(pRun
, diRun
);
5155 nLen
= pRun
->member
.run
.len
;
5156 str
= get_text( &pRun
->member
.run
, 0 );
5158 /* append '\r' to the last paragraph. */
5159 if (pRun
->next
->type
== diTextEnd
&& bEOP
)
5165 return buffer
- pStart
;
5168 static BOOL
ME_RegisterEditorClass(HINSTANCE hInstance
)
5173 wcW
.style
= CS_DBLCLKS
| CS_HREDRAW
| CS_VREDRAW
| CS_GLOBALCLASS
;
5174 wcW
.lpfnWndProc
= RichEditWndProcW
;
5176 wcW
.cbWndExtra
= sizeof(ME_TextEditor
*);
5177 wcW
.hInstance
= NULL
; /* hInstance would register DLL-local class */
5179 wcW
.hCursor
= LoadCursorW(NULL
, (LPWSTR
)IDC_IBEAM
);
5180 wcW
.hbrBackground
= GetStockObject(NULL_BRUSH
);
5181 wcW
.lpszMenuName
= NULL
;
5183 if (is_version_nt())
5185 wcW
.lpszClassName
= RICHEDIT_CLASS20W
;
5186 if (!RegisterClassW(&wcW
)) return FALSE
;
5187 wcW
.lpszClassName
= MSFTEDIT_CLASS
;
5188 if (!RegisterClassW(&wcW
)) return FALSE
;
5192 /* WNDCLASSA/W have the same layout */
5193 wcW
.lpszClassName
= (LPCWSTR
)"RichEdit20W";
5194 if (!RegisterClassA((WNDCLASSA
*)&wcW
)) return FALSE
;
5195 wcW
.lpszClassName
= (LPCWSTR
)"RichEdit50W";
5196 if (!RegisterClassA((WNDCLASSA
*)&wcW
)) return FALSE
;
5199 wcA
.style
= CS_DBLCLKS
| CS_HREDRAW
| CS_VREDRAW
| CS_GLOBALCLASS
;
5200 wcA
.lpfnWndProc
= RichEditWndProcA
;
5202 wcA
.cbWndExtra
= sizeof(ME_TextEditor
*);
5203 wcA
.hInstance
= NULL
; /* hInstance would register DLL-local class */
5205 wcA
.hCursor
= LoadCursorW(NULL
, (LPWSTR
)IDC_IBEAM
);
5206 wcA
.hbrBackground
= GetStockObject(NULL_BRUSH
);
5207 wcA
.lpszMenuName
= NULL
;
5208 wcA
.lpszClassName
= RICHEDIT_CLASS20A
;
5209 if (!RegisterClassA(&wcA
)) return FALSE
;
5210 wcA
.lpszClassName
= "RichEdit50A";
5211 if (!RegisterClassA(&wcA
)) return FALSE
;
5216 static LRESULT WINAPI
REComboWndProc(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
) {
5217 /* FIXME: Not implemented */
5218 TRACE("hWnd %p msg %04x (%s) %08lx %08lx\n",
5219 hWnd
, msg
, get_msg_name(msg
), wParam
, lParam
);
5220 return DefWindowProcW(hWnd
, msg
, wParam
, lParam
);
5223 static LRESULT WINAPI
REListWndProc(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
) {
5224 /* FIXME: Not implemented */
5225 TRACE("hWnd %p msg %04x (%s) %08lx %08lx\n",
5226 hWnd
, msg
, get_msg_name(msg
), wParam
, lParam
);
5227 return DefWindowProcW(hWnd
, msg
, wParam
, lParam
);
5230 /******************************************************************
5231 * REExtendedRegisterClass (RICHED20.8)
5233 * FIXME undocumented
5234 * Need to check for errors and implement controls and callbacks
5236 LRESULT WINAPI
REExtendedRegisterClass(void)
5241 FIXME("semi stub\n");
5245 wcW
.hInstance
= NULL
;
5248 wcW
.hbrBackground
= (HBRUSH
)(COLOR_WINDOW
+1);
5249 wcW
.lpszMenuName
= NULL
;
5251 if (!ME_ListBoxRegistered
)
5253 wcW
.style
= CS_PARENTDC
| CS_DBLCLKS
| CS_GLOBALCLASS
;
5254 wcW
.lpfnWndProc
= REListWndProc
;
5255 wcW
.lpszClassName
= REListBox20W
;
5256 if (RegisterClassW(&wcW
)) ME_ListBoxRegistered
= TRUE
;
5259 if (!ME_ComboBoxRegistered
)
5261 wcW
.style
= CS_PARENTDC
| CS_DBLCLKS
| CS_GLOBALCLASS
| CS_VREDRAW
| CS_HREDRAW
;
5262 wcW
.lpfnWndProc
= REComboWndProc
;
5263 wcW
.lpszClassName
= REComboBox20W
;
5264 if (RegisterClassW(&wcW
)) ME_ComboBoxRegistered
= TRUE
;
5268 if (ME_ListBoxRegistered
)
5270 if (ME_ComboBoxRegistered
)
5276 static int wchar_comp( const void *key
, const void *elem
)
5278 return *(const WCHAR
*)key
- *(const WCHAR
*)elem
;
5281 /* neutral characters end the url if the next non-neutral character is a space character,
5282 otherwise they are included in the url. */
5283 static BOOL
isurlneutral( WCHAR c
)
5285 /* NB this list is sorted */
5286 static const WCHAR neutral_chars
[] = {'!','\"','\'','(',')',',','-','.',':',';','<','>','?','[',']','{','}'};
5288 /* Some shortcuts */
5289 if (isalnum( c
)) return FALSE
;
5290 if (c
> neutral_chars
[ARRAY_SIZE( neutral_chars
) - 1]) return FALSE
;
5292 return !!bsearch( &c
, neutral_chars
, ARRAY_SIZE( neutral_chars
), sizeof(c
), wchar_comp
);
5296 * This proc takes a selection, and scans it forward in order to select the span
5297 * of a possible URL candidate. A possible URL candidate must start with isalnum
5298 * or one of the following special characters: *|/\+%#@ and must consist entirely
5299 * of the characters allowed to start the URL, plus : (colon) which may occur
5300 * at most once, and not at either end.
5302 static BOOL
ME_FindNextURLCandidate(ME_TextEditor
*editor
,
5303 const ME_Cursor
*start
,
5305 ME_Cursor
*candidate_min
,
5306 ME_Cursor
*candidate_max
)
5308 ME_Cursor cursor
= *start
, neutral_end
, space_end
;
5309 BOOL candidateStarted
= FALSE
, quoted
= FALSE
;
5314 WCHAR
*str
= get_text( &cursor
.pRun
->member
.run
, 0 );
5315 int run_len
= cursor
.pRun
->member
.run
.len
;
5317 nChars
-= run_len
- cursor
.nOffset
;
5319 /* Find start of candidate */
5320 if (!candidateStarted
)
5322 while (cursor
.nOffset
< run_len
)
5324 c
= str
[cursor
.nOffset
];
5325 if (!isspaceW( c
) && !isurlneutral( c
))
5327 *candidate_min
= cursor
;
5328 candidateStarted
= TRUE
;
5329 neutral_end
.pPara
= NULL
;
5330 space_end
.pPara
= NULL
;
5334 quoted
= (c
== '<');
5339 /* Find end of candidate */
5340 if (candidateStarted
)
5342 while (cursor
.nOffset
< run_len
)
5344 c
= str
[cursor
.nOffset
];
5347 if (quoted
&& c
!= '\r')
5349 if (!space_end
.pPara
)
5351 if (neutral_end
.pPara
)
5352 space_end
= neutral_end
;
5360 else if (isurlneutral( c
))
5362 if (quoted
&& c
== '>')
5364 neutral_end
.pPara
= NULL
;
5365 space_end
.pPara
= NULL
;
5368 if (!neutral_end
.pPara
)
5369 neutral_end
= cursor
;
5372 neutral_end
.pPara
= NULL
;
5379 if (!ME_NextRun(&cursor
.pPara
, &cursor
.pRun
, TRUE
))
5384 if (candidateStarted
)
5386 if (space_end
.pPara
)
5387 *candidate_max
= space_end
;
5388 else if (neutral_end
.pPara
)
5389 *candidate_max
= neutral_end
;
5391 *candidate_max
= cursor
;
5394 *candidate_max
= *candidate_min
= cursor
;
5399 * This proc evaluates the selection and returns TRUE if it can be considered an URL
5401 static BOOL
ME_IsCandidateAnURL(ME_TextEditor
*editor
, const ME_Cursor
*start
, int nChars
)
5403 #define MAX_PREFIX_LEN 9
5405 const WCHAR text
[MAX_PREFIX_LEN
];
5408 {{'p','r','o','s','p','e','r','o',':'}, 9},
5409 {{'t','e','l','n','e','t',':'}, 7},
5410 {{'g','o','p','h','e','r',':'}, 7},
5411 {{'m','a','i','l','t','o',':'}, 7},
5412 {{'h','t','t','p','s',':'}, 6},
5413 {{'f','i','l','e',':'}, 5},
5414 {{'n','e','w','s',':'}, 5},
5415 {{'w','a','i','s',':'}, 5},
5416 {{'n','n','t','p',':'}, 5},
5417 {{'h','t','t','p',':'}, 5},
5418 {{'w','w','w','.'}, 4},
5419 {{'f','t','p',':'}, 4},
5421 WCHAR bufferW
[MAX_PREFIX_LEN
+ 1];
5424 ME_GetTextW(editor
, bufferW
, MAX_PREFIX_LEN
, start
, nChars
, FALSE
, FALSE
);
5425 for (i
= 0; i
< ARRAY_SIZE(prefixes
); i
++)
5427 if (nChars
< prefixes
[i
].length
) continue;
5428 if (!memcmp(prefixes
[i
].text
, bufferW
, prefixes
[i
].length
* sizeof(WCHAR
)))
5432 #undef MAX_PREFIX_LEN
5436 * This proc walks through the indicated selection and evaluates whether each
5437 * section identified by ME_FindNextURLCandidate and in-between sections have
5438 * their proper CFE_LINK attributes set or unset. If the CFE_LINK attribute is
5439 * not what it is supposed to be, this proc sets or unsets it as appropriate.
5441 * Since this function can cause runs to be split, do not depend on the value
5442 * of the start cursor at the end of the function.
5444 * nChars may be set to INT_MAX to update to the end of the text.
5446 * Returns TRUE if at least one section was modified.
5448 static BOOL
ME_UpdateLinkAttribute(ME_TextEditor
*editor
, ME_Cursor
*start
, int nChars
)
5450 BOOL modified
= FALSE
;
5451 ME_Cursor startCur
= *start
;
5453 if (!editor
->AutoURLDetect_bEnable
) return FALSE
;
5458 ME_Cursor candidateStart
, candidateEnd
;
5460 if (ME_FindNextURLCandidate(editor
, &startCur
, nChars
,
5461 &candidateStart
, &candidateEnd
))
5463 /* Section before candidate is not an URL */
5464 int cMin
= ME_GetCursorOfs(&candidateStart
);
5465 int cMax
= ME_GetCursorOfs(&candidateEnd
);
5467 if (!ME_IsCandidateAnURL(editor
, &candidateStart
, cMax
- cMin
))
5468 candidateStart
= candidateEnd
;
5469 nChars
-= cMax
- ME_GetCursorOfs(&startCur
);
5473 /* No more candidates until end of selection */
5477 if (startCur
.pRun
!= candidateStart
.pRun
||
5478 startCur
.nOffset
!= candidateStart
.nOffset
)
5480 /* CFE_LINK effect should be consistently unset */
5481 link
.cbSize
= sizeof(link
);
5482 ME_GetCharFormat(editor
, &startCur
, &candidateStart
, &link
);
5483 if (!(link
.dwMask
& CFM_LINK
) || (link
.dwEffects
& CFE_LINK
))
5485 /* CFE_LINK must be unset from this range */
5486 memset(&link
, 0, sizeof(CHARFORMAT2W
));
5487 link
.cbSize
= sizeof(link
);
5488 link
.dwMask
= CFM_LINK
;
5490 ME_SetCharFormat(editor
, &startCur
, &candidateStart
, &link
);
5491 /* Update candidateEnd since setting character formats may split
5492 * runs, which can cause a cursor to be at an invalid offset within
5494 while (candidateEnd
.nOffset
>= candidateEnd
.pRun
->member
.run
.len
)
5496 candidateEnd
.nOffset
-= candidateEnd
.pRun
->member
.run
.len
;
5497 candidateEnd
.pRun
= ME_FindItemFwd(candidateEnd
.pRun
, diRun
);
5502 if (candidateStart
.pRun
!= candidateEnd
.pRun
||
5503 candidateStart
.nOffset
!= candidateEnd
.nOffset
)
5505 /* CFE_LINK effect should be consistently set */
5506 link
.cbSize
= sizeof(link
);
5507 ME_GetCharFormat(editor
, &candidateStart
, &candidateEnd
, &link
);
5508 if (!(link
.dwMask
& CFM_LINK
) || !(link
.dwEffects
& CFE_LINK
))
5510 /* CFE_LINK must be set on this range */
5511 memset(&link
, 0, sizeof(CHARFORMAT2W
));
5512 link
.cbSize
= sizeof(link
);
5513 link
.dwMask
= CFM_LINK
;
5514 link
.dwEffects
= CFE_LINK
;
5515 ME_SetCharFormat(editor
, &candidateStart
, &candidateEnd
, &link
);
5519 startCur
= candidateEnd
;
5520 } while (nChars
> 0);