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
;
283 ME_Paragraph
*editor_first_para( ME_TextEditor
*editor
)
285 return para_next( &editor
->pBuffer
->pFirst
->member
.para
);
288 /* Note, returns the diTextEnd sentinel paragraph */
289 ME_Paragraph
*editor_end_para( ME_TextEditor
*editor
)
291 return &editor
->pBuffer
->pLast
->member
.para
;
294 static LRESULT
ME_StreamInText(ME_TextEditor
*editor
, DWORD dwFormat
, ME_InStream
*stream
, ME_Style
*style
)
297 LRESULT total_bytes_read
= 0;
298 BOOL is_read
= FALSE
;
299 DWORD cp
= CP_ACP
, copy
= 0;
300 char conv_buf
[4 + STREAMIN_BUFFER_SIZE
]; /* up to 4 additional UTF-8 bytes */
302 static const char bom_utf8
[] = {0xEF, 0xBB, 0xBF};
304 TRACE("%08x %p\n", dwFormat
, stream
);
308 WCHAR wszText
[STREAMIN_BUFFER_SIZE
+1];
312 ME_StreamInFill(stream
);
313 if (stream
->editstream
->dwError
)
317 total_bytes_read
+= stream
->dwSize
;
320 if (!(dwFormat
& SF_UNICODE
))
322 char * buf
= stream
->buffer
;
323 DWORD size
= stream
->dwSize
, end
;
328 if (stream
->dwSize
>= 3 && !memcmp(stream
->buffer
, bom_utf8
, 3))
340 memcpy(conv_buf
+ copy
, buf
, size
);
345 while ((buf
[end
-1] & 0xC0) == 0x80)
348 --total_bytes_read
; /* strange, but seems to match windows */
350 if (buf
[end
-1] & 0x80)
353 if ((buf
[end
-1] & 0xE0) == 0xC0)
355 if ((buf
[end
-1] & 0xF0) == 0xE0)
357 if ((buf
[end
-1] & 0xF8) == 0xF0)
360 if (size
- end
>= need
)
362 /* we have enough bytes for this sequence */
367 /* need more bytes, so don't transcode this sequence */
375 nWideChars
= MultiByteToWideChar(cp
, 0, buf
, end
, wszText
, STREAMIN_BUFFER_SIZE
);
382 memcpy(conv_buf
, buf
+ end
, size
- end
);
389 nWideChars
= stream
->dwSize
>> 1;
390 pText
= (WCHAR
*)stream
->buffer
;
393 ME_InsertTextFromCursor(editor
, 0, pText
, nWideChars
, style
);
394 if (stream
->dwSize
== 0)
398 return total_bytes_read
;
401 static void ME_ApplyBorderProperties(RTF_Info
*info
,
402 ME_BorderRect
*borderRect
,
403 RTFBorder
*borderDef
)
406 ME_Border
*pBorders
[] = {&borderRect
->top
,
410 for (i
= 0; i
< 4; i
++)
412 RTFColor
*colorDef
= info
->colorList
;
413 pBorders
[i
]->width
= borderDef
[i
].width
;
414 colorNum
= borderDef
[i
].color
;
415 while (colorDef
&& colorDef
->rtfCNum
!= colorNum
)
416 colorDef
= colorDef
->rtfNextColor
;
418 pBorders
[i
]->colorRef
= RGB(
419 colorDef
->rtfCRed
>= 0 ? colorDef
->rtfCRed
: 0,
420 colorDef
->rtfCGreen
>= 0 ? colorDef
->rtfCGreen
: 0,
421 colorDef
->rtfCBlue
>= 0 ? colorDef
->rtfCBlue
: 0);
423 pBorders
[i
]->colorRef
= RGB(0, 0, 0);
427 void ME_RTFCharAttrHook(RTF_Info
*info
)
430 fmt
.cbSize
= sizeof(fmt
);
434 switch(info
->rtfMinor
)
437 /* FIXME add more flags once they're implemented */
438 fmt
.dwMask
= CFM_BOLD
| CFM_ITALIC
| CFM_UNDERLINE
| CFM_UNDERLINETYPE
| CFM_STRIKEOUT
|
439 CFM_COLOR
| CFM_BACKCOLOR
| CFM_SIZE
| CFM_WEIGHT
;
440 fmt
.dwEffects
= CFE_AUTOCOLOR
| CFE_AUTOBACKCOLOR
;
441 fmt
.yHeight
= 12*20; /* 12pt */
442 fmt
.wWeight
= FW_NORMAL
;
443 fmt
.bUnderlineType
= CFU_UNDERLINE
;
446 fmt
.dwMask
= CFM_BOLD
| CFM_WEIGHT
;
447 fmt
.dwEffects
= info
->rtfParam
? CFE_BOLD
: 0;
448 fmt
.wWeight
= info
->rtfParam
? FW_BOLD
: FW_NORMAL
;
451 fmt
.dwMask
= CFM_ITALIC
;
452 fmt
.dwEffects
= info
->rtfParam
? fmt
.dwMask
: 0;
455 fmt
.dwMask
= CFM_UNDERLINETYPE
| CFM_UNDERLINE
;
456 fmt
.bUnderlineType
= CFU_UNDERLINE
;
457 fmt
.dwEffects
= info
->rtfParam
? CFE_UNDERLINE
: 0;
459 case rtfDotUnderline
:
460 fmt
.dwMask
= CFM_UNDERLINETYPE
| CFM_UNDERLINE
;
461 fmt
.bUnderlineType
= CFU_UNDERLINEDOTTED
;
462 fmt
.dwEffects
= info
->rtfParam
? CFE_UNDERLINE
: 0;
465 fmt
.dwMask
= CFM_UNDERLINETYPE
| CFM_UNDERLINE
;
466 fmt
.bUnderlineType
= CFU_UNDERLINEDOUBLE
;
467 fmt
.dwEffects
= info
->rtfParam
? CFE_UNDERLINE
: 0;
469 case rtfWordUnderline
:
470 fmt
.dwMask
= CFM_UNDERLINETYPE
| CFM_UNDERLINE
;
471 fmt
.bUnderlineType
= CFU_UNDERLINEWORD
;
472 fmt
.dwEffects
= info
->rtfParam
? CFE_UNDERLINE
: 0;
475 fmt
.dwMask
= CFM_UNDERLINE
;
479 fmt
.dwMask
= CFM_STRIKEOUT
;
480 fmt
.dwEffects
= info
->rtfParam
? fmt
.dwMask
: 0;
484 case rtfSubScrShrink
:
485 case rtfSuperScrShrink
:
487 fmt
.dwMask
= CFM_SUBSCRIPT
|CFM_SUPERSCRIPT
;
488 if (info
->rtfMinor
== rtfSubScrShrink
) fmt
.dwEffects
= CFE_SUBSCRIPT
;
489 if (info
->rtfMinor
== rtfSuperScrShrink
) fmt
.dwEffects
= CFE_SUPERSCRIPT
;
490 if (info
->rtfMinor
== rtfNoSuperSub
) fmt
.dwEffects
= 0;
493 fmt
.dwMask
= CFM_HIDDEN
;
494 fmt
.dwEffects
= info
->rtfParam
? fmt
.dwMask
: 0;
497 fmt
.dwMask
= CFM_BACKCOLOR
;
499 if (info
->rtfParam
== 0)
500 fmt
.dwEffects
= CFE_AUTOBACKCOLOR
;
501 else if (info
->rtfParam
!= rtfNoParam
)
503 RTFColor
*c
= RTFGetColor(info
, info
->rtfParam
);
504 if (c
&& c
->rtfCBlue
>= 0)
505 fmt
.crBackColor
= (c
->rtfCBlue
<<16)|(c
->rtfCGreen
<<8)|(c
->rtfCRed
);
507 fmt
.dwEffects
= CFE_AUTOBACKCOLOR
;
511 fmt
.dwMask
= CFM_COLOR
;
513 if (info
->rtfParam
== 0)
514 fmt
.dwEffects
= CFE_AUTOCOLOR
;
515 else if (info
->rtfParam
!= rtfNoParam
)
517 RTFColor
*c
= RTFGetColor(info
, info
->rtfParam
);
518 if (c
&& c
->rtfCBlue
>= 0)
519 fmt
.crTextColor
= (c
->rtfCBlue
<<16)|(c
->rtfCGreen
<<8)|(c
->rtfCRed
);
521 fmt
.dwEffects
= CFE_AUTOCOLOR
;
526 if (info
->rtfParam
!= rtfNoParam
)
528 RTFFont
*f
= RTFGetFont(info
, info
->rtfParam
);
531 MultiByteToWideChar(CP_ACP
, 0, f
->rtfFName
, -1, fmt
.szFaceName
, ARRAY_SIZE(fmt
.szFaceName
));
532 fmt
.szFaceName
[ARRAY_SIZE(fmt
.szFaceName
)-1] = '\0';
533 fmt
.bCharSet
= f
->rtfFCharSet
;
534 fmt
.dwMask
= CFM_FACE
| CFM_CHARSET
;
535 fmt
.bPitchAndFamily
= f
->rtfFPitch
| (f
->rtfFFamily
<< 4);
540 fmt
.dwMask
= CFM_SIZE
;
541 if (info
->rtfParam
!= rtfNoParam
)
542 fmt
.yHeight
= info
->rtfParam
*10;
547 RTFFlushOutputBuffer(info
);
548 /* FIXME too slow ? how come ? */
549 style2
= ME_ApplyStyle(info
->editor
, info
->style
, &fmt
);
550 ME_ReleaseStyle(info
->style
);
551 info
->style
= style2
;
552 info
->styleChanged
= TRUE
;
556 /* FIXME this function doesn't get any information about context of the RTF tag, which is very bad,
557 the same tags mean different things in different contexts */
558 void ME_RTFParAttrHook(RTF_Info
*info
)
560 switch(info
->rtfMinor
)
562 case rtfParDef
: /* restores default paragraph attributes */
563 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
564 info
->borderType
= RTFBorderParaLeft
;
565 else /* v1.0 - 3.0 */
566 info
->borderType
= RTFBorderParaTop
;
567 info
->fmt
.dwMask
= PFM_ALIGNMENT
| PFM_BORDER
| PFM_LINESPACING
| PFM_TABSTOPS
|
568 PFM_OFFSET
| PFM_RIGHTINDENT
| PFM_SPACEAFTER
| PFM_SPACEBEFORE
|
569 PFM_STARTINDENT
| PFM_RTLPARA
| PFM_NUMBERING
| PFM_NUMBERINGSTART
|
570 PFM_NUMBERINGSTYLE
| PFM_NUMBERINGTAB
;
572 info
->fmt
.wAlignment
= PFA_LEFT
;
573 info
->fmt
.cTabCount
= 0;
574 info
->fmt
.dxOffset
= info
->fmt
.dxStartIndent
= info
->fmt
.dxRightIndent
= 0;
575 info
->fmt
.wBorderWidth
= info
->fmt
.wBorders
= 0;
576 info
->fmt
.wBorderSpace
= 0;
577 info
->fmt
.bLineSpacingRule
= 0;
578 info
->fmt
.dySpaceBefore
= info
->fmt
.dySpaceAfter
= 0;
579 info
->fmt
.dyLineSpacing
= 0;
580 info
->fmt
.wEffects
&= ~PFE_RTLPARA
;
581 info
->fmt
.wNumbering
= 0;
582 info
->fmt
.wNumberingStart
= 0;
583 info
->fmt
.wNumberingStyle
= 0;
584 info
->fmt
.wNumberingTab
= 0;
586 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
588 if (info
->tableDef
&& info
->tableDef
->row_start
&&
589 info
->tableDef
->row_start
->nFlags
& MEPF_ROWEND
)
593 /* We are just after a table row. */
594 RTFFlushOutputBuffer(info
);
595 cursor
= info
->editor
->pCursors
[0];
596 para
= &cursor
.pPara
->member
.para
;
597 if (para
== para_next( info
->tableDef
->row_start
)
598 && !cursor
.nOffset
&& !cursor
.pRun
->member
.run
.nCharOfs
)
600 /* Since the table row end, no text has been inserted, and the \intbl
601 * control word has not be used. We can confirm that we are not in a
604 info
->tableDef
->row_start
= NULL
;
605 info
->canInheritInTbl
= FALSE
;
609 else /* v1.0 - v3.0 */
611 info
->fmt
.dwMask
|= PFM_TABLE
;
612 info
->fmt
.wEffects
&= ~PFE_TABLE
;
616 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
618 while (info
->rtfParam
> info
->nestingLevel
)
620 RTFTable
*tableDef
= heap_alloc_zero(sizeof(*tableDef
));
621 tableDef
->parent
= info
->tableDef
;
622 info
->tableDef
= tableDef
;
624 RTFFlushOutputBuffer(info
);
625 if (tableDef
->row_start
&& tableDef
->row_start
->nFlags
& MEPF_ROWEND
)
627 ME_Paragraph
*para
= para_next( tableDef
->row_start
);
628 tableDef
->row_start
= table_insert_row_start_at_para( info
->editor
, para
);
634 cursor
= info
->editor
->pCursors
[0];
635 if (cursor
.nOffset
|| cursor
.pRun
->member
.run
.nCharOfs
)
636 ME_InsertTextFromCursor(info
->editor
, 0, &endl
, 1, info
->style
);
637 tableDef
->row_start
= table_insert_row_start( info
->editor
, info
->editor
->pCursors
);
640 info
->nestingLevel
++;
642 info
->canInheritInTbl
= FALSE
;
647 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
649 if (info
->nestingLevel
< 1)
655 info
->tableDef
= heap_alloc_zero(sizeof(*info
->tableDef
));
656 tableDef
= info
->tableDef
;
657 RTFFlushOutputBuffer(info
);
658 if (tableDef
->row_start
&& tableDef
->row_start
->nFlags
& MEPF_ROWEND
)
659 para
= para_next( tableDef
->row_start
);
661 para
= &info
->editor
->pCursors
[0].pPara
->member
.para
;
663 tableDef
->row_start
= table_insert_row_start_at_para( info
->editor
, para
);
665 info
->nestingLevel
= 1;
666 info
->canInheritInTbl
= TRUE
;
669 } else { /* v1.0 - v3.0 */
670 info
->fmt
.dwMask
|= PFM_TABLE
;
671 info
->fmt
.wEffects
|= PFE_TABLE
;
677 if ((info
->fmt
.dwMask
& (PFM_STARTINDENT
| PFM_OFFSET
)) != (PFM_STARTINDENT
| PFM_OFFSET
))
680 fmt
.cbSize
= sizeof(fmt
);
681 ME_GetSelectionParaFormat(info
->editor
, &fmt
);
682 info
->fmt
.dwMask
|= PFM_STARTINDENT
| PFM_OFFSET
;
683 info
->fmt
.dxStartIndent
= fmt
.dxStartIndent
;
684 info
->fmt
.dxOffset
= fmt
.dxOffset
;
686 if (info
->rtfMinor
== rtfFirstIndent
)
688 info
->fmt
.dxStartIndent
+= info
->fmt
.dxOffset
+ info
->rtfParam
;
689 info
->fmt
.dxOffset
= -info
->rtfParam
;
692 info
->fmt
.dxStartIndent
= info
->rtfParam
- info
->fmt
.dxOffset
;
695 info
->fmt
.dwMask
|= PFM_RIGHTINDENT
;
696 info
->fmt
.dxRightIndent
= info
->rtfParam
;
700 info
->fmt
.dwMask
|= PFM_ALIGNMENT
;
701 info
->fmt
.wAlignment
= PFA_LEFT
;
704 info
->fmt
.dwMask
|= PFM_ALIGNMENT
;
705 info
->fmt
.wAlignment
= PFA_RIGHT
;
708 info
->fmt
.dwMask
|= PFM_ALIGNMENT
;
709 info
->fmt
.wAlignment
= PFA_CENTER
;
712 if (!(info
->fmt
.dwMask
& PFM_TABSTOPS
))
715 fmt
.cbSize
= sizeof(fmt
);
716 ME_GetSelectionParaFormat(info
->editor
, &fmt
);
717 memcpy(info
->fmt
.rgxTabs
, fmt
.rgxTabs
,
718 fmt
.cTabCount
* sizeof(fmt
.rgxTabs
[0]));
719 info
->fmt
.cTabCount
= fmt
.cTabCount
;
720 info
->fmt
.dwMask
|= PFM_TABSTOPS
;
722 if (info
->fmt
.cTabCount
< MAX_TAB_STOPS
&& info
->rtfParam
< 0x1000000)
723 info
->fmt
.rgxTabs
[info
->fmt
.cTabCount
++] = info
->rtfParam
;
726 info
->fmt
.dwMask
|= PFM_KEEP
;
727 info
->fmt
.wEffects
|= PFE_KEEP
;
729 case rtfNoWidowControl
:
730 info
->fmt
.dwMask
|= PFM_NOWIDOWCONTROL
;
731 info
->fmt
.wEffects
|= PFE_NOWIDOWCONTROL
;
734 info
->fmt
.dwMask
|= PFM_KEEPNEXT
;
735 info
->fmt
.wEffects
|= PFE_KEEPNEXT
;
738 info
->fmt
.dwMask
|= PFM_SPACEAFTER
;
739 info
->fmt
.dySpaceAfter
= info
->rtfParam
;
742 info
->fmt
.dwMask
|= PFM_SPACEBEFORE
;
743 info
->fmt
.dySpaceBefore
= info
->rtfParam
;
745 case rtfSpaceBetween
:
746 info
->fmt
.dwMask
|= PFM_LINESPACING
;
747 if ((int)info
->rtfParam
> 0)
749 info
->fmt
.dyLineSpacing
= info
->rtfParam
;
750 info
->fmt
.bLineSpacingRule
= 3;
754 info
->fmt
.dyLineSpacing
= info
->rtfParam
;
755 info
->fmt
.bLineSpacingRule
= 4;
758 case rtfSpaceMultiply
:
759 info
->fmt
.dwMask
|= PFM_LINESPACING
;
760 info
->fmt
.dyLineSpacing
= info
->rtfParam
* 20;
761 info
->fmt
.bLineSpacingRule
= 5;
764 info
->fmt
.dwMask
|= PFM_NUMBERING
;
765 info
->fmt
.wNumbering
= PFN_BULLET
;
768 info
->fmt
.dwMask
|= PFM_NUMBERING
;
769 info
->fmt
.wNumbering
= 2; /* FIXME: MSDN says it's not used ?? */
772 info
->borderType
= RTFBorderParaLeft
;
773 info
->fmt
.wBorders
|= 1;
774 info
->fmt
.dwMask
|= PFM_BORDER
;
777 info
->borderType
= RTFBorderParaRight
;
778 info
->fmt
.wBorders
|= 2;
779 info
->fmt
.dwMask
|= PFM_BORDER
;
782 info
->borderType
= RTFBorderParaTop
;
783 info
->fmt
.wBorders
|= 4;
784 info
->fmt
.dwMask
|= PFM_BORDER
;
786 case rtfBorderBottom
:
787 info
->borderType
= RTFBorderParaBottom
;
788 info
->fmt
.wBorders
|= 8;
789 info
->fmt
.dwMask
|= PFM_BORDER
;
791 case rtfBorderSingle
:
792 info
->fmt
.wBorders
&= ~0x700;
793 info
->fmt
.wBorders
|= 1 << 8;
794 info
->fmt
.dwMask
|= PFM_BORDER
;
797 info
->fmt
.wBorders
&= ~0x700;
798 info
->fmt
.wBorders
|= 2 << 8;
799 info
->fmt
.dwMask
|= PFM_BORDER
;
801 case rtfBorderShadow
:
802 info
->fmt
.wBorders
&= ~0x700;
803 info
->fmt
.wBorders
|= 10 << 8;
804 info
->fmt
.dwMask
|= PFM_BORDER
;
806 case rtfBorderDouble
:
807 info
->fmt
.wBorders
&= ~0x700;
808 info
->fmt
.wBorders
|= 7 << 8;
809 info
->fmt
.dwMask
|= PFM_BORDER
;
812 info
->fmt
.wBorders
&= ~0x700;
813 info
->fmt
.wBorders
|= 11 << 8;
814 info
->fmt
.dwMask
|= PFM_BORDER
;
818 int borderSide
= info
->borderType
& RTFBorderSideMask
;
819 RTFTable
*tableDef
= info
->tableDef
;
820 if ((info
->borderType
& RTFBorderTypeMask
) == RTFBorderTypeCell
)
823 if (!tableDef
|| tableDef
->numCellsDefined
>= MAX_TABLE_CELLS
)
825 border
= &tableDef
->cells
[tableDef
->numCellsDefined
].border
[borderSide
];
826 border
->width
= info
->rtfParam
;
829 info
->fmt
.wBorderWidth
= info
->rtfParam
;
830 info
->fmt
.dwMask
|= PFM_BORDER
;
834 info
->fmt
.wBorderSpace
= info
->rtfParam
;
835 info
->fmt
.dwMask
|= PFM_BORDER
;
839 RTFTable
*tableDef
= info
->tableDef
;
840 int borderSide
= info
->borderType
& RTFBorderSideMask
;
841 int borderType
= info
->borderType
& RTFBorderTypeMask
;
844 case RTFBorderTypePara
:
845 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
847 /* v1.0 - 3.0 treat paragraph and row borders the same. */
848 case RTFBorderTypeRow
:
850 tableDef
->border
[borderSide
].color
= info
->rtfParam
;
853 case RTFBorderTypeCell
:
854 if (tableDef
&& tableDef
->numCellsDefined
< MAX_TABLE_CELLS
) {
855 tableDef
->cells
[tableDef
->numCellsDefined
].border
[borderSide
].color
= info
->rtfParam
;
862 info
->fmt
.dwMask
|= PFM_RTLPARA
;
863 info
->fmt
.wEffects
|= PFE_RTLPARA
;
866 info
->fmt
.dwMask
|= PFM_RTLPARA
;
867 info
->fmt
.wEffects
&= ~PFE_RTLPARA
;
872 void ME_RTFTblAttrHook(RTF_Info
*info
)
874 switch (info
->rtfMinor
)
878 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
879 info
->borderType
= 0; /* Not sure */
880 else /* v1.0 - 3.0 */
881 info
->borderType
= RTFBorderRowTop
;
882 if (!info
->tableDef
) {
883 info
->tableDef
= ME_MakeTableDef(info
->editor
);
885 ME_InitTableDef(info
->editor
, info
->tableDef
);
894 info
->tableDef
= ME_MakeTableDef(info
->editor
);
896 cellNum
= info
->tableDef
->numCellsDefined
;
897 if (cellNum
>= MAX_TABLE_CELLS
)
899 info
->tableDef
->cells
[cellNum
].rightBoundary
= info
->rtfParam
;
900 if (cellNum
< MAX_TAB_STOPS
) {
901 /* Tab stops were used to store cell positions before v4.1 but v4.1
902 * still seems to set the tabstops without using them. */
903 ME_DisplayItem
*para
= info
->editor
->pCursors
[0].pPara
;
904 PARAFORMAT2
*pFmt
= ¶
->member
.para
.fmt
;
905 pFmt
->rgxTabs
[cellNum
] &= ~0x00FFFFFF;
906 pFmt
->rgxTabs
[cellNum
] |= 0x00FFFFFF & info
->rtfParam
;
908 info
->tableDef
->numCellsDefined
++;
912 info
->borderType
= RTFBorderRowTop
;
915 info
->borderType
= RTFBorderRowLeft
;
917 case rtfRowBordBottom
:
918 info
->borderType
= RTFBorderRowBottom
;
920 case rtfRowBordRight
:
921 info
->borderType
= RTFBorderRowRight
;
924 info
->borderType
= RTFBorderCellTop
;
926 case rtfCellBordLeft
:
927 info
->borderType
= RTFBorderCellLeft
;
929 case rtfCellBordBottom
:
930 info
->borderType
= RTFBorderCellBottom
;
932 case rtfCellBordRight
:
933 info
->borderType
= RTFBorderCellRight
;
937 info
->tableDef
->gapH
= info
->rtfParam
;
941 info
->tableDef
->leftEdge
= info
->rtfParam
;
946 void ME_RTFSpecialCharHook(RTF_Info
*info
)
948 RTFTable
*tableDef
= info
->tableDef
;
949 switch (info
->rtfMinor
)
952 if (info
->editor
->bEmulateVersion10
) /* v1.0 - v3.0 */
954 /* else fall through since v4.1 treats rtfNestCell and rtfCell the same */
958 RTFFlushOutputBuffer(info
);
959 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
961 if (tableDef
->row_start
)
963 if (!info
->nestingLevel
&& tableDef
->row_start
->nFlags
& MEPF_ROWEND
)
965 ME_Paragraph
*para
= para_next( tableDef
->row_start
);
966 tableDef
->row_start
= table_insert_row_start_at_para( info
->editor
, para
);
967 info
->nestingLevel
= 1;
969 table_insert_cell( info
->editor
, info
->editor
->pCursors
);
972 else /* v1.0 - v3.0 */
974 ME_Paragraph
*para
= &info
->editor
->pCursors
[0].pPara
->member
.para
;
976 if (para_in_table( para
) && tableDef
->numCellsInserted
< tableDef
->numCellsDefined
)
979 ME_InsertTextFromCursor(info
->editor
, 0, &tab
, 1, info
->style
);
980 tableDef
->numCellsInserted
++;
985 if (info
->editor
->bEmulateVersion10
) /* v1.0 - v3.0 */
987 /* else fall through since v4.1 treats rtfNestRow and rtfRow the same */
997 RTFFlushOutputBuffer(info
);
998 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
1000 if (!tableDef
->row_start
) break;
1001 if (!info
->nestingLevel
&& tableDef
->row_start
->nFlags
& MEPF_ROWEND
)
1003 para
= para_next( tableDef
->row_start
);
1004 tableDef
->row_start
= table_insert_row_start_at_para( info
->editor
, para
);
1005 info
->nestingLevel
++;
1007 para
= tableDef
->row_start
;
1008 cell
= table_row_first_cell( para
);
1009 assert( cell
&& !cell_prev( cell
) );
1010 if (tableDef
->numCellsDefined
< 1)
1012 /* 2000 twips appears to be the cell size that native richedit uses
1013 * when no cell sizes are specified. */
1014 const int default_size
= 2000;
1015 int right_boundary
= default_size
;
1016 cell
->nRightBoundary
= right_boundary
;
1017 while (cell_next( cell
))
1019 cell
= cell_next( cell
);
1020 right_boundary
+= default_size
;
1021 cell
->nRightBoundary
= right_boundary
;
1023 para
= table_insert_cell( info
->editor
, info
->editor
->pCursors
);
1024 cell
= para_cell( para
);
1025 cell
->nRightBoundary
= right_boundary
;
1029 for (i
= 0; i
< tableDef
->numCellsDefined
; i
++)
1031 RTFCell
*cellDef
= &tableDef
->cells
[i
];
1032 cell
->nRightBoundary
= cellDef
->rightBoundary
;
1033 ME_ApplyBorderProperties( info
, &cell
->border
, cellDef
->border
);
1034 cell
= cell_next( cell
);
1037 para
= table_insert_cell( info
->editor
, info
->editor
->pCursors
);
1038 cell
= para_cell( para
);
1041 /* Cell for table row delimiter is empty */
1042 cell
->nRightBoundary
= tableDef
->cells
[i
- 1].rightBoundary
;
1045 run
= ME_FindItemFwd( cell_get_di( cell
) , diRun
);
1046 if (info
->editor
->pCursors
[0].pRun
!= run
||
1047 info
->editor
->pCursors
[0].nOffset
)
1050 /* Delete inserted cells that aren't defined. */
1051 info
->editor
->pCursors
[1].pRun
= run
;
1052 info
->editor
->pCursors
[1].pPara
= ME_GetParagraph(run
);
1053 info
->editor
->pCursors
[1].nOffset
= 0;
1054 nOfs
= ME_GetCursorOfs(&info
->editor
->pCursors
[1]);
1055 nChars
= ME_GetCursorOfs(&info
->editor
->pCursors
[0]) - nOfs
;
1056 ME_InternalDeleteText(info
->editor
, &info
->editor
->pCursors
[1],
1060 para
= table_insert_row_end( info
->editor
, info
->editor
->pCursors
);
1061 para
->fmt
.dxOffset
= abs(info
->tableDef
->gapH
);
1062 para
->fmt
.dxStartIndent
= info
->tableDef
->leftEdge
;
1063 ME_ApplyBorderProperties( info
, ¶
->border
, tableDef
->border
);
1064 info
->nestingLevel
--;
1065 if (!info
->nestingLevel
)
1067 if (info
->canInheritInTbl
) tableDef
->row_start
= para
;
1070 while (info
->tableDef
)
1072 tableDef
= info
->tableDef
;
1073 info
->tableDef
= tableDef
->parent
;
1074 heap_free(tableDef
);
1080 info
->tableDef
= tableDef
->parent
;
1081 heap_free(tableDef
);
1084 else /* v1.0 - v3.0 */
1088 para
= &info
->editor
->pCursors
[0].pPara
->member
.para
;
1089 para
->fmt
.dxOffset
= info
->tableDef
->gapH
;
1090 para
->fmt
.dxStartIndent
= info
->tableDef
->leftEdge
;
1092 ME_ApplyBorderProperties( info
, ¶
->border
, tableDef
->border
);
1093 while (tableDef
->numCellsInserted
< tableDef
->numCellsDefined
)
1096 ME_InsertTextFromCursor(info
->editor
, 0, &tab
, 1, info
->style
);
1097 tableDef
->numCellsInserted
++;
1099 para
->fmt
.cTabCount
= min(tableDef
->numCellsDefined
, MAX_TAB_STOPS
);
1100 if (!tableDef
->numCellsDefined
) para
->fmt
.wEffects
&= ~PFE_TABLE
;
1101 ME_InsertTextFromCursor(info
->editor
, 0, &endl
, 1, info
->style
);
1102 tableDef
->numCellsInserted
= 0;
1108 if (info
->editor
->bEmulateVersion10
) /* v1.0 - 3.0 */
1112 RTFFlushOutputBuffer(info
);
1113 para
= &info
->editor
->pCursors
[0].pPara
->member
.para
;
1114 if (para_in_table( para
))
1116 /* rtfPar is treated like a space within a table. */
1117 info
->rtfClass
= rtfText
;
1118 info
->rtfMajor
= ' ';
1120 else if (info
->rtfMinor
== rtfPar
&& tableDef
)
1121 tableDef
->numCellsInserted
= 0;
1127 static HRESULT
insert_static_object(ME_TextEditor
*editor
, HENHMETAFILE hemf
, HBITMAP hbmp
,
1130 LPOLEOBJECT lpObject
= NULL
;
1131 LPSTORAGE lpStorage
= NULL
;
1132 LPOLECLIENTSITE lpClientSite
= NULL
;
1133 LPDATAOBJECT lpDataObject
= NULL
;
1134 LPOLECACHE lpOleCache
= NULL
;
1135 LPRICHEDITOLE lpReOle
= NULL
;
1139 HRESULT hr
= E_FAIL
;
1144 stgm
.tymed
= TYMED_ENHMF
;
1145 stgm
.u
.hEnhMetaFile
= hemf
;
1146 fm
.cfFormat
= CF_ENHMETAFILE
;
1150 stgm
.tymed
= TYMED_GDI
;
1151 stgm
.u
.hBitmap
= hbmp
;
1152 fm
.cfFormat
= CF_BITMAP
;
1156 stgm
.pUnkForRelease
= NULL
;
1159 fm
.dwAspect
= DVASPECT_CONTENT
;
1161 fm
.tymed
= stgm
.tymed
;
1165 if (!CreateIRichEditOle(NULL
, editor
, (LPVOID
*)&editor
->reOle
))
1169 if (OleCreateDefaultHandler(&CLSID_NULL
, NULL
, &IID_IOleObject
, (void**)&lpObject
) == S_OK
&&
1170 IUnknown_QueryInterface(editor
->reOle
, &IID_IRichEditOle
, (void**)&lpReOle
) == S_OK
&&
1171 IRichEditOle_GetClientSite(lpReOle
, &lpClientSite
) == S_OK
&&
1172 IOleObject_SetClientSite(lpObject
, lpClientSite
) == S_OK
&&
1173 IOleObject_GetUserClassID(lpObject
, &clsid
) == S_OK
&&
1174 IOleObject_QueryInterface(lpObject
, &IID_IOleCache
, (void**)&lpOleCache
) == S_OK
&&
1175 IOleCache_Cache(lpOleCache
, &fm
, 0, &conn
) == S_OK
&&
1176 IOleObject_QueryInterface(lpObject
, &IID_IDataObject
, (void**)&lpDataObject
) == S_OK
&&
1177 IDataObject_SetData(lpDataObject
, &fm
, &stgm
, TRUE
) == S_OK
)
1181 reobject
.cbStruct
= sizeof(reobject
);
1182 reobject
.cp
= REO_CP_SELECTION
;
1183 reobject
.clsid
= clsid
;
1184 reobject
.poleobj
= lpObject
;
1185 reobject
.pstg
= lpStorage
;
1186 reobject
.polesite
= lpClientSite
;
1187 /* convert from twips to .01 mm */
1188 reobject
.sizel
.cx
= MulDiv(sz
->cx
, 254, 144);
1189 reobject
.sizel
.cy
= MulDiv(sz
->cy
, 254, 144);
1190 reobject
.dvaspect
= DVASPECT_CONTENT
;
1191 reobject
.dwFlags
= 0; /* FIXME */
1192 reobject
.dwUser
= 0;
1194 ME_InsertOLEFromCursor(editor
, &reobject
, 0);
1198 if (lpObject
) IOleObject_Release(lpObject
);
1199 if (lpClientSite
) IOleClientSite_Release(lpClientSite
);
1200 if (lpStorage
) IStorage_Release(lpStorage
);
1201 if (lpDataObject
) IDataObject_Release(lpDataObject
);
1202 if (lpOleCache
) IOleCache_Release(lpOleCache
);
1203 if (lpReOle
) IRichEditOle_Release(lpReOle
);
1208 static void ME_RTFReadShpPictGroup( RTF_Info
*info
)
1216 if (info
->rtfClass
== rtfEOF
) return;
1217 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1219 if (--level
== 0) break;
1221 else if (RTFCheckCM( info
, rtfGroup
, rtfBeginGroup
))
1227 RTFRouteToken( info
);
1228 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1233 RTFRouteToken( info
); /* feed "}" back to router */
1237 static DWORD
read_hex_data( RTF_Info
*info
, BYTE
**out
)
1239 DWORD read
= 0, size
= 1024;
1245 if (info
->rtfClass
!= rtfText
)
1247 ERR("Called with incorrect token\n");
1251 buf
= HeapAlloc( GetProcessHeap(), 0, size
);
1254 val
= info
->rtfMajor
;
1255 for (flip
= TRUE
;; flip
= !flip
)
1257 RTFGetToken( info
);
1258 if (info
->rtfClass
== rtfEOF
)
1260 HeapFree( GetProcessHeap(), 0, buf
);
1263 if (info
->rtfClass
!= rtfText
) break;
1269 buf
= HeapReAlloc( GetProcessHeap(), 0, buf
, size
);
1272 buf
[read
++] = RTFCharToHex(val
) * 16 + RTFCharToHex(info
->rtfMajor
);
1275 val
= info
->rtfMajor
;
1277 if (flip
) FIXME("wrong hex string\n");
1283 static void ME_RTFReadPictGroup(RTF_Info
*info
)
1286 BYTE
*buffer
= NULL
;
1291 enum gfxkind
{gfx_unknown
= 0, gfx_enhmetafile
, gfx_metafile
, gfx_dib
} gfx
= gfx_unknown
;
1299 RTFGetToken( info
);
1301 if (info
->rtfClass
== rtfText
)
1306 size
= read_hex_data( info
, &buffer
);
1310 RTFSkipGroup( info
);
1312 } /* We potentially have a new token so fall through. */
1314 if (info
->rtfClass
== rtfEOF
) return;
1316 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1318 if (--level
== 0) break;
1321 if (RTFCheckCM( info
, rtfGroup
, rtfBeginGroup
))
1326 if (!RTFCheckCM( info
, rtfControl
, rtfPictAttr
))
1328 RTFRouteToken( info
);
1329 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1334 if (RTFCheckMM( info
, rtfPictAttr
, rtfWinMetafile
))
1336 mfp
.mm
= info
->rtfParam
;
1339 else if (RTFCheckMM( info
, rtfPictAttr
, rtfDevIndBitmap
))
1341 if (info
->rtfParam
!= 0) FIXME("dibitmap should be 0 (%d)\n", info
->rtfParam
);
1344 else if (RTFCheckMM( info
, rtfPictAttr
, rtfEmfBlip
))
1345 gfx
= gfx_enhmetafile
;
1346 else if (RTFCheckMM( info
, rtfPictAttr
, rtfPicWid
))
1347 mfp
.xExt
= info
->rtfParam
;
1348 else if (RTFCheckMM( info
, rtfPictAttr
, rtfPicHt
))
1349 mfp
.yExt
= info
->rtfParam
;
1350 else if (RTFCheckMM( info
, rtfPictAttr
, rtfPicGoalWid
))
1351 sz
.cx
= info
->rtfParam
;
1352 else if (RTFCheckMM( info
, rtfPictAttr
, rtfPicGoalHt
))
1353 sz
.cy
= info
->rtfParam
;
1355 FIXME("Non supported attribute: %d %d %d\n", info
->rtfClass
, info
->rtfMajor
, info
->rtfMinor
);
1362 case gfx_enhmetafile
:
1363 if ((hemf
= SetEnhMetaFileBits( size
, buffer
)))
1364 insert_static_object( info
->editor
, hemf
, NULL
, &sz
);
1367 if ((hemf
= SetWinMetaFileBits( size
, buffer
, NULL
, &mfp
)))
1368 insert_static_object( info
->editor
, hemf
, NULL
, &sz
);
1372 BITMAPINFO
*bi
= (BITMAPINFO
*)buffer
;
1374 unsigned nc
= bi
->bmiHeader
.biClrUsed
;
1376 /* not quite right, especially for bitfields type of compression */
1377 if (!nc
&& bi
->bmiHeader
.biBitCount
<= 8)
1378 nc
= 1 << bi
->bmiHeader
.biBitCount
;
1379 if ((hbmp
= CreateDIBitmap( hdc
, &bi
->bmiHeader
,
1380 CBM_INIT
, (char*)(bi
+ 1) + nc
* sizeof(RGBQUAD
),
1381 bi
, DIB_RGB_COLORS
)) )
1382 insert_static_object( info
->editor
, NULL
, hbmp
, &sz
);
1383 ReleaseDC( 0, hdc
);
1390 HeapFree( GetProcessHeap(), 0, buffer
);
1391 RTFRouteToken( info
); /* feed "}" back to router */
1395 /* for now, lookup the \result part and use it, whatever the object */
1396 static void ME_RTFReadObjectGroup(RTF_Info
*info
)
1401 if (info
->rtfClass
== rtfEOF
)
1403 if (RTFCheckCM(info
, rtfGroup
, rtfEndGroup
))
1405 if (RTFCheckCM(info
, rtfGroup
, rtfBeginGroup
))
1408 if (info
->rtfClass
== rtfEOF
)
1410 if (RTFCheckCMM(info
, rtfControl
, rtfDestination
, rtfObjResult
))
1414 while (RTFGetToken (info
) != rtfEOF
)
1416 if (info
->rtfClass
== rtfGroup
)
1418 if (info
->rtfMajor
== rtfBeginGroup
) level
++;
1419 else if (info
->rtfMajor
== rtfEndGroup
&& --level
< 0) break;
1421 RTFRouteToken(info
);
1424 else RTFSkipGroup(info
);
1427 if (!RTFCheckCM (info
, rtfControl
, rtfObjAttr
))
1429 FIXME("Non supported attribute: %d %d %d\n", info
->rtfClass
, info
->rtfMajor
, info
->rtfMinor
);
1433 RTFRouteToken(info
); /* feed "}" back to router */
1436 static void ME_RTFReadParnumGroup( RTF_Info
*info
)
1438 int level
= 1, type
= -1;
1439 WORD indent
= 0, start
= 1;
1440 WCHAR txt_before
= 0, txt_after
= 0;
1444 RTFGetToken( info
);
1446 if (RTFCheckCMM( info
, rtfControl
, rtfDestination
, rtfParNumTextBefore
) ||
1447 RTFCheckCMM( info
, rtfControl
, rtfDestination
, rtfParNumTextAfter
))
1449 int loc
= info
->rtfMinor
;
1451 RTFGetToken( info
);
1452 if (info
->rtfClass
== rtfText
)
1454 if (loc
== rtfParNumTextBefore
)
1455 txt_before
= info
->rtfMajor
;
1457 txt_after
= info
->rtfMajor
;
1460 /* falling through to catch EOFs and group level changes */
1463 if (info
->rtfClass
== rtfEOF
)
1466 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1468 if (--level
== 0) break;
1472 if (RTFCheckCM( info
, rtfGroup
, rtfBeginGroup
))
1478 /* Ignore non para-attr */
1479 if (!RTFCheckCM( info
, rtfControl
, rtfParAttr
))
1482 switch (info
->rtfMinor
)
1484 case rtfParLevel
: /* Para level is ignored */
1491 case rtfParNumDecimal
:
1494 case rtfParNumULetter
:
1495 type
= PFN_UCLETTER
;
1497 case rtfParNumURoman
:
1500 case rtfParNumLLetter
:
1501 type
= PFN_LCLETTER
;
1503 case rtfParNumLRoman
:
1507 case rtfParNumIndent
:
1508 indent
= info
->rtfParam
;
1510 case rtfParNumStartAt
:
1511 start
= info
->rtfParam
;
1518 info
->fmt
.dwMask
|= (PFM_NUMBERING
| PFM_NUMBERINGSTART
| PFM_NUMBERINGSTYLE
| PFM_NUMBERINGTAB
);
1519 info
->fmt
.wNumbering
= type
;
1520 info
->fmt
.wNumberingStart
= start
;
1521 info
->fmt
.wNumberingStyle
= PFNS_PAREN
;
1522 if (type
!= PFN_BULLET
)
1524 if (txt_before
== 0 && txt_after
== 0)
1525 info
->fmt
.wNumberingStyle
= PFNS_PLAIN
;
1526 else if (txt_after
== '.')
1527 info
->fmt
.wNumberingStyle
= PFNS_PERIOD
;
1528 else if (txt_before
== '(' && txt_after
== ')')
1529 info
->fmt
.wNumberingStyle
= PFNS_PARENS
;
1531 info
->fmt
.wNumberingTab
= indent
;
1534 TRACE("type %d indent %d start %d txt before %04x txt after %04x\n",
1535 type
, indent
, start
, txt_before
, txt_after
);
1537 RTFRouteToken( info
); /* feed "}" back to router */
1540 static void ME_RTFReadHook(RTF_Info
*info
)
1542 switch(info
->rtfClass
)
1545 switch(info
->rtfMajor
)
1548 if (info
->stackTop
< maxStack
) {
1549 info
->stack
[info
->stackTop
].style
= info
->style
;
1550 ME_AddRefStyle(info
->style
);
1551 info
->stack
[info
->stackTop
].codePage
= info
->codePage
;
1552 info
->stack
[info
->stackTop
].unicodeLength
= info
->unicodeLength
;
1555 info
->styleChanged
= FALSE
;
1559 RTFFlushOutputBuffer(info
);
1561 if (info
->stackTop
<= 0)
1562 info
->rtfClass
= rtfEOF
;
1563 if (info
->stackTop
< 0)
1566 ME_ReleaseStyle(info
->style
);
1567 info
->style
= info
->stack
[info
->stackTop
].style
;
1568 info
->codePage
= info
->stack
[info
->stackTop
].codePage
;
1569 info
->unicodeLength
= info
->stack
[info
->stackTop
].unicodeLength
;
1578 ME_StreamInFill(ME_InStream
*stream
)
1580 stream
->editstream
->dwError
= stream
->editstream
->pfnCallback(stream
->editstream
->dwCookie
,
1581 (BYTE
*)stream
->buffer
,
1582 sizeof(stream
->buffer
),
1583 (LONG
*)&stream
->dwSize
);
1587 static LRESULT
ME_StreamIn(ME_TextEditor
*editor
, DWORD format
, EDITSTREAM
*stream
, BOOL stripLastCR
)
1591 int from
, to
, nUndoMode
;
1592 int nEventMask
= editor
->nEventMask
;
1593 ME_InStream inStream
;
1594 BOOL invalidRTF
= FALSE
;
1595 ME_Cursor
*selStart
, *selEnd
;
1596 LRESULT num_read
= 0; /* bytes read for SF_TEXT, non-control chars inserted for SF_RTF */
1598 TRACE("stream==%p editor==%p format==0x%X\n", stream
, editor
, format
);
1599 editor
->nEventMask
= 0;
1601 ME_GetSelectionOfs(editor
, &from
, &to
);
1602 if (format
& SFF_SELECTION
&& editor
->mode
& TM_RICHTEXT
)
1604 ME_GetSelection(editor
, &selStart
, &selEnd
);
1605 style
= ME_GetSelectionInsertStyle(editor
);
1607 ME_InternalDeleteText(editor
, selStart
, to
- from
, FALSE
);
1609 /* Don't insert text at the end of the table row */
1610 if (!editor
->bEmulateVersion10
) { /* v4.1 */
1611 ME_DisplayItem
*para
= editor
->pCursors
->pPara
;
1612 if (para
->member
.para
.nFlags
& MEPF_ROWEND
)
1614 para
= para
->member
.para
.next_para
;
1615 editor
->pCursors
[0].pPara
= para
;
1616 editor
->pCursors
[0].pRun
= ME_FindItemFwd(para
, diRun
);
1617 editor
->pCursors
[0].nOffset
= 0;
1619 if (para
->member
.para
.nFlags
& MEPF_ROWSTART
)
1621 para
= para
->member
.para
.next_para
;
1622 editor
->pCursors
[0].pPara
= para
;
1623 editor
->pCursors
[0].pRun
= ME_FindItemFwd(para
, diRun
);
1624 editor
->pCursors
[0].nOffset
= 0;
1626 editor
->pCursors
[1] = editor
->pCursors
[0];
1627 } else { /* v1.0 - 3.0 */
1628 if (editor
->pCursors
[0].pRun
->member
.run
.nFlags
& MERF_ENDPARA
&&
1629 ME_IsInTable(editor
->pCursors
[0].pRun
))
1633 style
= editor
->pBuffer
->pDefaultStyle
;
1634 ME_AddRefStyle(style
);
1635 set_selection_cursors(editor
, 0, 0);
1636 ME_InternalDeleteText(editor
, &editor
->pCursors
[1],
1637 ME_GetTextLength(editor
), FALSE
);
1639 ME_ClearTempStyle(editor
);
1640 ME_SetDefaultParaFormat(editor
, &editor
->pCursors
[0].pPara
->member
.para
.fmt
);
1644 /* Back up undo mode to a local variable */
1645 nUndoMode
= editor
->nUndoMode
;
1647 /* Only create an undo if SFF_SELECTION is set */
1648 if (!(format
& SFF_SELECTION
))
1649 editor
->nUndoMode
= umIgnore
;
1651 inStream
.editstream
= stream
;
1652 inStream
.editstream
->dwError
= 0;
1653 inStream
.dwSize
= 0;
1654 inStream
.dwUsed
= 0;
1656 if (format
& SF_RTF
)
1658 /* Check if it's really RTF, and if it is not, use plain text */
1659 ME_StreamInFill(&inStream
);
1660 if (!inStream
.editstream
->dwError
)
1662 if ((!editor
->bEmulateVersion10
&& strncmp(inStream
.buffer
, "{\\rtf", 5) && strncmp(inStream
.buffer
, "{\\urtf", 6))
1663 || (editor
->bEmulateVersion10
&& *inStream
.buffer
!= '{'))
1666 inStream
.editstream
->dwError
= -16;
1671 if (!invalidRTF
&& !inStream
.editstream
->dwError
)
1674 from
= ME_GetCursorOfs(&editor
->pCursors
[0]);
1675 if (format
& SF_RTF
) {
1677 /* setup the RTF parser */
1678 memset(&parser
, 0, sizeof parser
);
1679 RTFSetEditStream(&parser
, &inStream
);
1680 parser
.rtfFormat
= format
&(SF_TEXT
|SF_RTF
);
1681 parser
.editor
= editor
;
1682 parser
.style
= style
;
1683 WriterInit(&parser
);
1685 RTFSetReadHook(&parser
, ME_RTFReadHook
);
1686 RTFSetDestinationCallback(&parser
, rtfShpPict
, ME_RTFReadShpPictGroup
);
1687 RTFSetDestinationCallback(&parser
, rtfPict
, ME_RTFReadPictGroup
);
1688 RTFSetDestinationCallback(&parser
, rtfObject
, ME_RTFReadObjectGroup
);
1689 RTFSetDestinationCallback(&parser
, rtfParNumbering
, ME_RTFReadParnumGroup
);
1690 if (!parser
.editor
->bEmulateVersion10
) /* v4.1 */
1692 RTFSetDestinationCallback(&parser
, rtfNoNestTables
, RTFSkipGroup
);
1693 RTFSetDestinationCallback(&parser
, rtfNestTableProps
, RTFReadGroup
);
1697 /* do the parsing */
1699 RTFFlushOutputBuffer(&parser
);
1700 if (!editor
->bEmulateVersion10
) /* v4.1 */
1702 if (parser
.tableDef
&& parser
.tableDef
->row_start
&&
1703 (parser
.nestingLevel
> 0 || parser
.canInheritInTbl
))
1705 /* Delete any incomplete table row at the end of the rich text. */
1709 parser
.rtfMinor
= rtfRow
;
1710 /* Complete the table row before deleting it.
1711 * By doing it this way we will have the current paragraph format set
1712 * properly to reflect that is not in the complete table, and undo items
1713 * will be added for this change to the current paragraph format. */
1714 if (parser
.nestingLevel
> 0)
1716 while (parser
.nestingLevel
> 1)
1717 ME_RTFSpecialCharHook(&parser
); /* Decrements nestingLevel */
1718 para
= parser
.tableDef
->row_start
;
1719 ME_RTFSpecialCharHook(&parser
);
1723 para
= parser
.tableDef
->row_start
;
1724 ME_RTFSpecialCharHook(&parser
);
1725 assert( para
->nFlags
& MEPF_ROWEND
);
1726 para
= para_next( para
);
1729 editor
->pCursors
[1].pPara
= para_get_di( para
);
1730 editor
->pCursors
[1].pRun
= run_get_di( para_first_run( para
) );
1731 editor
->pCursors
[1].nOffset
= 0;
1732 nOfs
= ME_GetCursorOfs(&editor
->pCursors
[1]);
1733 nChars
= ME_GetCursorOfs(&editor
->pCursors
[0]) - nOfs
;
1734 ME_InternalDeleteText(editor
, &editor
->pCursors
[1], nChars
, TRUE
);
1735 if (parser
.tableDef
) parser
.tableDef
->row_start
= NULL
;
1738 ME_CheckTablesForCorruption(editor
);
1739 RTFDestroy(&parser
);
1741 if (parser
.stackTop
> 0)
1743 while (--parser
.stackTop
>= 0)
1745 ME_ReleaseStyle(parser
.style
);
1746 parser
.style
= parser
.stack
[parser
.stackTop
].style
;
1748 if (!inStream
.editstream
->dwError
)
1749 inStream
.editstream
->dwError
= HRESULT_FROM_WIN32(ERROR_HANDLE_EOF
);
1752 /* Remove last line break, as mandated by tests. This is not affected by
1753 CR/LF counters, since RTF streaming presents only \para tokens, which
1754 are converted according to the standard rules: \r for 2.0, \r\n for 1.0
1756 if (stripLastCR
&& !(format
& SFF_SELECTION
)) {
1758 ME_GetSelection(editor
, &selStart
, &selEnd
);
1759 newto
= ME_GetCursorOfs(selEnd
);
1760 if (newto
> to
+ (editor
->bEmulateVersion10
? 1 : 0)) {
1761 WCHAR lastchar
[3] = {'\0', '\0'};
1762 int linebreakSize
= editor
->bEmulateVersion10
? 2 : 1;
1763 ME_Cursor linebreakCursor
= *selEnd
, lastcharCursor
= *selEnd
;
1766 /* Set the final eop to the char fmt of the last char */
1767 cf
.cbSize
= sizeof(cf
);
1768 cf
.dwMask
= CFM_ALL2
;
1769 ME_MoveCursorChars(editor
, &lastcharCursor
, -1, FALSE
);
1770 ME_GetCharFormat(editor
, &lastcharCursor
, &linebreakCursor
, &cf
);
1771 set_selection_cursors(editor
, newto
, -1);
1772 ME_SetSelectionCharFormat(editor
, &cf
);
1773 set_selection_cursors(editor
, newto
, newto
);
1775 ME_MoveCursorChars(editor
, &linebreakCursor
, -linebreakSize
, FALSE
);
1776 ME_GetTextW(editor
, lastchar
, 2, &linebreakCursor
, linebreakSize
, FALSE
, FALSE
);
1777 if (lastchar
[0] == '\r' && (lastchar
[1] == '\n' || lastchar
[1] == '\0')) {
1778 ME_InternalDeleteText(editor
, &linebreakCursor
, linebreakSize
, FALSE
);
1782 to
= ME_GetCursorOfs(&editor
->pCursors
[0]);
1783 num_read
= to
- from
;
1785 style
= parser
.style
;
1787 else if (format
& SF_TEXT
)
1789 num_read
= ME_StreamInText(editor
, format
, &inStream
, style
);
1790 to
= ME_GetCursorOfs(&editor
->pCursors
[0]);
1793 ERR("EM_STREAMIN without SF_TEXT or SF_RTF\n");
1794 /* put the cursor at the top */
1795 if (!(format
& SFF_SELECTION
))
1796 set_selection_cursors(editor
, 0, 0);
1797 cursor_from_char_ofs( editor
, from
, &start
);
1798 ME_UpdateLinkAttribute(editor
, &start
, to
- from
);
1801 /* Restore saved undo mode */
1802 editor
->nUndoMode
= nUndoMode
;
1804 /* even if we didn't add an undo, we need to commit anything on the stack */
1805 ME_CommitUndo(editor
);
1807 /* If SFF_SELECTION isn't set, delete any undos from before we started too */
1808 if (!(format
& SFF_SELECTION
))
1809 ME_EmptyUndoStack(editor
);
1811 ME_ReleaseStyle(style
);
1812 editor
->nEventMask
= nEventMask
;
1813 ME_UpdateRepaint(editor
, FALSE
);
1814 if (!(format
& SFF_SELECTION
)) {
1815 ME_ClearTempStyle(editor
);
1817 update_caret(editor
);
1818 ME_SendSelChange(editor
);
1819 ME_SendRequestResize(editor
, FALSE
);
1825 typedef struct tagME_RTFStringStreamStruct
1830 } ME_RTFStringStreamStruct
;
1832 static DWORD CALLBACK
ME_ReadFromRTFString(DWORD_PTR dwCookie
, LPBYTE lpBuff
, LONG cb
, LONG
*pcb
)
1834 ME_RTFStringStreamStruct
*pStruct
= (ME_RTFStringStreamStruct
*)dwCookie
;
1837 count
= min(cb
, pStruct
->length
- pStruct
->pos
);
1838 memmove(lpBuff
, pStruct
->string
+ pStruct
->pos
, count
);
1839 pStruct
->pos
+= count
;
1845 ME_StreamInRTFString(ME_TextEditor
*editor
, BOOL selection
, char *string
)
1848 ME_RTFStringStreamStruct data
;
1850 data
.string
= string
;
1851 data
.length
= strlen(string
);
1853 es
.dwCookie
= (DWORD_PTR
)&data
;
1854 es
.pfnCallback
= ME_ReadFromRTFString
;
1855 ME_StreamIn(editor
, SF_RTF
| (selection
? SFF_SELECTION
: 0), &es
, TRUE
);
1860 ME_FindText(ME_TextEditor
*editor
, DWORD flags
, const CHARRANGE
*chrg
, const WCHAR
*text
, CHARRANGE
*chrgText
)
1862 const int nLen
= lstrlenW(text
);
1863 const int nTextLen
= ME_GetTextLength(editor
);
1866 WCHAR wLastChar
= ' ';
1868 TRACE("flags==0x%08x, chrg->cpMin==%d, chrg->cpMax==%d text==%s\n",
1869 flags
, chrg
->cpMin
, chrg
->cpMax
, debugstr_w(text
));
1871 if (flags
& ~(FR_DOWN
| FR_MATCHCASE
| FR_WHOLEWORD
))
1872 FIXME("Flags 0x%08x not implemented\n",
1873 flags
& ~(FR_DOWN
| FR_MATCHCASE
| FR_WHOLEWORD
));
1876 if (chrg
->cpMax
== -1)
1879 nMax
= chrg
->cpMax
> nTextLen
? nTextLen
: chrg
->cpMax
;
1881 /* In 1.0 emulation, if cpMax reaches end of text, add the FR_DOWN flag */
1882 if (editor
->bEmulateVersion10
&& nMax
== nTextLen
)
1887 /* In 1.0 emulation, cpMin must always be no greater than cpMax */
1888 if (editor
->bEmulateVersion10
&& nMax
< nMin
)
1892 chrgText
->cpMin
= -1;
1893 chrgText
->cpMax
= -1;
1898 /* when searching up, if cpMin < cpMax, then instead of searching
1899 * on [cpMin,cpMax], we search on [0,cpMin], otherwise, search on
1900 * [cpMax, cpMin]. The exception is when cpMax is -1, in which
1901 * case, it is always bigger than cpMin.
1903 if (!editor
->bEmulateVersion10
&& !(flags
& FR_DOWN
))
1907 nMax
= nMin
> nTextLen
? nTextLen
: nMin
;
1908 if (nMin
< nSwap
|| chrg
->cpMax
== -1)
1914 if (!nLen
|| nMin
< 0 || nMax
< 0 || nMax
< nMin
)
1917 chrgText
->cpMin
= chrgText
->cpMax
= -1;
1921 if (flags
& FR_DOWN
) /* Forward search */
1923 /* If possible, find the character before where the search starts */
1924 if ((flags
& FR_WHOLEWORD
) && nMin
)
1926 cursor_from_char_ofs( editor
, nMin
- 1, &cursor
);
1927 wLastChar
= *get_text( &cursor
.pRun
->member
.run
, cursor
.nOffset
);
1928 ME_MoveCursorChars(editor
, &cursor
, 1, FALSE
);
1930 else cursor_from_char_ofs( editor
, nMin
, &cursor
);
1932 while (cursor
.pRun
&& ME_GetCursorOfs(&cursor
) + nLen
<= nMax
)
1934 ME_DisplayItem
*pCurItem
= cursor
.pRun
;
1935 int nCurStart
= cursor
.nOffset
;
1938 while (pCurItem
&& ME_CharCompare( *get_text( &pCurItem
->member
.run
, nCurStart
+ nMatched
), text
[nMatched
], (flags
& FR_MATCHCASE
)))
1940 if ((flags
& FR_WHOLEWORD
) && iswalnum(wLastChar
))
1944 if (nMatched
== nLen
)
1946 ME_DisplayItem
*pNextItem
= pCurItem
;
1947 int nNextStart
= nCurStart
;
1950 /* Check to see if next character is a whitespace */
1951 if (flags
& FR_WHOLEWORD
)
1953 if (nCurStart
+ nMatched
== pCurItem
->member
.run
.len
)
1955 pNextItem
= ME_FindItemFwd(pCurItem
, diRun
);
1956 nNextStart
= -nMatched
;
1960 wNextChar
= *get_text( &pNextItem
->member
.run
, nNextStart
+ nMatched
);
1964 if (iswalnum(wNextChar
))
1968 cursor
.nOffset
+= cursor
.pPara
->member
.para
.nCharOfs
+ cursor
.pRun
->member
.run
.nCharOfs
;
1971 chrgText
->cpMin
= cursor
.nOffset
;
1972 chrgText
->cpMax
= cursor
.nOffset
+ nLen
;
1974 TRACE("found at %d-%d\n", cursor
.nOffset
, cursor
.nOffset
+ nLen
);
1975 return cursor
.nOffset
;
1977 if (nCurStart
+ nMatched
== pCurItem
->member
.run
.len
)
1979 pCurItem
= ME_FindItemFwd(pCurItem
, diRun
);
1980 nCurStart
= -nMatched
;
1984 wLastChar
= *get_text( &pCurItem
->member
.run
, nCurStart
+ nMatched
);
1989 if (cursor
.nOffset
== cursor
.pRun
->member
.run
.len
)
1991 ME_NextRun(&cursor
.pPara
, &cursor
.pRun
, TRUE
);
1996 else /* Backward search */
1998 /* If possible, find the character after where the search ends */
1999 if ((flags
& FR_WHOLEWORD
) && nMax
< nTextLen
- 1)
2001 cursor_from_char_ofs( editor
, nMax
+ 1, &cursor
);
2002 wLastChar
= *get_text( &cursor
.pRun
->member
.run
, cursor
.nOffset
);
2003 ME_MoveCursorChars(editor
, &cursor
, -1, FALSE
);
2005 else cursor_from_char_ofs( editor
, nMax
, &cursor
);
2007 while (cursor
.pRun
&& ME_GetCursorOfs(&cursor
) - nLen
>= nMin
)
2009 ME_DisplayItem
*pCurItem
= cursor
.pRun
;
2010 ME_DisplayItem
*pCurPara
= cursor
.pPara
;
2011 int nCurEnd
= cursor
.nOffset
;
2016 ME_PrevRun(&pCurPara
, &pCurItem
, TRUE
);
2017 nCurEnd
= pCurItem
->member
.run
.len
;
2020 while (pCurItem
&& ME_CharCompare( *get_text( &pCurItem
->member
.run
, nCurEnd
- nMatched
- 1 ),
2021 text
[nLen
- nMatched
- 1], (flags
& FR_MATCHCASE
) ))
2023 if ((flags
& FR_WHOLEWORD
) && iswalnum(wLastChar
))
2027 if (nMatched
== nLen
)
2029 ME_DisplayItem
*pPrevItem
= pCurItem
;
2030 int nPrevEnd
= nCurEnd
;
2034 /* Check to see if previous character is a whitespace */
2035 if (flags
& FR_WHOLEWORD
)
2037 if (nPrevEnd
- nMatched
== 0)
2039 pPrevItem
= ME_FindItemBack(pCurItem
, diRun
);
2041 nPrevEnd
= pPrevItem
->member
.run
.len
+ nMatched
;
2045 wPrevChar
= *get_text( &pPrevItem
->member
.run
, nPrevEnd
- nMatched
- 1 );
2049 if (iswalnum(wPrevChar
))
2053 nStart
= pCurPara
->member
.para
.nCharOfs
2054 + pCurItem
->member
.run
.nCharOfs
+ nCurEnd
- nMatched
;
2057 chrgText
->cpMin
= nStart
;
2058 chrgText
->cpMax
= nStart
+ nLen
;
2060 TRACE("found at %d-%d\n", nStart
, nStart
+ nLen
);
2063 if (nCurEnd
- nMatched
== 0)
2065 ME_PrevRun(&pCurPara
, &pCurItem
, TRUE
);
2066 /* Don't care about pCurItem becoming NULL here; it's already taken
2067 * care of in the exterior loop condition */
2068 nCurEnd
= pCurItem
->member
.run
.len
+ nMatched
;
2072 wLastChar
= *get_text( &pCurItem
->member
.run
, nCurEnd
- nMatched
- 1 );
2077 if (cursor
.nOffset
< 0)
2079 ME_PrevRun(&cursor
.pPara
, &cursor
.pRun
, TRUE
);
2080 cursor
.nOffset
= cursor
.pRun
->member
.run
.len
;
2084 TRACE("not found\n");
2086 chrgText
->cpMin
= chrgText
->cpMax
= -1;
2090 static int ME_GetTextEx(ME_TextEditor
*editor
, GETTEXTEX
*ex
, LPARAM pText
)
2095 if (!ex
->cb
|| !pText
) return 0;
2097 if (ex
->flags
& ~(GT_SELECTION
| GT_USECRLF
))
2098 FIXME("GETTEXTEX flags 0x%08x not supported\n", ex
->flags
& ~(GT_SELECTION
| GT_USECRLF
));
2100 if (ex
->flags
& GT_SELECTION
)
2103 int nStartCur
= ME_GetSelectionOfs(editor
, &from
, &to
);
2104 start
= editor
->pCursors
[nStartCur
];
2109 ME_SetCursorToStart(editor
, &start
);
2112 if (ex
->codepage
== CP_UNICODE
)
2114 return ME_GetTextW(editor
, (LPWSTR
)pText
, ex
->cb
/ sizeof(WCHAR
) - 1,
2115 &start
, nChars
, ex
->flags
& GT_USECRLF
, FALSE
);
2119 /* potentially each char may be a CR, why calculate the exact value with O(N) when
2120 we can just take a bigger buffer? :)
2121 The above assumption still holds with CR/LF counters, since CR->CRLF expansion
2122 occurs only in richedit 2.0 mode, in which line breaks have only one CR
2124 int crlfmul
= (ex
->flags
& GT_USECRLF
) ? 2 : 1;
2129 buflen
= min(crlfmul
* nChars
, ex
->cb
- 1);
2130 buffer
= heap_alloc((buflen
+ 1) * sizeof(WCHAR
));
2132 nChars
= ME_GetTextW(editor
, buffer
, buflen
, &start
, nChars
, ex
->flags
& GT_USECRLF
, FALSE
);
2133 rc
= WideCharToMultiByte(ex
->codepage
, 0, buffer
, nChars
+ 1,
2134 (LPSTR
)pText
, ex
->cb
, ex
->lpDefaultChar
, ex
->lpUsedDefChar
);
2135 if (rc
) rc
--; /* do not count 0 terminator */
2142 static int ME_GetTextRange(ME_TextEditor
*editor
, WCHAR
*strText
,
2143 const ME_Cursor
*start
, int nLen
, BOOL unicode
)
2145 if (!strText
) return 0;
2147 return ME_GetTextW(editor
, strText
, INT_MAX
, start
, nLen
, FALSE
, FALSE
);
2150 WCHAR
*p
= heap_alloc((nLen
+1) * sizeof(*p
));
2152 nChars
= ME_GetTextW(editor
, p
, nLen
, start
, nLen
, FALSE
, FALSE
);
2153 WideCharToMultiByte(CP_ACP
, 0, p
, nChars
+1, (char *)strText
,
2154 nLen
+1, NULL
, NULL
);
2160 int set_selection( ME_TextEditor
*editor
, int to
, int from
)
2164 TRACE("%d - %d\n", to
, from
);
2166 if (!editor
->bHideSelection
) ME_InvalidateSelection( editor
);
2167 end
= set_selection_cursors( editor
, to
, from
);
2168 if (!editor
->bHideSelection
) ME_InvalidateSelection( editor
);
2169 update_caret( editor
);
2170 ME_SendSelChange( editor
);
2175 typedef struct tagME_GlobalDestStruct
2179 } ME_GlobalDestStruct
;
2181 static DWORD CALLBACK
ME_ReadFromHGLOBALUnicode(DWORD_PTR dwCookie
, LPBYTE lpBuff
, LONG cb
, LONG
*pcb
)
2183 ME_GlobalDestStruct
*pData
= (ME_GlobalDestStruct
*)dwCookie
;
2188 pDest
= (WORD
*)lpBuff
;
2189 pSrc
= GlobalLock(pData
->hData
);
2190 for (i
= 0; i
<cb
&& pSrc
[pData
->nLength
+i
]; i
++) {
2191 pDest
[i
] = pSrc
[pData
->nLength
+i
];
2193 pData
->nLength
+= i
;
2195 GlobalUnlock(pData
->hData
);
2199 static DWORD CALLBACK
ME_ReadFromHGLOBALRTF(DWORD_PTR dwCookie
, LPBYTE lpBuff
, LONG cb
, LONG
*pcb
)
2201 ME_GlobalDestStruct
*pData
= (ME_GlobalDestStruct
*)dwCookie
;
2206 pSrc
= GlobalLock(pData
->hData
);
2207 for (i
= 0; i
<cb
&& pSrc
[pData
->nLength
+i
]; i
++) {
2208 pDest
[i
] = pSrc
[pData
->nLength
+i
];
2210 pData
->nLength
+= i
;
2212 GlobalUnlock(pData
->hData
);
2216 static const WCHAR rtfW
[] = {'R','i','c','h',' ','T','e','x','t',' ','F','o','r','m','a','t',0};
2218 static HRESULT
paste_rtf(ME_TextEditor
*editor
, FORMATETC
*fmt
, STGMEDIUM
*med
)
2221 ME_GlobalDestStruct gds
;
2224 gds
.hData
= med
->u
.hGlobal
;
2226 es
.dwCookie
= (DWORD_PTR
)&gds
;
2227 es
.pfnCallback
= ME_ReadFromHGLOBALRTF
;
2228 hr
= ME_StreamIn( editor
, SF_RTF
| SFF_SELECTION
, &es
, FALSE
) == 0 ? E_FAIL
: S_OK
;
2229 ReleaseStgMedium( med
);
2233 static HRESULT
paste_text(ME_TextEditor
*editor
, FORMATETC
*fmt
, STGMEDIUM
*med
)
2236 ME_GlobalDestStruct gds
;
2239 gds
.hData
= med
->u
.hGlobal
;
2241 es
.dwCookie
= (DWORD_PTR
)&gds
;
2242 es
.pfnCallback
= ME_ReadFromHGLOBALUnicode
;
2243 hr
= ME_StreamIn( editor
, SF_TEXT
| SF_UNICODE
| SFF_SELECTION
, &es
, FALSE
) == 0 ? E_FAIL
: S_OK
;
2244 ReleaseStgMedium( med
);
2248 static HRESULT
paste_emf(ME_TextEditor
*editor
, FORMATETC
*fmt
, STGMEDIUM
*med
)
2253 hr
= insert_static_object( editor
, med
->u
.hEnhMetaFile
, NULL
, &sz
);
2256 ME_CommitUndo( editor
);
2257 ME_UpdateRepaint( editor
, FALSE
);
2260 ReleaseStgMedium( med
);
2265 static struct paste_format
2268 HRESULT (*paste
)(ME_TextEditor
*, FORMATETC
*, STGMEDIUM
*);
2272 {{ -1, NULL
, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
}, paste_rtf
, rtfW
},
2273 {{ CF_UNICODETEXT
, NULL
, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
}, paste_text
},
2274 {{ CF_ENHMETAFILE
, NULL
, DVASPECT_CONTENT
, -1, TYMED_ENHMF
}, paste_emf
},
2278 static void init_paste_formats(void)
2280 struct paste_format
*format
;
2285 for (format
= paste_formats
; format
->fmt
.cfFormat
; format
++)
2288 format
->fmt
.cfFormat
= RegisterClipboardFormatW( format
->name
);
2294 static BOOL
paste_special(ME_TextEditor
*editor
, UINT cf
, REPASTESPECIAL
*ps
, BOOL check_only
)
2298 struct paste_format
*format
;
2301 /* Protect read-only edit control from modification */
2302 if (editor
->styleFlags
& ES_READONLY
)
2305 MessageBeep(MB_ICONERROR
);
2309 init_paste_formats();
2311 if (ps
&& ps
->dwAspect
!= DVASPECT_CONTENT
)
2312 FIXME("Ignoring aspect %x\n", ps
->dwAspect
);
2314 hr
= OleGetClipboard( &data
);
2315 if (hr
!= S_OK
) return FALSE
;
2317 if (cf
== CF_TEXT
) cf
= CF_UNICODETEXT
;
2320 for (format
= paste_formats
; format
->fmt
.cfFormat
; format
++)
2322 if (cf
&& cf
!= format
->fmt
.cfFormat
) continue;
2323 hr
= IDataObject_QueryGetData( data
, &format
->fmt
);
2328 hr
= IDataObject_GetData( data
, &format
->fmt
, &med
);
2329 if (hr
!= S_OK
) goto done
;
2330 hr
= format
->paste( editor
, &format
->fmt
, &med
);
2337 IDataObject_Release( data
);
2342 static HRESULT
editor_copy( ME_TextEditor
*editor
, ME_Cursor
*start
, int chars
, IDataObject
**data_out
)
2344 IDataObject
*data
= NULL
;
2347 if (editor
->lpOleCallback
)
2350 range
.cpMin
= ME_GetCursorOfs( start
);
2351 range
.cpMax
= range
.cpMin
+ chars
;
2352 hr
= IRichEditOleCallback_GetClipboardData( editor
->lpOleCallback
, &range
, RECO_COPY
, &data
);
2355 if (FAILED( hr
) || !data
)
2356 hr
= ME_GetDataObject( editor
, start
, chars
, &data
);
2358 if (SUCCEEDED( hr
))
2364 hr
= OleSetClipboard( data
);
2365 IDataObject_Release( data
);
2372 HRESULT
editor_copy_or_cut( ME_TextEditor
*editor
, BOOL cut
, ME_Cursor
*start
, int count
,
2373 IDataObject
**data_out
)
2377 if (cut
&& (editor
->styleFlags
& ES_READONLY
))
2379 return E_ACCESSDENIED
;
2382 hr
= editor_copy( editor
, start
, count
, data_out
);
2383 if (SUCCEEDED(hr
) && cut
)
2385 ME_InternalDeleteText( editor
, start
, count
, FALSE
);
2386 ME_CommitUndo( editor
);
2387 ME_UpdateRepaint( editor
, TRUE
);
2392 static BOOL
copy_or_cut( ME_TextEditor
*editor
, BOOL cut
)
2396 int start_cursor
= ME_GetSelectionOfs( editor
, &offs
, &count
);
2397 ME_Cursor
*sel_start
= &editor
->pCursors
[start_cursor
];
2399 if (editor
->cPasswordMask
) return FALSE
;
2402 hr
= editor_copy_or_cut( editor
, cut
, sel_start
, count
, NULL
);
2403 if (FAILED( hr
)) MessageBeep( MB_ICONERROR
);
2405 return SUCCEEDED( hr
);
2408 /* helper to send a msg filter notification */
2410 ME_FilterEvent(ME_TextEditor
*editor
, UINT msg
, WPARAM
* wParam
, LPARAM
* lParam
)
2414 if (!editor
->hWnd
|| !editor
->hwndParent
) return FALSE
;
2415 msgf
.nmhdr
.hwndFrom
= editor
->hWnd
;
2416 msgf
.nmhdr
.idFrom
= GetWindowLongW(editor
->hWnd
, GWLP_ID
);
2417 msgf
.nmhdr
.code
= EN_MSGFILTER
;
2419 msgf
.wParam
= *wParam
;
2420 msgf
.lParam
= *lParam
;
2421 if (SendMessageW(editor
->hwndParent
, WM_NOTIFY
, msgf
.nmhdr
.idFrom
, (LPARAM
)&msgf
))
2423 *wParam
= msgf
.wParam
;
2424 *lParam
= msgf
.lParam
;
2425 msgf
.wParam
= *wParam
;
2430 static void ME_UpdateSelectionLinkAttribute(ME_TextEditor
*editor
)
2432 ME_DisplayItem
*startPara
, *endPara
;
2433 ME_DisplayItem
*prev_para
;
2434 ME_Cursor
*from
, *to
;
2438 if (!editor
->AutoURLDetect_bEnable
) return;
2440 ME_GetSelection(editor
, &from
, &to
);
2442 /* Find paragraph previous to the one that contains start cursor */
2443 startPara
= from
->pPara
;
2444 prev_para
= startPara
->member
.para
.prev_para
;
2445 if (prev_para
->type
== diParagraph
) startPara
= prev_para
;
2447 /* Find paragraph that contains end cursor */
2448 endPara
= to
->pPara
->member
.para
.next_para
;
2450 start
.pPara
= startPara
;
2451 start
.pRun
= ME_FindItemFwd(startPara
, diRun
);
2453 nChars
= endPara
->member
.para
.nCharOfs
- startPara
->member
.para
.nCharOfs
;
2455 ME_UpdateLinkAttribute(editor
, &start
, nChars
);
2458 static BOOL
handle_enter(ME_TextEditor
*editor
)
2460 BOOL ctrl_is_down
= GetKeyState(VK_CONTROL
) & 0x8000;
2461 BOOL shift_is_down
= GetKeyState(VK_SHIFT
) & 0x8000;
2463 if (editor
->bDialogMode
)
2468 if (!(editor
->styleFlags
& ES_WANTRETURN
))
2470 if (editor
->hwndParent
)
2473 dw
= SendMessageW(editor
->hwndParent
, DM_GETDEFID
, 0, 0);
2474 if (HIWORD(dw
) == DC_HASDEFID
)
2476 HWND hwDefCtrl
= GetDlgItem(editor
->hwndParent
, LOWORD(dw
));
2479 SendMessageW(editor
->hwndParent
, WM_NEXTDLGCTL
, (WPARAM
)hwDefCtrl
, TRUE
);
2480 PostMessageW(hwDefCtrl
, WM_KEYDOWN
, VK_RETURN
, 0);
2488 if (editor
->styleFlags
& ES_MULTILINE
)
2490 static const WCHAR endl
= '\r';
2491 static const WCHAR endlv10
[] = {'\r','\n'};
2492 ME_Cursor cursor
= editor
->pCursors
[0];
2493 ME_DisplayItem
*para
= cursor
.pPara
;
2495 ME_Style
*style
, *eop_style
;
2497 if (editor
->styleFlags
& ES_READONLY
)
2499 MessageBeep(MB_ICONERROR
);
2503 ME_GetSelectionOfs(editor
, &from
, &to
);
2504 if (editor
->nTextLimit
> ME_GetTextLength(editor
) - (to
-from
))
2506 if (!editor
->bEmulateVersion10
) /* v4.1 */
2508 if (para
->member
.para
.nFlags
& MEPF_ROWEND
)
2510 /* Add a new table row after this row. */
2511 para
= para_get_di( table_append_row( editor
, ¶
->member
.para
) );
2512 para
= para
->member
.para
.next_para
;
2513 editor
->pCursors
[0].pPara
= para
;
2514 editor
->pCursors
[0].pRun
= ME_FindItemFwd(para
, diRun
);
2515 editor
->pCursors
[0].nOffset
= 0;
2516 editor
->pCursors
[1] = editor
->pCursors
[0];
2517 ME_CommitUndo(editor
);
2518 ME_CheckTablesForCorruption(editor
);
2519 ME_UpdateRepaint(editor
, FALSE
);
2522 else if (para
== editor
->pCursors
[1].pPara
&&
2523 cursor
.nOffset
+ cursor
.pRun
->member
.run
.nCharOfs
== 0 &&
2524 para
->member
.para
.prev_para
->member
.para
.nFlags
& MEPF_ROWSTART
&&
2525 !para
->member
.para
.prev_para
->member
.para
.nCharOfs
)
2527 /* Insert a newline before the table. */
2528 para
= para
->member
.para
.prev_para
;
2529 para
->member
.para
.nFlags
&= ~MEPF_ROWSTART
;
2530 editor
->pCursors
[0].pPara
= para
;
2531 editor
->pCursors
[0].pRun
= ME_FindItemFwd(para
, diRun
);
2532 editor
->pCursors
[1] = editor
->pCursors
[0];
2533 ME_InsertTextFromCursor(editor
, 0, &endl
, 1,
2534 editor
->pCursors
[0].pRun
->member
.run
.style
);
2535 para
= editor
->pBuffer
->pFirst
->member
.para
.next_para
;
2536 ME_SetDefaultParaFormat(editor
, ¶
->member
.para
.fmt
);
2537 para
->member
.para
.nFlags
= 0;
2538 para_mark_rewrap( editor
, ¶
->member
.para
);
2539 editor
->pCursors
[0].pPara
= para
;
2540 editor
->pCursors
[0].pRun
= ME_FindItemFwd(para
, diRun
);
2541 editor
->pCursors
[1] = editor
->pCursors
[0];
2542 para
->member
.para
.next_para
->member
.para
.nFlags
|= MEPF_ROWSTART
;
2543 ME_CommitCoalescingUndo(editor
);
2544 ME_CheckTablesForCorruption(editor
);
2545 ME_UpdateRepaint(editor
, FALSE
);
2549 else /* v1.0 - 3.0 */
2551 ME_DisplayItem
*para
= cursor
.pPara
;
2552 if (ME_IsInTable(para
))
2554 if (cursor
.pRun
->member
.run
.nFlags
& MERF_ENDPARA
)
2558 ME_ContinueCoalescingTransaction(editor
);
2559 para
= para_get_di( table_append_row( editor
, ¶
->member
.para
) );
2560 editor
->pCursors
[0].pPara
= para
;
2561 editor
->pCursors
[0].pRun
= ME_FindItemFwd(para
, diRun
);
2562 editor
->pCursors
[0].nOffset
= 0;
2563 editor
->pCursors
[1] = editor
->pCursors
[0];
2564 ME_CommitCoalescingUndo(editor
);
2565 ME_UpdateRepaint(editor
, FALSE
);
2571 ME_ContinueCoalescingTransaction(editor
);
2572 if (cursor
.pRun
->member
.run
.nCharOfs
+ cursor
.nOffset
== 0 &&
2573 !ME_IsInTable(para
->member
.para
.prev_para
))
2575 /* Insert newline before table */
2576 cursor
.pRun
= ME_FindItemBack(para
, diRun
);
2579 editor
->pCursors
[0].pRun
= cursor
.pRun
;
2580 editor
->pCursors
[0].pPara
= para
->member
.para
.prev_para
;
2582 editor
->pCursors
[0].nOffset
= 0;
2583 editor
->pCursors
[1] = editor
->pCursors
[0];
2584 ME_InsertTextFromCursor(editor
, 0, &endl
, 1,
2585 editor
->pCursors
[0].pRun
->member
.run
.style
);
2589 editor
->pCursors
[1] = editor
->pCursors
[0];
2590 para
= para_get_di( table_append_row( editor
, ¶
->member
.para
) );
2591 editor
->pCursors
[0].pPara
= para
;
2592 editor
->pCursors
[0].pRun
= ME_FindItemFwd(para
, diRun
);
2593 editor
->pCursors
[0].nOffset
= 0;
2594 editor
->pCursors
[1] = editor
->pCursors
[0];
2596 ME_CommitCoalescingUndo(editor
);
2597 ME_UpdateRepaint(editor
, FALSE
);
2603 style
= style_get_insert_style( editor
, editor
->pCursors
);
2605 /* Normally the new eop style is the insert style, however in a list it is copied from the existing
2606 eop style (this prevents the list label style changing when the new eop is inserted).
2607 No extra ref is taken here on eop_style. */
2608 if (para
->member
.para
.fmt
.wNumbering
)
2609 eop_style
= para
->member
.para
.eop_run
->style
;
2612 ME_ContinueCoalescingTransaction(editor
);
2614 ME_InsertEndRowFromCursor(editor
, 0);
2616 if (!editor
->bEmulateVersion10
)
2617 ME_InsertTextFromCursor(editor
, 0, &endl
, 1, eop_style
);
2619 ME_InsertTextFromCursor(editor
, 0, endlv10
, 2, eop_style
);
2620 ME_CommitCoalescingUndo(editor
);
2623 ME_UpdateSelectionLinkAttribute(editor
);
2624 ME_UpdateRepaint(editor
, FALSE
);
2625 ME_SaveTempStyle(editor
, style
); /* set the temp insert style for the new para */
2626 ME_ReleaseStyle(style
);
2634 ME_KeyDown(ME_TextEditor
*editor
, WORD nKey
)
2636 BOOL ctrl_is_down
= GetKeyState(VK_CONTROL
) & 0x8000;
2637 BOOL shift_is_down
= GetKeyState(VK_SHIFT
) & 0x8000;
2639 if (editor
->bMouseCaptured
)
2641 if (nKey
!= VK_SHIFT
&& nKey
!= VK_CONTROL
&& nKey
!= VK_MENU
)
2642 editor
->nSelectionType
= stPosition
;
2650 editor
->nUDArrowX
= -1;
2656 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
2657 ME_ArrowKey(editor
, nKey
, shift_is_down
, ctrl_is_down
);
2661 editor
->nUDArrowX
= -1;
2662 /* FIXME backspace and delete aren't the same, they act different wrt paragraph style of the merged paragraph */
2663 if (editor
->styleFlags
& ES_READONLY
)
2665 if (ME_IsSelection(editor
))
2667 ME_DeleteSelection(editor
);
2668 ME_CommitUndo(editor
);
2670 else if (nKey
== VK_DELETE
)
2672 /* Delete stops group typing.
2673 * (See MSDN remarks on EM_STOPGROUPTYPING message) */
2674 ME_DeleteTextAtCursor(editor
, 1, 1);
2675 ME_CommitUndo(editor
);
2677 else if (ME_ArrowKey(editor
, VK_LEFT
, FALSE
, FALSE
))
2679 BOOL bDeletionSucceeded
;
2680 /* Backspace can be grouped for a single undo */
2681 ME_ContinueCoalescingTransaction(editor
);
2682 bDeletionSucceeded
= ME_DeleteTextAtCursor(editor
, 1, 1);
2683 if (!bDeletionSucceeded
&& !editor
->bEmulateVersion10
) { /* v4.1 */
2684 /* Deletion was prevented so the cursor is moved back to where it was.
2685 * (e.g. this happens when trying to delete cell boundaries)
2687 ME_ArrowKey(editor
, VK_RIGHT
, FALSE
, FALSE
);
2689 ME_CommitCoalescingUndo(editor
);
2693 ME_MoveCursorFromTableRowStartParagraph(editor
);
2694 ME_UpdateSelectionLinkAttribute(editor
);
2695 ME_UpdateRepaint(editor
, FALSE
);
2696 ME_SendRequestResize(editor
, FALSE
);
2699 if (!editor
->bEmulateVersion10
)
2700 return handle_enter(editor
);
2703 if (editor
->bDialogMode
&& editor
->hwndParent
)
2704 PostMessageW(editor
->hwndParent
, WM_CLOSE
, 0, 0);
2707 if (editor
->bDialogMode
&& editor
->hwndParent
)
2708 SendMessageW(editor
->hwndParent
, WM_NEXTDLGCTL
, shift_is_down
, 0);
2713 set_selection( editor
, 0, -1 );
2719 return paste_special( editor
, 0, NULL
, FALSE
);
2724 return copy_or_cut(editor
, nKey
== 'X');
2742 if (nKey
!= VK_SHIFT
&& nKey
!= VK_CONTROL
&& nKey
&& nKey
!= VK_MENU
)
2743 editor
->nUDArrowX
= -1;
2750 chf
.cbSize
= sizeof(chf
);
2752 ME_GetSelectionCharFormat(editor
, &chf
);
2753 ME_DumpStyleToBuf(&chf
, buf
);
2754 MessageBoxA(NULL
, buf
, "Style dump", MB_OK
);
2758 ME_CheckCharOffsets(editor
);
2765 static LRESULT
ME_Char(ME_TextEditor
*editor
, WPARAM charCode
,
2766 LPARAM flags
, BOOL unicode
)
2770 if (editor
->bMouseCaptured
)
2773 if (editor
->styleFlags
& ES_READONLY
)
2775 MessageBeep(MB_ICONERROR
);
2776 return 0; /* FIXME really 0 ? */
2780 wstr
= (WCHAR
)charCode
;
2783 CHAR charA
= charCode
;
2784 MultiByteToWideChar(CP_ACP
, 0, &charA
, 1, &wstr
, 1);
2787 if (editor
->bEmulateVersion10
&& wstr
== '\r')
2788 handle_enter(editor
);
2790 if ((unsigned)wstr
>= ' ' || wstr
== '\t')
2792 ME_Cursor cursor
= editor
->pCursors
[0];
2793 ME_DisplayItem
*para
= cursor
.pPara
;
2795 BOOL ctrl_is_down
= GetKeyState(VK_CONTROL
) & 0x8000;
2796 ME_GetSelectionOfs(editor
, &from
, &to
);
2798 /* v4.1 allows tabs to be inserted with ctrl key down */
2799 !(ctrl_is_down
&& !editor
->bEmulateVersion10
))
2801 ME_DisplayItem
*para
;
2802 BOOL bSelectedRow
= FALSE
;
2804 para
= cursor
.pPara
;
2805 if (ME_IsSelection(editor
) &&
2806 cursor
.pRun
->member
.run
.nCharOfs
+ cursor
.nOffset
== 0 &&
2807 to
== ME_GetCursorOfs(&editor
->pCursors
[0]) &&
2808 para
->member
.para
.prev_para
->type
== diParagraph
)
2810 para
= para
->member
.para
.prev_para
;
2811 bSelectedRow
= TRUE
;
2813 if (ME_IsInTable(para
))
2815 ME_TabPressedInTable(editor
, bSelectedRow
);
2816 ME_CommitUndo(editor
);
2819 } else if (!editor
->bEmulateVersion10
) { /* v4.1 */
2820 if (para
->member
.para
.nFlags
& MEPF_ROWEND
) {
2822 para
= para
->member
.para
.next_para
;
2823 if (para
->member
.para
.nFlags
& MEPF_ROWSTART
)
2824 para
= para
->member
.para
.next_para
;
2825 editor
->pCursors
[0].pPara
= para
;
2826 editor
->pCursors
[0].pRun
= ME_FindItemFwd(para
, diRun
);
2827 editor
->pCursors
[0].nOffset
= 0;
2828 editor
->pCursors
[1] = editor
->pCursors
[0];
2831 } else { /* v1.0 - 3.0 */
2832 if (ME_IsInTable(cursor
.pRun
) &&
2833 cursor
.pRun
->member
.run
.nFlags
& MERF_ENDPARA
&&
2836 /* Text should not be inserted at the end of the table. */
2841 /* FIXME maybe it would make sense to call EM_REPLACESEL instead ? */
2842 /* WM_CHAR is restricted to nTextLimit */
2843 if(editor
->nTextLimit
> ME_GetTextLength(editor
) - (to
-from
))
2845 ME_Style
*style
= style_get_insert_style( editor
, editor
->pCursors
);
2846 ME_ContinueCoalescingTransaction(editor
);
2847 ME_InsertTextFromCursor(editor
, 0, &wstr
, 1, style
);
2848 ME_ReleaseStyle(style
);
2849 ME_CommitCoalescingUndo(editor
);
2850 ITextHost_TxSetCursor(editor
->texthost
, NULL
, FALSE
);
2853 ME_UpdateSelectionLinkAttribute(editor
);
2854 ME_UpdateRepaint(editor
, FALSE
);
2859 /* Process the message and calculate the new click count.
2861 * returns: The click count if it is mouse down event, else returns 0. */
2862 static int ME_CalculateClickCount(ME_TextEditor
*editor
, UINT msg
, WPARAM wParam
,
2865 static int clickNum
= 0;
2866 if (msg
< WM_MOUSEFIRST
|| msg
> WM_MOUSELAST
)
2869 if ((msg
== WM_LBUTTONDBLCLK
) ||
2870 (msg
== WM_RBUTTONDBLCLK
) ||
2871 (msg
== WM_MBUTTONDBLCLK
) ||
2872 (msg
== WM_XBUTTONDBLCLK
))
2874 msg
-= (WM_LBUTTONDBLCLK
- WM_LBUTTONDOWN
);
2877 if ((msg
== WM_LBUTTONDOWN
) ||
2878 (msg
== WM_RBUTTONDOWN
) ||
2879 (msg
== WM_MBUTTONDOWN
) ||
2880 (msg
== WM_XBUTTONDOWN
))
2882 static MSG prevClickMsg
;
2884 /* Compare the editor instead of the hwnd so that the this
2885 * can still be done for windowless richedit controls. */
2886 clickMsg
.hwnd
= (HWND
)editor
;
2887 clickMsg
.message
= msg
;
2888 clickMsg
.wParam
= wParam
;
2889 clickMsg
.lParam
= lParam
;
2890 clickMsg
.time
= GetMessageTime();
2891 clickMsg
.pt
.x
= (short)LOWORD(lParam
);
2892 clickMsg
.pt
.y
= (short)HIWORD(lParam
);
2893 if ((clickNum
!= 0) &&
2894 (clickMsg
.message
== prevClickMsg
.message
) &&
2895 (clickMsg
.hwnd
== prevClickMsg
.hwnd
) &&
2896 (clickMsg
.wParam
== prevClickMsg
.wParam
) &&
2897 (clickMsg
.time
- prevClickMsg
.time
< GetDoubleClickTime()) &&
2898 (abs(clickMsg
.pt
.x
- prevClickMsg
.pt
.x
) < GetSystemMetrics(SM_CXDOUBLECLK
)/2) &&
2899 (abs(clickMsg
.pt
.y
- prevClickMsg
.pt
.y
) < GetSystemMetrics(SM_CYDOUBLECLK
)/2))
2905 prevClickMsg
= clickMsg
;
2912 static BOOL
is_link( ME_Run
*run
)
2914 return (run
->style
->fmt
.dwMask
& CFM_LINK
) && (run
->style
->fmt
.dwEffects
& CFE_LINK
);
2917 static BOOL
ME_SetCursor(ME_TextEditor
*editor
)
2923 DWORD messagePos
= GetMessagePos();
2924 pt
.x
= (short)LOWORD(messagePos
);
2925 pt
.y
= (short)HIWORD(messagePos
);
2929 sbi
.cbSize
= sizeof(sbi
);
2930 GetScrollBarInfo(editor
->hWnd
, OBJID_HSCROLL
, &sbi
);
2931 if (!(sbi
.rgstate
[0] & (STATE_SYSTEM_INVISIBLE
|STATE_SYSTEM_OFFSCREEN
)) &&
2932 PtInRect(&sbi
.rcScrollBar
, pt
))
2934 ITextHost_TxSetCursor(editor
->texthost
,
2935 LoadCursorW(NULL
, (WCHAR
*)IDC_ARROW
), FALSE
);
2938 sbi
.cbSize
= sizeof(sbi
);
2939 GetScrollBarInfo(editor
->hWnd
, OBJID_VSCROLL
, &sbi
);
2940 if (!(sbi
.rgstate
[0] & (STATE_SYSTEM_INVISIBLE
|STATE_SYSTEM_OFFSCREEN
)) &&
2941 PtInRect(&sbi
.rcScrollBar
, pt
))
2943 ITextHost_TxSetCursor(editor
->texthost
,
2944 LoadCursorW(NULL
, (WCHAR
*)IDC_ARROW
), FALSE
);
2948 ITextHost_TxScreenToClient(editor
->texthost
, &pt
);
2950 if (editor
->nSelectionType
== stLine
&& editor
->bMouseCaptured
) {
2951 ITextHost_TxSetCursor(editor
->texthost
, hLeft
, FALSE
);
2954 if (!editor
->bEmulateVersion10
/* v4.1 */ &&
2955 pt
.y
< editor
->rcFormat
.top
&&
2956 pt
.x
< editor
->rcFormat
.left
)
2958 ITextHost_TxSetCursor(editor
->texthost
, hLeft
, FALSE
);
2961 if (pt
.y
< editor
->rcFormat
.top
|| pt
.y
> editor
->rcFormat
.bottom
)
2963 if (editor
->bEmulateVersion10
) /* v1.0 - 3.0 */
2964 ITextHost_TxSetCursor(editor
->texthost
,
2965 LoadCursorW(NULL
, (WCHAR
*)IDC_ARROW
), FALSE
);
2967 ITextHost_TxSetCursor(editor
->texthost
,
2968 LoadCursorW(NULL
, (WCHAR
*)IDC_IBEAM
), TRUE
);
2971 if (pt
.x
< editor
->rcFormat
.left
)
2973 ITextHost_TxSetCursor(editor
->texthost
, hLeft
, FALSE
);
2976 ME_CharFromPos(editor
, pt
.x
, pt
.y
, &cursor
, &isExact
);
2981 run
= &cursor
.pRun
->member
.run
;
2984 ITextHost_TxSetCursor(editor
->texthost
,
2985 LoadCursorW(NULL
, (WCHAR
*)IDC_HAND
),
2990 if (ME_IsSelection(editor
))
2992 int selStart
, selEnd
;
2993 int offset
= ME_GetCursorOfs(&cursor
);
2995 ME_GetSelectionOfs(editor
, &selStart
, &selEnd
);
2996 if (selStart
<= offset
&& selEnd
>= offset
) {
2997 ITextHost_TxSetCursor(editor
->texthost
,
2998 LoadCursorW(NULL
, (WCHAR
*)IDC_ARROW
),
3004 ITextHost_TxSetCursor(editor
->texthost
,
3005 LoadCursorW(NULL
, (WCHAR
*)IDC_IBEAM
), TRUE
);
3009 static void ME_SetDefaultFormatRect(ME_TextEditor
*editor
)
3011 ITextHost_TxGetClientRect(editor
->texthost
, &editor
->rcFormat
);
3012 editor
->rcFormat
.top
+= editor
->exStyleFlags
& WS_EX_CLIENTEDGE
? 1 : 0;
3013 editor
->rcFormat
.left
+= 1 + editor
->selofs
;
3014 editor
->rcFormat
.right
-= 1;
3017 static LONG
ME_GetSelectionType(ME_TextEditor
*editor
)
3019 LONG sel_type
= SEL_EMPTY
;
3022 ME_GetSelectionOfs(editor
, &start
, &end
);
3024 sel_type
= SEL_EMPTY
;
3027 LONG object_count
= 0, character_count
= 0;
3030 for (i
= 0; i
< end
- start
; i
++)
3034 cursor_from_char_ofs( editor
, start
+ i
, &cursor
);
3035 if (cursor
.pRun
->member
.run
.reobj
)
3039 if (character_count
>= 2 && object_count
>= 2)
3040 return (SEL_TEXT
| SEL_MULTICHAR
| SEL_OBJECT
| SEL_MULTIOBJECT
);
3042 if (character_count
)
3044 sel_type
|= SEL_TEXT
;
3045 if (character_count
>= 2)
3046 sel_type
|= SEL_MULTICHAR
;
3050 sel_type
|= SEL_OBJECT
;
3051 if (object_count
>= 2)
3052 sel_type
|= SEL_MULTIOBJECT
;
3058 static BOOL
ME_ShowContextMenu(ME_TextEditor
*editor
, int x
, int y
)
3064 if(!editor
->lpOleCallback
|| !editor
->hWnd
)
3066 ME_GetSelectionOfs(editor
, &selrange
.cpMin
, &selrange
.cpMax
);
3067 seltype
= ME_GetSelectionType(editor
);
3068 if(SUCCEEDED(IRichEditOleCallback_GetContextMenu(editor
->lpOleCallback
, seltype
, NULL
, &selrange
, &menu
)))
3070 TrackPopupMenu(menu
, TPM_LEFTALIGN
| TPM_RIGHTBUTTON
, x
, y
, 0, editor
->hwndParent
, NULL
);
3076 ME_TextEditor
*ME_MakeEditor(ITextHost
*texthost
, BOOL bEmulateVersion10
)
3078 ME_TextEditor
*ed
= heap_alloc(sizeof(*ed
));
3084 ed
->hwndParent
= NULL
;
3085 ed
->sizeWindow
.cx
= ed
->sizeWindow
.cy
= 0;
3086 ed
->texthost
= texthost
;
3088 ed
->bEmulateVersion10
= bEmulateVersion10
;
3090 ed
->exStyleFlags
= 0;
3092 ITextHost_TxGetPropertyBits(texthost
,
3093 (TXTBIT_RICHTEXT
|TXTBIT_MULTILINE
|
3094 TXTBIT_READONLY
|TXTBIT_USEPASSWORD
|
3095 TXTBIT_HIDESELECTION
|TXTBIT_SAVESELECTION
|
3096 TXTBIT_AUTOWORDSEL
|TXTBIT_VERTICAL
|
3097 TXTBIT_WORDWRAP
|TXTBIT_DISABLEDRAG
),
3099 ITextHost_TxGetScrollBars(texthost
, &ed
->styleFlags
);
3100 ed
->styleFlags
&= (WS_VSCROLL
|WS_HSCROLL
|ES_AUTOVSCROLL
|
3101 ES_AUTOHSCROLL
|ES_DISABLENOSCROLL
);
3102 ed
->pBuffer
= ME_MakeText();
3103 ed
->nZoomNumerator
= ed
->nZoomDenominator
= 0;
3104 ed
->nAvailWidth
= 0; /* wrap to client area */
3105 list_init( &ed
->style_list
);
3106 ME_MakeFirstParagraph(ed
);
3107 /* The four cursors are for:
3108 * 0 - The position where the caret is shown
3109 * 1 - The anchored end of the selection (for normal selection)
3110 * 2 & 3 - The anchored start and end respectively for word, line,
3111 * or paragraph selection.
3114 ed
->pCursors
= heap_alloc(ed
->nCursors
* sizeof(*ed
->pCursors
));
3115 ME_SetCursorToStart(ed
, &ed
->pCursors
[0]);
3116 ed
->pCursors
[1] = ed
->pCursors
[0];
3117 ed
->pCursors
[2] = ed
->pCursors
[0];
3118 ed
->pCursors
[3] = ed
->pCursors
[1];
3119 ed
->nLastTotalLength
= ed
->nTotalLength
= 0;
3120 ed
->nLastTotalWidth
= ed
->nTotalWidth
= 0;
3122 ed
->rgbBackColor
= -1;
3123 ed
->hbrBackground
= GetSysColorBrush(COLOR_WINDOW
);
3125 ed
->nModifyStep
= 0;
3126 ed
->nTextLimit
= TEXT_LIMIT_DEFAULT
;
3127 list_init( &ed
->undo_stack
);
3128 list_init( &ed
->redo_stack
);
3129 ed
->nUndoStackSize
= 0;
3130 ed
->nUndoLimit
= STACK_SIZE_DEFAULT
;
3131 ed
->nUndoMode
= umAddToUndo
;
3132 ed
->nParagraphs
= 1;
3133 ed
->nLastSelStart
= ed
->nLastSelEnd
= 0;
3134 ed
->pLastSelStartPara
= ed
->pLastSelEndPara
= ed
->pCursors
[0].pPara
;
3135 ed
->bHideSelection
= FALSE
;
3136 ed
->pfnWordBreak
= NULL
;
3137 ed
->lpOleCallback
= NULL
;
3138 ed
->mode
= TM_MULTILEVELUNDO
| TM_MULTICODEPAGE
;
3139 ed
->mode
|= (props
& TXTBIT_RICHTEXT
) ? TM_RICHTEXT
: TM_PLAINTEXT
;
3140 ed
->AutoURLDetect_bEnable
= FALSE
;
3141 ed
->bHaveFocus
= FALSE
;
3142 ed
->bDialogMode
= FALSE
;
3143 ed
->bMouseCaptured
= FALSE
;
3144 ed
->caret_hidden
= FALSE
;
3145 ed
->caret_height
= 0;
3146 for (i
=0; i
<HFONT_CACHE_SIZE
; i
++)
3148 ed
->pFontCache
[i
].nRefs
= 0;
3149 ed
->pFontCache
[i
].nAge
= 0;
3150 ed
->pFontCache
[i
].hFont
= NULL
;
3153 ME_CheckCharOffsets(ed
);
3154 SetRectEmpty(&ed
->rcFormat
);
3155 ed
->bDefaultFormatRect
= TRUE
;
3156 ITextHost_TxGetSelectionBarWidth(ed
->texthost
, &selbarwidth
);
3158 /* FIXME: Convert selbarwidth from HIMETRIC to pixels */
3159 ed
->selofs
= SELECTIONBAR_WIDTH
;
3160 ed
->styleFlags
|= ES_SELECTIONBAR
;
3164 ed
->nSelectionType
= stPosition
;
3166 ed
->cPasswordMask
= 0;
3167 if (props
& TXTBIT_USEPASSWORD
)
3168 ITextHost_TxGetPasswordChar(texthost
, &ed
->cPasswordMask
);
3170 if (props
& TXTBIT_AUTOWORDSEL
)
3171 ed
->styleFlags
|= ECO_AUTOWORDSELECTION
;
3172 if (props
& TXTBIT_MULTILINE
) {
3173 ed
->styleFlags
|= ES_MULTILINE
;
3174 ed
->bWordWrap
= (props
& TXTBIT_WORDWRAP
) != 0;
3176 ed
->bWordWrap
= FALSE
;
3178 if (props
& TXTBIT_READONLY
)
3179 ed
->styleFlags
|= ES_READONLY
;
3180 if (!(props
& TXTBIT_HIDESELECTION
))
3181 ed
->styleFlags
|= ES_NOHIDESEL
;
3182 if (props
& TXTBIT_SAVESELECTION
)
3183 ed
->styleFlags
|= ES_SAVESEL
;
3184 if (props
& TXTBIT_VERTICAL
)
3185 ed
->styleFlags
|= ES_VERTICAL
;
3186 if (props
& TXTBIT_DISABLEDRAG
)
3187 ed
->styleFlags
|= ES_NOOLEDRAGDROP
;
3189 ed
->notified_cr
.cpMin
= ed
->notified_cr
.cpMax
= 0;
3191 /* Default scrollbar information */
3192 ed
->vert_si
.cbSize
= sizeof(SCROLLINFO
);
3193 ed
->vert_si
.nMin
= 0;
3194 ed
->vert_si
.nMax
= 0;
3195 ed
->vert_si
.nPage
= 0;
3196 ed
->vert_si
.nPos
= 0;
3198 ed
->horz_si
.cbSize
= sizeof(SCROLLINFO
);
3199 ed
->horz_si
.nMin
= 0;
3200 ed
->horz_si
.nMax
= 0;
3201 ed
->horz_si
.nPage
= 0;
3202 ed
->horz_si
.nPos
= 0;
3204 ed
->wheel_remain
= 0;
3206 list_init( &ed
->reobj_list
);
3207 OleInitialize(NULL
);
3212 void ME_DestroyEditor(ME_TextEditor
*editor
)
3214 ME_DisplayItem
*p
= editor
->pBuffer
->pFirst
, *pNext
= NULL
;
3215 ME_Style
*s
, *cursor2
;
3218 ME_ClearTempStyle(editor
);
3219 ME_EmptyUndoStack(editor
);
3220 editor
->pBuffer
->pFirst
= NULL
;
3224 if (p
->type
== diParagraph
)
3225 para_destroy( editor
, &p
->member
.para
);
3227 ME_DestroyDisplayItem(p
);
3231 LIST_FOR_EACH_ENTRY_SAFE( s
, cursor2
, &editor
->style_list
, ME_Style
, entry
)
3232 ME_DestroyStyle( s
);
3234 ME_ReleaseStyle(editor
->pBuffer
->pDefaultStyle
);
3235 for (i
=0; i
<HFONT_CACHE_SIZE
; i
++)
3237 if (editor
->pFontCache
[i
].hFont
)
3238 DeleteObject(editor
->pFontCache
[i
].hFont
);
3240 if (editor
->rgbBackColor
!= -1)
3241 DeleteObject(editor
->hbrBackground
);
3242 if(editor
->lpOleCallback
)
3243 IRichEditOleCallback_Release(editor
->lpOleCallback
);
3244 ITextHost_Release(editor
->texthost
);
3247 IUnknown_Release(editor
->reOle
);
3248 editor
->reOle
= NULL
;
3252 heap_free(editor
->pBuffer
);
3253 heap_free(editor
->pCursors
);
3257 BOOL WINAPI
DllMain(HINSTANCE hinstDLL
, DWORD fdwReason
, LPVOID lpvReserved
)
3262 case DLL_PROCESS_ATTACH
:
3263 DisableThreadLibraryCalls(hinstDLL
);
3264 me_heap
= HeapCreate (0, 0x10000, 0);
3265 if (!ME_RegisterEditorClass(hinstDLL
)) return FALSE
;
3266 hLeft
= LoadCursorW(hinstDLL
, MAKEINTRESOURCEW(OCR_REVERSE
));
3270 case DLL_PROCESS_DETACH
:
3271 if (lpvReserved
) break;
3272 UnregisterClassW(RICHEDIT_CLASS20W
, 0);
3273 UnregisterClassW(MSFTEDIT_CLASS
, 0);
3274 UnregisterClassA(RICHEDIT_CLASS20A
, 0);
3275 UnregisterClassA("RichEdit50A", 0);
3276 if (ME_ListBoxRegistered
)
3277 UnregisterClassW(REListBox20W
, 0);
3278 if (ME_ComboBoxRegistered
)
3279 UnregisterClassW(REComboBox20W
, 0);
3281 HeapDestroy (me_heap
);
3288 static inline int get_default_line_height( ME_TextEditor
*editor
)
3292 if (editor
->pBuffer
&& editor
->pBuffer
->pDefaultStyle
)
3293 height
= editor
->pBuffer
->pDefaultStyle
->tm
.tmHeight
;
3294 if (height
<= 0) height
= 24;
3299 static inline int calc_wheel_change( int *remain
, int amount_per_click
)
3301 int change
= amount_per_click
* (float)*remain
/ WHEEL_DELTA
;
3302 *remain
-= WHEEL_DELTA
* change
/ amount_per_click
;
3306 static const char * const edit_messages
[] = {
3335 "EM_SETPASSWORDCHAR",
3336 "EM_EMPTYUNDOBUFFER",
3337 "EM_GETFIRSTVISIBLELINE",
3339 "EM_SETWORDBREAKPROC",
3340 "EM_GETWORDBREAKPROC",
3341 "EM_GETPASSWORDCHAR",
3351 static const char * const richedit_messages
[] = {
3356 "EM_EXLINEFROMCHAR",
3362 "EM_GETOLEINTERFACE",
3372 "EM_SETOLECALLBACK",
3374 "EM_SETTARGETDEVICE",
3382 "EM_GETWORDBREAKPROCEX",
3383 "EM_SETWORDBREAKPROCEX",
3385 "EM_UNKNOWN_USER_83",
3390 "EM_STOPGROUPTYPING",
3394 "EM_GETAUTOURLDETECT",
3397 "EM_GETTEXTLENGTHEX",
3400 "EM_UNKNOWN_USER_98",
3401 "EM_UNKNOWN_USER_99",
3402 "EM_SETPUNCTUATION",
3403 "EM_GETPUNCTUATION",
3404 "EM_SETWORDWRAPMODE",
3405 "EM_GETWORDWRAPMODE",
3411 "EM_UNKNOWN_USER_109",
3412 "EM_UNKNOWN_USER_110",
3413 "EM_UNKNOWN_USER_111",
3414 "EM_UNKNOWN_USER_112",
3415 "EM_UNKNOWN_USER_113",
3416 "EM_UNKNOWN_USER_114",
3417 "EM_UNKNOWN_USER_115",
3418 "EM_UNKNOWN_USER_116",
3419 "EM_UNKNOWN_USER_117",
3420 "EM_UNKNOWN_USER_118",
3421 "EM_UNKNOWN_USER_119",
3422 "EM_SETLANGOPTIONS",
3423 "EM_GETLANGOPTIONS",
3424 "EM_GETIMECOMPMODE",
3428 "EM_SETIMEMODEBIAS",
3433 get_msg_name(UINT msg
)
3435 if (msg
>= EM_GETSEL
&& msg
<= EM_CHARFROMPOS
)
3436 return edit_messages
[msg
- EM_GETSEL
];
3437 if (msg
>= EM_CANPASTE
&& msg
<= EM_GETIMEMODEBIAS
)
3438 return richedit_messages
[msg
- EM_CANPASTE
];
3442 static void ME_LinkNotify(ME_TextEditor
*editor
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
3446 ME_Cursor cursor
; /* The start of the clicked text. */
3449 x
= (short)LOWORD(lParam
);
3450 y
= (short)HIWORD(lParam
);
3451 ME_CharFromPos(editor
, x
, y
, &cursor
, &isExact
);
3452 if (!isExact
) return;
3454 if (is_link( &cursor
.pRun
->member
.run
))
3455 { /* The clicked run has CFE_LINK set */
3458 info
.nmhdr
.hwndFrom
= NULL
;
3459 info
.nmhdr
.idFrom
= 0;
3460 info
.nmhdr
.code
= EN_LINK
;
3462 info
.wParam
= wParam
;
3463 info
.lParam
= lParam
;
3466 /* find the first contiguous run with CFE_LINK set */
3467 info
.chrg
.cpMin
= ME_GetCursorOfs(&cursor
);
3469 while (ME_PrevRun( NULL
, &di
, FALSE
) && is_link( &di
->member
.run
))
3470 info
.chrg
.cpMin
-= di
->member
.run
.len
;
3472 /* find the last contiguous run with CFE_LINK set */
3473 info
.chrg
.cpMax
= ME_GetCursorOfs(&cursor
) + cursor
.pRun
->member
.run
.len
;
3475 while (ME_NextRun( NULL
, &di
, FALSE
) && is_link( &di
->member
.run
))
3476 info
.chrg
.cpMax
+= di
->member
.run
.len
;
3478 ITextHost_TxNotify(editor
->texthost
, info
.nmhdr
.code
, &info
);
3482 void ME_ReplaceSel(ME_TextEditor
*editor
, BOOL can_undo
, const WCHAR
*str
, int len
)
3484 int from
, to
, nStartCursor
;
3487 nStartCursor
= ME_GetSelectionOfs(editor
, &from
, &to
);
3488 style
= ME_GetSelectionInsertStyle(editor
);
3489 ME_InternalDeleteText(editor
, &editor
->pCursors
[nStartCursor
], to
-from
, FALSE
);
3490 ME_InsertTextFromCursor(editor
, 0, str
, len
, style
);
3491 ME_ReleaseStyle(style
);
3492 /* drop temporary style if line end */
3494 * FIXME question: does abc\n mean: put abc,
3495 * clear temp style, put \n? (would require a change)
3497 if (len
>0 && str
[len
-1] == '\n')
3498 ME_ClearTempStyle(editor
);
3499 ME_CommitUndo(editor
);
3500 ME_UpdateSelectionLinkAttribute(editor
);
3502 ME_EmptyUndoStack(editor
);
3503 ME_UpdateRepaint(editor
, FALSE
);
3506 static void ME_SetText(ME_TextEditor
*editor
, void *text
, BOOL unicode
)
3508 LONG codepage
= unicode
? CP_UNICODE
: CP_ACP
;
3511 LPWSTR wszText
= ME_ToUnicode(codepage
, text
, &textLen
);
3512 ME_InsertTextFromCursor(editor
, 0, wszText
, textLen
, editor
->pBuffer
->pDefaultStyle
);
3513 ME_EndToUnicode(codepage
, wszText
);
3516 static LRESULT
ME_WmCreate(ME_TextEditor
*editor
, LPARAM lParam
, BOOL unicode
)
3518 CREATESTRUCTW
*createW
= (CREATESTRUCTW
*)lParam
;
3519 CREATESTRUCTA
*createA
= (CREATESTRUCTA
*)lParam
;
3524 text
= unicode
? (void*)createW
->lpszName
: (void*)createA
->lpszName
;
3526 ME_SetDefaultFormatRect(editor
);
3528 max
= (editor
->styleFlags
& ES_DISABLENOSCROLL
) ? 1 : 0;
3529 if (~editor
->styleFlags
& ES_DISABLENOSCROLL
|| editor
->styleFlags
& WS_VSCROLL
)
3530 ITextHost_TxSetScrollRange(editor
->texthost
, SB_VERT
, 0, max
, TRUE
);
3532 if (~editor
->styleFlags
& ES_DISABLENOSCROLL
|| editor
->styleFlags
& WS_HSCROLL
)
3533 ITextHost_TxSetScrollRange(editor
->texthost
, SB_HORZ
, 0, max
, TRUE
);
3535 if (editor
->styleFlags
& ES_DISABLENOSCROLL
)
3537 if (editor
->styleFlags
& WS_VSCROLL
)
3539 ITextHost_TxEnableScrollBar(editor
->texthost
, SB_VERT
, ESB_DISABLE_BOTH
);
3540 ITextHost_TxShowScrollBar(editor
->texthost
, SB_VERT
, TRUE
);
3542 if (editor
->styleFlags
& WS_HSCROLL
)
3544 ITextHost_TxEnableScrollBar(editor
->texthost
, SB_HORZ
, ESB_DISABLE_BOTH
);
3545 ITextHost_TxShowScrollBar(editor
->texthost
, SB_HORZ
, TRUE
);
3551 ME_SetText(editor
, text
, unicode
);
3552 ME_SetCursorToStart(editor
, &editor
->pCursors
[0]);
3553 ME_SetCursorToStart(editor
, &editor
->pCursors
[1]);
3556 ME_CommitUndo(editor
);
3557 ME_WrapMarkedParagraphs(editor
);
3558 update_caret(editor
);
3562 static LRESULT
handle_EM_SETCHARFORMAT( ME_TextEditor
*editor
, WPARAM flags
, const CHARFORMAT2W
*fmt_in
)
3565 BOOL changed
= TRUE
;
3566 ME_Cursor start
, end
;
3568 if (!cfany_to_cf2w( &fmt
, fmt_in
)) return 0;
3570 if (flags
& SCF_ALL
)
3572 if (editor
->mode
& TM_PLAINTEXT
)
3574 ME_SetDefaultCharFormat( editor
, &fmt
);
3578 ME_SetCursorToStart( editor
, &start
);
3579 ME_SetCharFormat( editor
, &start
, NULL
, &fmt
);
3580 editor
->nModifyStep
= 1;
3583 else if (flags
& SCF_SELECTION
)
3585 if (editor
->mode
& TM_PLAINTEXT
) return 0;
3586 if (flags
& SCF_WORD
)
3588 end
= editor
->pCursors
[0];
3589 ME_MoveCursorWords( editor
, &end
, +1 );
3591 ME_MoveCursorWords( editor
, &start
, -1 );
3592 ME_SetCharFormat( editor
, &start
, &end
, &fmt
);
3594 changed
= ME_IsSelection( editor
);
3595 ME_SetSelectionCharFormat( editor
, &fmt
);
3596 if (changed
) editor
->nModifyStep
= 1;
3598 else /* SCF_DEFAULT */
3600 ME_SetDefaultCharFormat( editor
, &fmt
);
3603 ME_CommitUndo( editor
);
3606 ME_WrapMarkedParagraphs( editor
);
3607 ME_UpdateScrollBar( editor
);
3612 #define UNSUPPORTED_MSG(e) \
3614 FIXME(#e ": stub\n"); \
3615 *phresult = S_FALSE; \
3618 /* Handle messages for windowless and windowed richedit controls.
3620 * The LRESULT that is returned is a return value for window procs,
3621 * and the phresult parameter is the COM return code needed by the
3622 * text services interface. */
3623 LRESULT
ME_HandleMessage(ME_TextEditor
*editor
, UINT msg
, WPARAM wParam
,
3624 LPARAM lParam
, BOOL unicode
, HRESULT
* phresult
)
3630 UNSUPPORTED_MSG(EM_DISPLAYBAND
)
3631 UNSUPPORTED_MSG(EM_FINDWORDBREAK
)
3632 UNSUPPORTED_MSG(EM_FMTLINES
)
3633 UNSUPPORTED_MSG(EM_FORMATRANGE
)
3634 UNSUPPORTED_MSG(EM_GETBIDIOPTIONS
)
3635 UNSUPPORTED_MSG(EM_GETEDITSTYLE
)
3636 UNSUPPORTED_MSG(EM_GETIMECOMPMODE
)
3637 UNSUPPORTED_MSG(EM_GETIMESTATUS
)
3638 UNSUPPORTED_MSG(EM_SETIMESTATUS
)
3639 UNSUPPORTED_MSG(EM_GETLANGOPTIONS
)
3640 UNSUPPORTED_MSG(EM_GETREDONAME
)
3641 UNSUPPORTED_MSG(EM_GETTYPOGRAPHYOPTIONS
)
3642 UNSUPPORTED_MSG(EM_GETUNDONAME
)
3643 UNSUPPORTED_MSG(EM_GETWORDBREAKPROCEX
)
3644 UNSUPPORTED_MSG(EM_SETBIDIOPTIONS
)
3645 UNSUPPORTED_MSG(EM_SETEDITSTYLE
)
3646 UNSUPPORTED_MSG(EM_SETLANGOPTIONS
)
3647 UNSUPPORTED_MSG(EM_SETMARGINS
)
3648 UNSUPPORTED_MSG(EM_SETPALETTE
)
3649 UNSUPPORTED_MSG(EM_SETTABSTOPS
)
3650 UNSUPPORTED_MSG(EM_SETTYPOGRAPHYOPTIONS
)
3651 UNSUPPORTED_MSG(EM_SETWORDBREAKPROCEX
)
3653 /* Messages specific to Richedit controls */
3656 return ME_StreamIn(editor
, wParam
, (EDITSTREAM
*)lParam
, TRUE
);
3658 return ME_StreamOut(editor
, wParam
, (EDITSTREAM
*)lParam
);
3661 UINT code
= DLGC_WANTCHARS
|DLGC_WANTTAB
|DLGC_WANTARROWS
;
3664 editor
->bDialogMode
= TRUE
;
3665 if (editor
->styleFlags
& ES_MULTILINE
)
3666 code
|= DLGC_WANTMESSAGE
;
3667 if (!(editor
->styleFlags
& ES_SAVESEL
))
3668 code
|= DLGC_HASSETSEL
;
3671 case EM_EMPTYUNDOBUFFER
:
3672 ME_EmptyUndoStack(editor
);
3676 /* Note: wParam/lParam can be NULL */
3678 PUINT pfrom
= wParam
? (PUINT
)wParam
: &from
;
3679 PUINT pto
= lParam
? (PUINT
)lParam
: &to
;
3680 ME_GetSelectionOfs(editor
, (int *)pfrom
, (int *)pto
);
3681 if ((*pfrom
|*pto
) & 0xFFFF0000)
3683 return MAKELONG(*pfrom
,*pto
);
3687 CHARRANGE
*pRange
= (CHARRANGE
*)lParam
;
3688 ME_GetSelectionOfs(editor
, &pRange
->cpMin
, &pRange
->cpMax
);
3689 TRACE("EM_EXGETSEL = (%d,%d)\n", pRange
->cpMin
, pRange
->cpMax
);
3692 case EM_SETUNDOLIMIT
:
3694 if ((int)wParam
< 0)
3695 editor
->nUndoLimit
= STACK_SIZE_DEFAULT
;
3697 editor
->nUndoLimit
= min(wParam
, STACK_SIZE_MAX
);
3698 /* Setting a max stack size keeps wine from getting killed
3699 for hogging memory. Windows allocates all this memory at once, so
3700 no program would realistically set a value above our maximum. */
3701 return editor
->nUndoLimit
;
3704 return !list_empty( &editor
->undo_stack
);
3706 return !list_empty( &editor
->redo_stack
);
3707 case WM_UNDO
: /* FIXME: actually not the same */
3709 return ME_Undo(editor
);
3711 return ME_Redo(editor
);
3714 /* these flags are equivalent to the ES_* counterparts */
3715 DWORD mask
= ECO_VERTICAL
| ECO_AUTOHSCROLL
| ECO_AUTOVSCROLL
|
3716 ECO_NOHIDESEL
| ECO_READONLY
| ECO_WANTRETURN
| ECO_SELECTIONBAR
;
3717 DWORD settings
= editor
->styleFlags
& mask
;
3721 case EM_SETFONTSIZE
:
3724 LONG tmp_size
, size
;
3725 BOOL is_increase
= ((LONG
)wParam
> 0);
3727 if (editor
->mode
& TM_PLAINTEXT
)
3730 cf
.cbSize
= sizeof(cf
);
3731 cf
.dwMask
= CFM_SIZE
;
3732 ME_GetSelectionCharFormat(editor
, &cf
);
3733 tmp_size
= (cf
.yHeight
/ 20) + wParam
;
3737 else if (tmp_size
> 12 && tmp_size
< 28 && tmp_size
% 2)
3738 size
= tmp_size
+ (is_increase
? 1 : -1);
3739 else if (tmp_size
> 28 && tmp_size
< 36)
3740 size
= is_increase
? 36 : 28;
3741 else if (tmp_size
> 36 && tmp_size
< 48)
3742 size
= is_increase
? 48 : 36;
3743 else if (tmp_size
> 48 && tmp_size
< 72)
3744 size
= is_increase
? 72 : 48;
3745 else if (tmp_size
> 72 && tmp_size
< 80)
3746 size
= is_increase
? 80 : 72;
3747 else if (tmp_size
> 80 && tmp_size
< 1638)
3748 size
= 10 * (is_increase
? (tmp_size
/ 10 + 1) : (tmp_size
/ 10));
3749 else if (tmp_size
>= 1638)
3754 cf
.yHeight
= size
* 20; /* convert twips to points */
3755 ME_SetSelectionCharFormat(editor
, &cf
);
3756 ME_CommitUndo(editor
);
3757 ME_WrapMarkedParagraphs(editor
);
3758 ME_UpdateScrollBar(editor
);
3764 /* these flags are equivalent to ES_* counterparts, except for
3765 * ECO_AUTOWORDSELECTION that doesn't have an ES_* counterpart,
3766 * but is still stored in editor->styleFlags. */
3767 const DWORD mask
= ECO_VERTICAL
| ECO_AUTOHSCROLL
| ECO_AUTOVSCROLL
|
3768 ECO_NOHIDESEL
| ECO_READONLY
| ECO_WANTRETURN
|
3769 ECO_SELECTIONBAR
| ECO_AUTOWORDSELECTION
;
3770 DWORD settings
= mask
& editor
->styleFlags
;
3771 DWORD oldSettings
= settings
;
3772 DWORD changedSettings
;
3788 changedSettings
= oldSettings
^ settings
;
3790 if (changedSettings
) {
3791 editor
->styleFlags
= (editor
->styleFlags
& ~mask
) | (settings
& mask
);
3793 if (changedSettings
& ECO_SELECTIONBAR
)
3795 ITextHost_TxInvalidateRect(editor
->texthost
, &editor
->rcFormat
, TRUE
);
3796 if (settings
& ECO_SELECTIONBAR
) {
3797 assert(!editor
->selofs
);
3798 editor
->selofs
= SELECTIONBAR_WIDTH
;
3799 editor
->rcFormat
.left
+= editor
->selofs
;
3801 editor
->rcFormat
.left
-= editor
->selofs
;
3804 ME_RewrapRepaint(editor
);
3807 if ((changedSettings
& settings
& ES_NOHIDESEL
) && !editor
->bHaveFocus
)
3808 ME_InvalidateSelection( editor
);
3810 if (changedSettings
& settings
& ECO_VERTICAL
)
3811 FIXME("ECO_VERTICAL not implemented yet!\n");
3812 if (changedSettings
& settings
& ECO_AUTOHSCROLL
)
3813 FIXME("ECO_AUTOHSCROLL not implemented yet!\n");
3814 if (changedSettings
& settings
& ECO_AUTOVSCROLL
)
3815 FIXME("ECO_AUTOVSCROLL not implemented yet!\n");
3816 if (changedSettings
& settings
& ECO_WANTRETURN
)
3817 FIXME("ECO_WANTRETURN not implemented yet!\n");
3818 if (changedSettings
& settings
& ECO_AUTOWORDSELECTION
)
3819 FIXME("ECO_AUTOWORDSELECTION not implemented yet!\n");
3826 return set_selection( editor
, wParam
, lParam
);
3828 case EM_SETSCROLLPOS
:
3830 POINT
*point
= (POINT
*)lParam
;
3831 ME_ScrollAbs(editor
, point
->x
, point
->y
);
3834 case EM_AUTOURLDETECT
:
3836 if (wParam
==1 || wParam
==0)
3838 editor
->AutoURLDetect_bEnable
= (BOOL
)wParam
;
3841 return E_INVALIDARG
;
3843 case EM_GETAUTOURLDETECT
:
3845 return editor
->AutoURLDetect_bEnable
;
3849 CHARRANGE range
= *(CHARRANGE
*)lParam
;
3851 return set_selection( editor
, range
.cpMin
, range
.cpMax
);
3853 case EM_SHOWSCROLLBAR
:
3866 flags
= WS_HSCROLL
|WS_VSCROLL
;
3873 editor
->styleFlags
|= flags
;
3874 if (flags
& WS_HSCROLL
)
3875 ITextHost_TxShowScrollBar(editor
->texthost
, SB_HORZ
,
3876 editor
->nTotalWidth
> editor
->sizeWindow
.cx
);
3877 if (flags
& WS_VSCROLL
)
3878 ITextHost_TxShowScrollBar(editor
->texthost
, SB_VERT
,
3879 editor
->nTotalLength
> editor
->sizeWindow
.cy
);
3881 editor
->styleFlags
&= ~flags
;
3882 ITextHost_TxShowScrollBar(editor
->texthost
, wParam
, FALSE
);
3889 SETTEXTEX
*pStruct
= (SETTEXTEX
*)wParam
;
3892 BOOL bRtf
, bUnicode
, bSelection
, bUTF8
;
3893 int oldModify
= editor
->nModifyStep
;
3894 static const char utf8_bom
[] = {0xef, 0xbb, 0xbf};
3896 if (!pStruct
) return 0;
3898 /* If we detect ascii rtf at the start of the string,
3899 * we know it isn't unicode. */
3900 bRtf
= (lParam
&& (!strncmp((char *)lParam
, "{\\rtf", 5) ||
3901 !strncmp((char *)lParam
, "{\\urtf", 6)));
3902 bUnicode
= !bRtf
&& pStruct
->codepage
== CP_UNICODE
;
3903 bUTF8
= (lParam
&& (!strncmp((char *)lParam
, utf8_bom
, 3)));
3905 TRACE("EM_SETTEXTEX - %s, flags %d, cp %d\n",
3906 bUnicode
? debugstr_w((LPCWSTR
)lParam
) : debugstr_a((LPCSTR
)lParam
),
3907 pStruct
->flags
, pStruct
->codepage
);
3909 bSelection
= (pStruct
->flags
& ST_SELECTION
) != 0;
3911 int nStartCursor
= ME_GetSelectionOfs(editor
, &from
, &to
);
3912 style
= ME_GetSelectionInsertStyle(editor
);
3913 ME_InternalDeleteText(editor
, &editor
->pCursors
[nStartCursor
], to
- from
, FALSE
);
3916 ME_SetCursorToStart(editor
, &start
);
3917 ME_InternalDeleteText(editor
, &start
, ME_GetTextLength(editor
), FALSE
);
3918 style
= editor
->pBuffer
->pDefaultStyle
;
3922 ME_StreamInRTFString(editor
, bSelection
, (char *)lParam
);
3924 /* FIXME: The length returned doesn't include the rtf control
3925 * characters, only the actual text. */
3926 len
= lParam
? strlen((char *)lParam
) : 0;
3929 if (bUTF8
&& !bUnicode
) {
3930 wszText
= ME_ToUnicode(CP_UTF8
, (void *)(lParam
+3), &len
);
3931 ME_InsertTextFromCursor(editor
, 0, wszText
, len
, style
);
3932 ME_EndToUnicode(CP_UTF8
, wszText
);
3934 wszText
= ME_ToUnicode(pStruct
->codepage
, (void *)lParam
, &len
);
3935 ME_InsertTextFromCursor(editor
, 0, wszText
, len
, style
);
3936 ME_EndToUnicode(pStruct
->codepage
, wszText
);
3941 ME_ReleaseStyle(style
);
3942 ME_UpdateSelectionLinkAttribute(editor
);
3946 ME_SetCursorToStart(editor
, &cursor
);
3947 ME_UpdateLinkAttribute(editor
, &cursor
, INT_MAX
);
3949 ME_CommitUndo(editor
);
3950 if (!(pStruct
->flags
& ST_KEEPUNDO
))
3952 editor
->nModifyStep
= oldModify
;
3953 ME_EmptyUndoStack(editor
);
3955 ME_UpdateRepaint(editor
, FALSE
);
3958 case EM_SELECTIONTYPE
:
3959 return ME_GetSelectionType(editor
);
3960 case EM_SETBKGNDCOLOR
:
3963 if (editor
->rgbBackColor
!= -1) {
3964 DeleteObject(editor
->hbrBackground
);
3965 lColor
= editor
->rgbBackColor
;
3967 else lColor
= ITextHost_TxGetSysColor(editor
->texthost
, COLOR_WINDOW
);
3971 editor
->rgbBackColor
= -1;
3972 editor
->hbrBackground
= GetSysColorBrush(COLOR_WINDOW
);
3976 editor
->rgbBackColor
= lParam
;
3977 editor
->hbrBackground
= CreateSolidBrush(editor
->rgbBackColor
);
3979 ITextHost_TxInvalidateRect(editor
->texthost
, NULL
, TRUE
);
3983 return editor
->nModifyStep
== 0 ? 0 : -1;
3987 editor
->nModifyStep
= 1;
3989 editor
->nModifyStep
= 0;
3993 case EM_SETREADONLY
:
3996 editor
->styleFlags
|= ES_READONLY
;
3998 editor
->styleFlags
&= ~ES_READONLY
;
4001 case EM_SETEVENTMASK
:
4003 DWORD nOldMask
= editor
->nEventMask
;
4005 editor
->nEventMask
= lParam
;
4008 case EM_GETEVENTMASK
:
4009 return editor
->nEventMask
;
4010 case EM_SETCHARFORMAT
:
4011 return handle_EM_SETCHARFORMAT( editor
, wParam
, (CHARFORMAT2W
*)lParam
);
4012 case EM_GETCHARFORMAT
:
4014 CHARFORMAT2W tmp
, *dst
= (CHARFORMAT2W
*)lParam
;
4015 if (dst
->cbSize
!= sizeof(CHARFORMATA
) &&
4016 dst
->cbSize
!= sizeof(CHARFORMATW
) &&
4017 dst
->cbSize
!= sizeof(CHARFORMAT2A
) &&
4018 dst
->cbSize
!= sizeof(CHARFORMAT2W
))
4020 tmp
.cbSize
= sizeof(tmp
);
4022 ME_GetDefaultCharFormat(editor
, &tmp
);
4024 ME_GetSelectionCharFormat(editor
, &tmp
);
4025 cf2w_to_cfany(dst
, &tmp
);
4028 case EM_SETPARAFORMAT
:
4030 BOOL result
= ME_SetSelectionParaFormat(editor
, (PARAFORMAT2
*)lParam
);
4031 ME_WrapMarkedParagraphs(editor
);
4032 ME_UpdateScrollBar(editor
);
4033 ME_CommitUndo(editor
);
4036 case EM_GETPARAFORMAT
:
4037 ME_GetSelectionParaFormat(editor
, (PARAFORMAT2
*)lParam
);
4038 return ((PARAFORMAT2
*)lParam
)->dwMask
;
4039 case EM_GETFIRSTVISIBLELINE
:
4041 ME_DisplayItem
*p
= editor
->pBuffer
->pFirst
;
4042 int y
= editor
->vert_si
.nPos
;
4047 p
= ME_FindItemFwd(p
, diStartRowOrParagraphOrEnd
);
4048 if (p
->type
== diTextEnd
)
4050 if (p
->type
== diParagraph
) {
4051 ypara
= p
->member
.para
.pt
.y
;
4054 ystart
= ypara
+ p
->member
.row
.pt
.y
;
4055 yend
= ystart
+ p
->member
.row
.nHeight
;
4063 case EM_HIDESELECTION
:
4065 editor
->bHideSelection
= (wParam
!= 0);
4066 ME_InvalidateSelection(editor
);
4071 if (!(editor
->styleFlags
& ES_MULTILINE
))
4073 ME_ScrollDown( editor
, lParam
* get_default_line_height( editor
) );
4079 int nStartCursor
= ME_GetSelectionOfs(editor
, &from
, &to
);
4080 ME_InternalDeleteText(editor
, &editor
->pCursors
[nStartCursor
], to
-from
, FALSE
);
4081 ME_CommitUndo(editor
);
4082 ME_UpdateRepaint(editor
, TRUE
);
4088 LONG codepage
= unicode
? CP_UNICODE
: CP_ACP
;
4089 LPWSTR wszText
= ME_ToUnicode(codepage
, (void *)lParam
, &len
);
4091 TRACE("EM_REPLACESEL - %s\n", debugstr_w(wszText
));
4093 ME_ReplaceSel(editor
, !!wParam
, wszText
, len
);
4094 ME_EndToUnicode(codepage
, wszText
);
4097 case EM_SCROLLCARET
:
4098 ME_EnsureVisible(editor
, &editor
->pCursors
[0]);
4105 BOOL bRepaint
= LOWORD(lParam
);
4108 wParam
= (WPARAM
)GetStockObject(SYSTEM_FONT
);
4110 if (!GetObjectW((HGDIOBJ
)wParam
, sizeof(LOGFONTW
), &lf
))
4113 hDC
= ITextHost_TxGetDC(editor
->texthost
);
4114 ME_CharFormatFromLogFont(hDC
, &lf
, &fmt
);
4115 ITextHost_TxReleaseDC(editor
->texthost
, hDC
);
4116 if (editor
->mode
& TM_RICHTEXT
) {
4118 ME_SetCursorToStart(editor
, &start
);
4119 ME_SetCharFormat(editor
, &start
, NULL
, &fmt
);
4121 ME_SetDefaultCharFormat(editor
, &fmt
);
4123 ME_CommitUndo(editor
);
4124 ME_MarkAllForWrapping(editor
);
4125 ME_WrapMarkedParagraphs(editor
);
4126 ME_UpdateScrollBar(editor
);
4134 ME_SetCursorToStart(editor
, &cursor
);
4135 ME_InternalDeleteText(editor
, &cursor
, ME_GetTextLength(editor
), FALSE
);
4138 TRACE("WM_SETTEXT lParam==%lx\n",lParam
);
4139 if (!strncmp((char *)lParam
, "{\\rtf", 5) ||
4140 !strncmp((char *)lParam
, "{\\urtf", 6))
4142 /* Undocumented: WM_SETTEXT supports RTF text */
4143 ME_StreamInRTFString(editor
, 0, (char *)lParam
);
4146 ME_SetText(editor
, (void*)lParam
, unicode
);
4149 TRACE("WM_SETTEXT - NULL\n");
4150 ME_SetCursorToStart(editor
, &cursor
);
4151 ME_UpdateLinkAttribute(editor
, &cursor
, INT_MAX
);
4152 set_selection_cursors(editor
, 0, 0);
4153 editor
->nModifyStep
= 0;
4154 ME_CommitUndo(editor
);
4155 ME_EmptyUndoStack(editor
);
4156 ME_UpdateRepaint(editor
, FALSE
);
4160 return paste_special( editor
, 0, NULL
, TRUE
);
4162 case WM_MBUTTONDOWN
:
4166 case EM_PASTESPECIAL
:
4167 paste_special( editor
, wParam
, (REPASTESPECIAL
*)lParam
, FALSE
);
4171 copy_or_cut(editor
, msg
== WM_CUT
);
4173 case WM_GETTEXTLENGTH
:
4175 GETTEXTLENGTHEX how
;
4177 /* CR/LF conversion required in 2.0 mode, verbatim in 1.0 mode */
4178 how
.flags
= GTL_CLOSE
| (editor
->bEmulateVersion10
? 0 : GTL_USECRLF
) | GTL_NUMCHARS
;
4179 how
.codepage
= unicode
? CP_UNICODE
: CP_ACP
;
4180 return ME_GetTextLengthEx(editor
, &how
);
4182 case EM_GETTEXTLENGTHEX
:
4183 return ME_GetTextLengthEx(editor
, (GETTEXTLENGTHEX
*)wParam
);
4187 ex
.cb
= wParam
* (unicode
? sizeof(WCHAR
) : sizeof(CHAR
));
4188 ex
.flags
= GT_USECRLF
;
4189 ex
.codepage
= unicode
? CP_UNICODE
: CP_ACP
;
4190 ex
.lpDefaultChar
= NULL
;
4191 ex
.lpUsedDefChar
= NULL
;
4192 return ME_GetTextEx(editor
, &ex
, lParam
);
4195 return ME_GetTextEx(editor
, (GETTEXTEX
*)wParam
, lParam
);
4198 int nFrom
, nTo
, nStartCur
= ME_GetSelectionOfs(editor
, &nFrom
, &nTo
);
4199 ME_Cursor
*from
= &editor
->pCursors
[nStartCur
];
4200 return ME_GetTextRange(editor
, (WCHAR
*)lParam
, from
,
4201 nTo
- nFrom
, unicode
);
4203 case EM_GETSCROLLPOS
:
4205 POINT
*point
= (POINT
*)lParam
;
4206 point
->x
= editor
->horz_si
.nPos
;
4207 point
->y
= editor
->vert_si
.nPos
;
4208 /* 16-bit scaled value is returned as stored in scrollinfo */
4209 if (editor
->horz_si
.nMax
> 0xffff)
4210 point
->x
= MulDiv(point
->x
, 0xffff, editor
->horz_si
.nMax
);
4211 if (editor
->vert_si
.nMax
> 0xffff)
4212 point
->y
= MulDiv(point
->y
, 0xffff, editor
->vert_si
.nMax
);
4215 case EM_GETTEXTRANGE
:
4217 TEXTRANGEW
*rng
= (TEXTRANGEW
*)lParam
;
4219 int nStart
= rng
->chrg
.cpMin
;
4220 int nEnd
= rng
->chrg
.cpMax
;
4221 int textlength
= ME_GetTextLength(editor
);
4223 TRACE("EM_GETTEXTRANGE min=%d max=%d unicode=%d textlength=%d\n",
4224 rng
->chrg
.cpMin
, rng
->chrg
.cpMax
, unicode
, textlength
);
4225 if (nStart
< 0) return 0;
4226 if ((nStart
== 0 && nEnd
== -1) || nEnd
> textlength
)
4228 if (nStart
>= nEnd
) return 0;
4230 cursor_from_char_ofs( editor
, nStart
, &start
);
4231 return ME_GetTextRange(editor
, rng
->lpstrText
, &start
, nEnd
- nStart
, unicode
);
4235 ME_DisplayItem
*run
;
4236 const unsigned int nMaxChars
= *(WORD
*) lParam
;
4237 unsigned int nCharsLeft
= nMaxChars
;
4238 char *dest
= (char *) lParam
;
4239 BOOL wroteNull
= FALSE
;
4241 TRACE("EM_GETLINE: row=%d, nMaxChars=%d (%s)\n", (int) wParam
, nMaxChars
,
4242 unicode
? "Unicode" : "Ansi");
4244 run
= ME_FindRowWithNumber(editor
, wParam
);
4248 while (nCharsLeft
&& (run
= ME_FindItemFwd(run
, diRunOrStartRow
))
4249 && run
->type
== diRun
)
4251 WCHAR
*str
= get_text( &run
->member
.run
, 0 );
4254 nCopy
= min(nCharsLeft
, run
->member
.run
.len
);
4257 memcpy(dest
, str
, nCopy
* sizeof(WCHAR
));
4259 nCopy
= WideCharToMultiByte(CP_ACP
, 0, str
, nCopy
, dest
,
4260 nCharsLeft
, NULL
, NULL
);
4261 dest
+= nCopy
* (unicode
? sizeof(WCHAR
) : 1);
4262 nCharsLeft
-= nCopy
;
4265 /* append line termination, space allowing */
4269 *((WCHAR
*)dest
) = '\0';
4276 TRACE("EM_GETLINE: got %u characters\n", nMaxChars
- nCharsLeft
);
4277 return nMaxChars
- nCharsLeft
- (wroteNull
? 1 : 0);
4279 case EM_GETLINECOUNT
:
4281 ME_DisplayItem
*item
= editor
->pBuffer
->pLast
;
4282 int nRows
= editor
->total_rows
;
4283 ME_DisplayItem
*prev_para
= NULL
, *last_para
= NULL
;
4285 last_para
= ME_FindItemBack(item
, diRun
);
4286 prev_para
= ME_FindItemBack(last_para
, diRun
);
4288 assert(last_para
->member
.run
.nFlags
& MERF_ENDPARA
);
4289 if (editor
->bEmulateVersion10
&& prev_para
&&
4290 last_para
->member
.run
.nCharOfs
== 0 &&
4291 prev_para
->member
.run
.len
== 1 &&
4292 *get_text( &prev_para
->member
.run
, 0 ) == '\r')
4294 /* In 1.0 emulation, the last solitary \r at the very end of the text
4295 (if one exists) is NOT a line break.
4296 FIXME: this is an ugly hack. This should have a more regular model. */
4300 TRACE("EM_GETLINECOUNT: nRows==%d\n", nRows
);
4301 return max(1, nRows
);
4303 case EM_LINEFROMCHAR
:
4306 return ME_RowNumberFromCharOfs(editor
, ME_GetCursorOfs(&editor
->pCursors
[1]));
4308 return ME_RowNumberFromCharOfs(editor
, wParam
);
4310 case EM_EXLINEFROMCHAR
:
4313 return ME_RowNumberFromCharOfs(editor
, ME_GetCursorOfs(&editor
->pCursors
[1]));
4315 return ME_RowNumberFromCharOfs(editor
, lParam
);
4319 ME_DisplayItem
*item
, *para
;
4323 item
= ME_FindItemBack(editor
->pCursors
[0].pRun
, diStartRow
);
4325 item
= ME_FindRowWithNumber(editor
, wParam
);
4328 para
= ME_GetParagraph(item
);
4329 item
= ME_FindItemFwd(item
, diRun
);
4330 nCharOfs
= para
->member
.para
.nCharOfs
+ item
->member
.run
.nCharOfs
;
4331 TRACE("EM_LINEINDEX: nCharOfs==%d\n", nCharOfs
);
4336 ME_DisplayItem
*item
, *item_end
;
4337 int nChars
= 0, nThisLineOfs
= 0, nNextLineOfs
= 0;
4340 if (wParam
> ME_GetTextLength(editor
))
4344 FIXME("EM_LINELENGTH: returning number of unselected characters on lines with selection unsupported.\n");
4347 cursor_from_char_ofs( editor
, wParam
, &cursor
);
4348 item
= ME_RowStart( cursor
.pRun
);
4349 nThisLineOfs
= run_char_ofs( &ME_FindItemFwd( item
, diRun
)->member
.run
, 0 );
4350 item_end
= ME_FindItemFwd(item
, diStartRowOrParagraphOrEnd
);
4351 if (item_end
->type
== diStartRow
)
4352 nNextLineOfs
= run_char_ofs( &ME_FindItemFwd( item_end
, diRun
)->member
.run
, 0 );
4355 ME_DisplayItem
*endRun
= ME_FindItemBack(item_end
, diRun
);
4356 assert(endRun
&& endRun
->member
.run
.nFlags
& MERF_ENDPARA
);
4357 nNextLineOfs
= run_char_ofs( &endRun
->member
.run
, 0 );
4359 nChars
= nNextLineOfs
- nThisLineOfs
;
4360 TRACE("EM_LINELENGTH(%ld)==%d\n", wParam
, nChars
);
4363 case EM_EXLIMITTEXT
:
4365 if ((int)lParam
< 0)
4368 editor
->nTextLimit
= 65536;
4370 editor
->nTextLimit
= (int) lParam
;
4376 editor
->nTextLimit
= 65536;
4378 editor
->nTextLimit
= (int) wParam
;
4381 case EM_GETLIMITTEXT
:
4383 return editor
->nTextLimit
;
4389 FINDTEXTA
*ft
= (FINDTEXTA
*)lParam
;
4390 int nChars
= MultiByteToWideChar(CP_ACP
, 0, ft
->lpstrText
, -1, NULL
, 0);
4393 if ((tmp
= heap_alloc(nChars
* sizeof(*tmp
))) != NULL
)
4394 MultiByteToWideChar(CP_ACP
, 0, ft
->lpstrText
, -1, tmp
, nChars
);
4395 r
= ME_FindText(editor
, wParam
, &ft
->chrg
, tmp
, NULL
);
4398 FINDTEXTW
*ft
= (FINDTEXTW
*)lParam
;
4399 r
= ME_FindText(editor
, wParam
, &ft
->chrg
, ft
->lpstrText
, NULL
);
4407 FINDTEXTEXA
*ex
= (FINDTEXTEXA
*)lParam
;
4408 int nChars
= MultiByteToWideChar(CP_ACP
, 0, ex
->lpstrText
, -1, NULL
, 0);
4411 if ((tmp
= heap_alloc(nChars
* sizeof(*tmp
))) != NULL
)
4412 MultiByteToWideChar(CP_ACP
, 0, ex
->lpstrText
, -1, tmp
, nChars
);
4413 r
= ME_FindText(editor
, wParam
, &ex
->chrg
, tmp
, &ex
->chrgText
);
4416 FINDTEXTEXW
*ex
= (FINDTEXTEXW
*)lParam
;
4417 r
= ME_FindText(editor
, wParam
, &ex
->chrg
, ex
->lpstrText
, &ex
->chrgText
);
4423 FINDTEXTW
*ft
= (FINDTEXTW
*)lParam
;
4424 return ME_FindText(editor
, wParam
, &ft
->chrg
, ft
->lpstrText
, NULL
);
4426 case EM_FINDTEXTEXW
:
4428 FINDTEXTEXW
*ex
= (FINDTEXTEXW
*)lParam
;
4429 return ME_FindText(editor
, wParam
, &ex
->chrg
, ex
->lpstrText
, &ex
->chrgText
);
4432 if (!wParam
|| !lParam
)
4434 *(int *)wParam
= editor
->nZoomNumerator
;
4435 *(int *)lParam
= editor
->nZoomDenominator
;
4438 return ME_SetZoom(editor
, wParam
, lParam
);
4439 case EM_CHARFROMPOS
:
4442 if (ME_CharFromPos(editor
, ((POINTL
*)lParam
)->x
, ((POINTL
*)lParam
)->y
,
4444 return ME_GetCursorOfs(&cursor
);
4448 case EM_POSFROMCHAR
:
4451 int nCharOfs
, nLength
;
4455 /* detect which API version we're dealing with */
4456 if (wParam
>= 0x40000)
4458 nLength
= ME_GetTextLength(editor
);
4459 nCharOfs
= min(nCharOfs
, nLength
);
4460 nCharOfs
= max(nCharOfs
, 0);
4462 cursor_from_char_ofs( editor
, nCharOfs
, &cursor
);
4463 pt
.y
= cursor
.pRun
->member
.run
.pt
.y
;
4464 pt
.x
= cursor
.pRun
->member
.run
.pt
.x
+
4465 ME_PointFromChar( editor
, &cursor
.pRun
->member
.run
, cursor
.nOffset
, TRUE
);
4466 pt
.y
+= cursor
.pPara
->member
.para
.pt
.y
+ editor
->rcFormat
.top
;
4467 pt
.x
+= editor
->rcFormat
.left
;
4469 pt
.x
-= editor
->horz_si
.nPos
;
4470 pt
.y
-= editor
->vert_si
.nPos
;
4472 if (wParam
>= 0x40000) {
4473 *(POINTL
*)wParam
= pt
;
4475 return (wParam
>= 0x40000) ? 0 : MAKELONG( pt
.x
, pt
.y
);
4478 return ME_WmCreate(editor
, lParam
, unicode
);
4480 ME_DestroyEditor(editor
);
4485 if (wParam
== (WPARAM
)editor
->hWnd
&& GetCursorPos(&cursor_pos
) &&
4486 ScreenToClient(editor
->hWnd
, &cursor_pos
))
4487 ME_LinkNotify(editor
, msg
, 0, MAKELPARAM(cursor_pos
.x
, cursor_pos
.y
));
4488 return ME_SetCursor(editor
);
4490 case WM_LBUTTONDBLCLK
:
4491 case WM_LBUTTONDOWN
:
4493 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
4494 if ((editor
->nEventMask
& ENM_MOUSEEVENTS
) &&
4495 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4497 ITextHost_TxSetFocus(editor
->texthost
);
4498 ME_LButtonDown(editor
, (short)LOWORD(lParam
), (short)HIWORD(lParam
),
4499 ME_CalculateClickCount(editor
, msg
, wParam
, lParam
));
4500 ITextHost_TxSetCapture(editor
->texthost
, TRUE
);
4501 editor
->bMouseCaptured
= TRUE
;
4502 ME_LinkNotify(editor
, msg
, wParam
, lParam
);
4503 if (!ME_SetCursor(editor
)) goto do_default
;
4507 if ((editor
->nEventMask
& ENM_MOUSEEVENTS
) &&
4508 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4510 if (editor
->bMouseCaptured
)
4511 ME_MouseMove(editor
, (short)LOWORD(lParam
), (short)HIWORD(lParam
));
4513 ME_LinkNotify(editor
, msg
, wParam
, lParam
);
4514 /* Set cursor if mouse is captured, since WM_SETCURSOR won't be received. */
4515 if (editor
->bMouseCaptured
)
4516 ME_SetCursor(editor
);
4519 if (editor
->bMouseCaptured
) {
4520 ITextHost_TxSetCapture(editor
->texthost
, FALSE
);
4521 editor
->bMouseCaptured
= FALSE
;
4523 if (editor
->nSelectionType
== stDocument
)
4524 editor
->nSelectionType
= stPosition
;
4525 if ((editor
->nEventMask
& ENM_MOUSEEVENTS
) &&
4526 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4530 ME_SetCursor(editor
);
4531 ME_LinkNotify(editor
, msg
, wParam
, lParam
);
4535 case WM_RBUTTONDOWN
:
4536 case WM_RBUTTONDBLCLK
:
4537 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
4538 if ((editor
->nEventMask
& ENM_MOUSEEVENTS
) &&
4539 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4541 ME_LinkNotify(editor
, msg
, wParam
, lParam
);
4543 case WM_CONTEXTMENU
:
4544 if (!ME_ShowContextMenu(editor
, (short)LOWORD(lParam
), (short)HIWORD(lParam
)))
4548 editor
->bHaveFocus
= TRUE
;
4549 create_caret(editor
);
4550 update_caret(editor
);
4551 ME_SendOldNotify(editor
, EN_SETFOCUS
);
4552 if (!editor
->bHideSelection
&& !(editor
->styleFlags
& ES_NOHIDESEL
))
4553 ME_InvalidateSelection( editor
);
4556 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
4557 editor
->bHaveFocus
= FALSE
;
4558 editor
->wheel_remain
= 0;
4561 ME_SendOldNotify(editor
, EN_KILLFOCUS
);
4562 if (!editor
->bHideSelection
&& !(editor
->styleFlags
& ES_NOHIDESEL
))
4563 ME_InvalidateSelection( editor
);
4566 TRACE("editor wnd command = %d\n", LOWORD(wParam
));
4569 if ((editor
->nEventMask
& ENM_KEYEVENTS
) &&
4570 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4574 if ((editor
->nEventMask
& ENM_KEYEVENTS
) &&
4575 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4577 if (ME_KeyDown(editor
, LOWORD(wParam
)))
4581 if ((editor
->nEventMask
& ENM_KEYEVENTS
) &&
4582 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4584 return ME_Char(editor
, wParam
, lParam
, unicode
);
4588 if(wParam
== UNICODE_NOCHAR
) return TRUE
;
4589 if(wParam
<= 0x000fffff)
4591 if(wParam
> 0xffff) /* convert to surrogates */
4594 ME_Char(editor
, (wParam
>> 10) + 0xd800, 0, TRUE
);
4595 ME_Char(editor
, (wParam
& 0x03ff) + 0xdc00, 0, TRUE
);
4597 ME_Char(editor
, wParam
, 0, TRUE
);
4603 case EM_STOPGROUPTYPING
:
4604 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
4608 const int scrollUnit
= 7;
4610 switch(LOWORD(wParam
))
4613 ME_ScrollAbs(editor
, 0, 0);
4616 ME_ScrollAbs(editor
,
4617 editor
->horz_si
.nMax
- (int)editor
->horz_si
.nPage
,
4618 editor
->vert_si
.nMax
- (int)editor
->vert_si
.nPage
);
4621 ME_ScrollLeft(editor
, scrollUnit
);
4624 ME_ScrollRight(editor
, scrollUnit
);
4627 ME_ScrollLeft(editor
, editor
->sizeWindow
.cx
);
4630 ME_ScrollRight(editor
, editor
->sizeWindow
.cx
);
4633 case SB_THUMBPOSITION
:
4635 int pos
= HIWORD(wParam
);
4636 if (editor
->horz_si
.nMax
> 0xffff)
4637 pos
= MulDiv(pos
, editor
->horz_si
.nMax
, 0xffff);
4638 ME_HScrollAbs(editor
, pos
);
4644 case EM_SCROLL
: /* fall through */
4648 int lineHeight
= get_default_line_height( editor
);
4650 origNPos
= editor
->vert_si
.nPos
;
4652 switch(LOWORD(wParam
))
4655 ME_ScrollAbs(editor
, 0, 0);
4658 ME_ScrollAbs(editor
,
4659 editor
->horz_si
.nMax
- (int)editor
->horz_si
.nPage
,
4660 editor
->vert_si
.nMax
- (int)editor
->vert_si
.nPage
);
4663 ME_ScrollUp(editor
,lineHeight
);
4666 ME_ScrollDown(editor
,lineHeight
);
4669 ME_ScrollUp(editor
,editor
->sizeWindow
.cy
);
4672 ME_ScrollDown(editor
,editor
->sizeWindow
.cy
);
4675 case SB_THUMBPOSITION
:
4677 int pos
= HIWORD(wParam
);
4678 if (editor
->vert_si
.nMax
> 0xffff)
4679 pos
= MulDiv(pos
, editor
->vert_si
.nMax
, 0xffff);
4680 ME_VScrollAbs(editor
, pos
);
4684 if (msg
== EM_SCROLL
)
4685 return 0x00010000 | (((editor
->vert_si
.nPos
- origNPos
)/lineHeight
) & 0xffff);
4693 if ((editor
->nEventMask
& ENM_MOUSEEVENTS
) &&
4694 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4697 ctrl_is_down
= GetKeyState(VK_CONTROL
) & 0x8000;
4699 delta
= GET_WHEEL_DELTA_WPARAM(wParam
);
4701 /* if scrolling changes direction, ignore left overs */
4702 if ((delta
< 0 && editor
->wheel_remain
< 0) ||
4703 (delta
> 0 && editor
->wheel_remain
> 0))
4704 editor
->wheel_remain
+= delta
;
4706 editor
->wheel_remain
= delta
;
4708 if (editor
->wheel_remain
)
4712 if (!editor
->nZoomNumerator
|| !editor
->nZoomDenominator
)
4716 numerator
= editor
->nZoomNumerator
* 100 / editor
->nZoomDenominator
;
4718 numerator
+= calc_wheel_change( &editor
->wheel_remain
, 10 );
4719 if (numerator
>= 10 && numerator
<= 500)
4720 ME_SetZoom(editor
, numerator
, 100);
4725 SystemParametersInfoW( SPI_GETWHEELSCROLLLINES
, 0, &max_lines
, 0 );
4727 lines
= calc_wheel_change( &editor
->wheel_remain
, (int)max_lines
);
4729 ME_ScrollDown( editor
, -lines
* get_default_line_height( editor
) );
4736 *((RECT
*)lParam
) = editor
->rcFormat
;
4737 if (editor
->bDefaultFormatRect
)
4738 ((RECT
*)lParam
)->left
-= editor
->selofs
;
4748 RECT
*rc
= (RECT
*)lParam
;
4750 border
= editor
->exStyleFlags
& WS_EX_CLIENTEDGE
? 1 : 0;
4751 ITextHost_TxGetClientRect(editor
->texthost
, &clientRect
);
4754 editor
->rcFormat
.top
= max(0, rc
->top
- border
);
4755 editor
->rcFormat
.left
= max(0, rc
->left
- border
);
4756 editor
->rcFormat
.bottom
= min(clientRect
.bottom
, rc
->bottom
);
4757 editor
->rcFormat
.right
= min(clientRect
.right
, rc
->right
+ border
);
4758 } else if (wParam
== 1) {
4759 /* MSDN incorrectly says a wParam value of 1 causes the
4760 * lParam rect to be used as a relative offset,
4761 * however, the tests show it just prevents min/max bound
4763 editor
->rcFormat
.top
= rc
->top
- border
;
4764 editor
->rcFormat
.left
= rc
->left
- border
;
4765 editor
->rcFormat
.bottom
= rc
->bottom
;
4766 editor
->rcFormat
.right
= rc
->right
+ border
;
4770 editor
->bDefaultFormatRect
= FALSE
;
4774 ME_SetDefaultFormatRect(editor
);
4775 editor
->bDefaultFormatRect
= TRUE
;
4777 ME_MarkAllForWrapping(editor
);
4778 ME_WrapMarkedParagraphs(editor
);
4779 ME_UpdateScrollBar(editor
);
4780 if (msg
!= EM_SETRECTNP
)
4784 case EM_REQUESTRESIZE
:
4785 ME_SendRequestResize(editor
, TRUE
);
4789 case WM_WINDOWPOSCHANGED
:
4792 WINDOWPOS
*winpos
= (WINDOWPOS
*)lParam
;
4794 if (winpos
->flags
& SWP_NOCLIENTSIZE
) goto do_default
;
4795 ITextHost_TxGetClientRect(editor
->texthost
, &clientRect
);
4796 if (editor
->bDefaultFormatRect
) {
4797 ME_SetDefaultFormatRect(editor
);
4799 editor
->rcFormat
.right
+= clientRect
.right
- editor
->prevClientRect
.right
;
4800 editor
->rcFormat
.bottom
+= clientRect
.bottom
- editor
->prevClientRect
.bottom
;
4802 editor
->prevClientRect
= clientRect
;
4803 ME_RewrapRepaint(editor
);
4806 /* IME messages to make richedit controls IME aware */
4807 case WM_IME_SETCONTEXT
:
4808 case WM_IME_CONTROL
:
4810 case WM_IME_COMPOSITIONFULL
:
4812 case WM_IME_STARTCOMPOSITION
:
4814 editor
->imeStartIndex
=ME_GetCursorOfs(&editor
->pCursors
[0]);
4815 ME_DeleteSelection(editor
);
4816 ME_CommitUndo(editor
);
4817 ME_UpdateRepaint(editor
, FALSE
);
4820 case WM_IME_COMPOSITION
:
4824 ME_Style
*style
= style_get_insert_style( editor
, editor
->pCursors
);
4825 hIMC
= ITextHost_TxImmGetContext(editor
->texthost
);
4826 ME_DeleteSelection(editor
);
4827 ME_SaveTempStyle(editor
, style
);
4828 if (lParam
& (GCS_RESULTSTR
|GCS_COMPSTR
))
4830 LPWSTR lpCompStr
= NULL
;
4832 DWORD dwIndex
= lParam
& GCS_RESULTSTR
;
4834 dwIndex
= GCS_COMPSTR
;
4836 dwBufLen
= ImmGetCompositionStringW(hIMC
, dwIndex
, NULL
, 0);
4837 lpCompStr
= HeapAlloc(GetProcessHeap(),0,dwBufLen
+ sizeof(WCHAR
));
4838 ImmGetCompositionStringW(hIMC
, dwIndex
, lpCompStr
, dwBufLen
);
4839 lpCompStr
[dwBufLen
/sizeof(WCHAR
)] = 0;
4840 ME_InsertTextFromCursor(editor
,0,lpCompStr
,dwBufLen
/sizeof(WCHAR
),style
);
4841 HeapFree(GetProcessHeap(), 0, lpCompStr
);
4843 if (dwIndex
== GCS_COMPSTR
)
4844 set_selection_cursors(editor
,editor
->imeStartIndex
,
4845 editor
->imeStartIndex
+ dwBufLen
/sizeof(WCHAR
));
4847 ME_ReleaseStyle(style
);
4848 ME_CommitUndo(editor
);
4849 ME_UpdateRepaint(editor
, FALSE
);
4852 case WM_IME_ENDCOMPOSITION
:
4854 ME_DeleteSelection(editor
);
4855 editor
->imeStartIndex
=-1;
4858 case EM_GETOLEINTERFACE
:
4861 if (!CreateIRichEditOle(NULL
, editor
, (LPVOID
*)&editor
->reOle
))
4863 if (IUnknown_QueryInterface(editor
->reOle
, &IID_IRichEditOle
, (LPVOID
*)lParam
) == S_OK
)
4867 case EM_GETPASSWORDCHAR
:
4869 return editor
->cPasswordMask
;
4871 case EM_SETOLECALLBACK
:
4872 if(editor
->lpOleCallback
)
4873 IRichEditOleCallback_Release(editor
->lpOleCallback
);
4874 editor
->lpOleCallback
= (IRichEditOleCallback
*)lParam
;
4875 if(editor
->lpOleCallback
)
4876 IRichEditOleCallback_AddRef(editor
->lpOleCallback
);
4878 case EM_GETWORDBREAKPROC
:
4879 return (LRESULT
)editor
->pfnWordBreak
;
4880 case EM_SETWORDBREAKPROC
:
4882 EDITWORDBREAKPROCW pfnOld
= editor
->pfnWordBreak
;
4884 editor
->pfnWordBreak
= (EDITWORDBREAKPROCW
)lParam
;
4885 return (LRESULT
)pfnOld
;
4887 case EM_GETTEXTMODE
:
4888 return editor
->mode
;
4889 case EM_SETTEXTMODE
:
4894 if (ME_GetTextLength(editor
) ||
4895 !list_empty( &editor
->undo_stack
) || !list_empty( &editor
->redo_stack
))
4896 return E_UNEXPECTED
;
4898 /* Check for mutually exclusive flags in adjacent bits of wParam */
4899 if ((wParam
& (TM_RICHTEXT
| TM_MULTILEVELUNDO
| TM_MULTICODEPAGE
)) &
4900 (wParam
& (TM_PLAINTEXT
| TM_SINGLELEVELUNDO
| TM_SINGLECODEPAGE
)) << 1)
4901 return E_INVALIDARG
;
4903 if (wParam
& (TM_RICHTEXT
| TM_PLAINTEXT
))
4905 mask
|= TM_RICHTEXT
| TM_PLAINTEXT
;
4906 changes
|= wParam
& (TM_RICHTEXT
| TM_PLAINTEXT
);
4907 if (wParam
& TM_PLAINTEXT
) {
4908 /* Clear selection since it should be possible to select the
4909 * end of text run for rich text */
4910 ME_InvalidateSelection(editor
);
4911 ME_SetCursorToStart(editor
, &editor
->pCursors
[0]);
4912 editor
->pCursors
[1] = editor
->pCursors
[0];
4913 /* plain text can only have the default style. */
4914 ME_ClearTempStyle(editor
);
4915 ME_AddRefStyle(editor
->pBuffer
->pDefaultStyle
);
4916 ME_ReleaseStyle(editor
->pCursors
[0].pRun
->member
.run
.style
);
4917 editor
->pCursors
[0].pRun
->member
.run
.style
= editor
->pBuffer
->pDefaultStyle
;
4920 /* FIXME: Currently no support for undo level and code page options */
4921 editor
->mode
= (editor
->mode
& ~mask
) | changes
;
4924 case EM_SETPASSWORDCHAR
:
4926 editor
->cPasswordMask
= wParam
;
4927 ME_RewrapRepaint(editor
);
4930 case EM_SETTARGETDEVICE
:
4933 BOOL
new = (lParam
== 0 && (editor
->styleFlags
& ES_MULTILINE
));
4934 if (editor
->nAvailWidth
|| editor
->bWordWrap
!= new)
4936 editor
->bWordWrap
= new;
4937 editor
->nAvailWidth
= 0; /* wrap to client area */
4938 ME_RewrapRepaint(editor
);
4941 int width
= max(0, lParam
);
4942 if ((editor
->styleFlags
& ES_MULTILINE
) &&
4943 (!editor
->bWordWrap
|| editor
->nAvailWidth
!= width
))
4945 editor
->nAvailWidth
= width
;
4946 editor
->bWordWrap
= TRUE
;
4947 ME_RewrapRepaint(editor
);
4949 FIXME("EM_SETTARGETDEVICE doesn't use non-NULL target devices\n");
4954 *phresult
= S_FALSE
;
4960 static BOOL
create_windowed_editor(HWND hwnd
, CREATESTRUCTW
*create
, BOOL emulate_10
)
4962 ITextHost
*host
= ME_CreateTextHost( hwnd
, create
, emulate_10
);
4963 ME_TextEditor
*editor
;
4965 if (!host
) return FALSE
;
4967 editor
= ME_MakeEditor( host
, emulate_10
);
4970 ITextHost_Release( host
);
4974 editor
->exStyleFlags
= GetWindowLongW( hwnd
, GWL_EXSTYLE
);
4975 editor
->styleFlags
|= GetWindowLongW( hwnd
, GWL_STYLE
) & ES_WANTRETURN
;
4976 editor
->hWnd
= hwnd
; /* FIXME: Remove editor's dependence on hWnd */
4977 editor
->hwndParent
= create
->hwndParent
;
4979 SetWindowLongPtrW( hwnd
, 0, (LONG_PTR
)editor
);
4984 static LRESULT
RichEditWndProc_common(HWND hWnd
, UINT msg
, WPARAM wParam
,
4985 LPARAM lParam
, BOOL unicode
)
4987 ME_TextEditor
*editor
;
4989 LRESULT lresult
= 0;
4991 TRACE("enter hwnd %p msg %04x (%s) %lx %lx, unicode %d\n",
4992 hWnd
, msg
, get_msg_name(msg
), wParam
, lParam
, unicode
);
4994 editor
= (ME_TextEditor
*)GetWindowLongPtrW(hWnd
, 0);
4997 if (msg
== WM_NCCREATE
)
4999 CREATESTRUCTW
*pcs
= (CREATESTRUCTW
*)lParam
;
5001 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd
, pcs
->style
);
5002 return create_windowed_editor( hWnd
, pcs
, FALSE
);
5006 return DefWindowProcW(hWnd
, msg
, wParam
, lParam
);
5019 update_caret(editor
);
5020 hdc
= BeginPaint(editor
->hWnd
, &ps
);
5021 if (!editor
->bEmulateVersion10
|| (editor
->nEventMask
& ENM_UPDATE
))
5022 ME_SendOldNotify(editor
, EN_UPDATE
);
5023 old_brush
= SelectObject(hdc
, editor
->hbrBackground
);
5025 /* Erase area outside of the formatting rectangle */
5026 if (ps
.rcPaint
.top
< editor
->rcFormat
.top
)
5029 rc
.bottom
= editor
->rcFormat
.top
;
5030 PatBlt(hdc
, rc
.left
, rc
.top
, rc
.right
- rc
.left
, rc
.bottom
- rc
.top
, PATCOPY
);
5031 ps
.rcPaint
.top
= editor
->rcFormat
.top
;
5033 if (ps
.rcPaint
.bottom
> editor
->rcFormat
.bottom
) {
5035 rc
.top
= editor
->rcFormat
.bottom
;
5036 PatBlt(hdc
, rc
.left
, rc
.top
, rc
.right
- rc
.left
, rc
.bottom
- rc
.top
, PATCOPY
);
5037 ps
.rcPaint
.bottom
= editor
->rcFormat
.bottom
;
5039 if (ps
.rcPaint
.left
< editor
->rcFormat
.left
) {
5041 rc
.right
= editor
->rcFormat
.left
;
5042 PatBlt(hdc
, rc
.left
, rc
.top
, rc
.right
- rc
.left
, rc
.bottom
- rc
.top
, PATCOPY
);
5043 ps
.rcPaint
.left
= editor
->rcFormat
.left
;
5045 if (ps
.rcPaint
.right
> editor
->rcFormat
.right
) {
5047 rc
.left
= editor
->rcFormat
.right
;
5048 PatBlt(hdc
, rc
.left
, rc
.top
, rc
.right
- rc
.left
, rc
.bottom
- rc
.top
, PATCOPY
);
5049 ps
.rcPaint
.right
= editor
->rcFormat
.right
;
5052 ME_PaintContent(editor
, hdc
, &ps
.rcPaint
);
5053 SelectObject(hdc
, old_brush
);
5054 EndPaint(editor
->hWnd
, &ps
);
5059 HDC hDC
= (HDC
)wParam
;
5062 if (GetUpdateRect(editor
->hWnd
, &rc
, TRUE
))
5063 FillRect(hDC
, &rc
, editor
->hbrBackground
);
5069 const DWORD mask
= ECO_VERTICAL
| ECO_AUTOHSCROLL
| ECO_AUTOVSCROLL
|
5070 ECO_NOHIDESEL
| ECO_READONLY
| ECO_WANTRETURN
|
5072 lresult
= ME_HandleMessage(editor
, msg
, wParam
, lParam
, unicode
, &hresult
);
5073 dwStyle
= GetWindowLongW(hWnd
, GWL_STYLE
);
5074 dwStyle
= (dwStyle
& ~mask
) | (lresult
& mask
);
5075 SetWindowLongW(hWnd
, GWL_STYLE
, dwStyle
);
5078 case EM_SETREADONLY
:
5081 lresult
= ME_HandleMessage(editor
, msg
, wParam
, lParam
, unicode
, &hresult
);
5082 dwStyle
= GetWindowLongW(hWnd
, GWL_STYLE
);
5083 dwStyle
&= ~ES_READONLY
;
5085 dwStyle
|= ES_READONLY
;
5086 SetWindowLongW(hWnd
, GWL_STYLE
, dwStyle
);
5090 lresult
= ME_HandleMessage(editor
, msg
, wParam
, lParam
, unicode
, &hresult
);
5093 if (hresult
== S_FALSE
)
5094 lresult
= DefWindowProcW(hWnd
, msg
, wParam
, lParam
);
5096 TRACE("exit hwnd %p msg %04x (%s) %lx %lx, unicode %d -> %lu\n",
5097 hWnd
, msg
, get_msg_name(msg
), wParam
, lParam
, unicode
, lresult
);
5102 static LRESULT WINAPI
RichEditWndProcW(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
5104 BOOL unicode
= TRUE
;
5106 /* Under Win9x RichEdit20W returns ANSI strings, see the tests. */
5107 if (msg
== WM_GETTEXT
&& (GetVersion() & 0x80000000))
5110 return RichEditWndProc_common(hWnd
, msg
, wParam
, lParam
, unicode
);
5113 static LRESULT WINAPI
RichEditWndProcA(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
5115 return RichEditWndProc_common(hWnd
, msg
, wParam
, lParam
, FALSE
);
5118 /******************************************************************
5119 * RichEditANSIWndProc (RICHED20.10)
5121 LRESULT WINAPI
RichEditANSIWndProc(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
5123 return RichEditWndProcA(hWnd
, msg
, wParam
, lParam
);
5126 /******************************************************************
5127 * RichEdit10ANSIWndProc (RICHED20.9)
5129 LRESULT WINAPI
RichEdit10ANSIWndProc(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
5131 if (msg
== WM_NCCREATE
&& !GetWindowLongPtrW(hWnd
, 0))
5133 CREATESTRUCTW
*pcs
= (CREATESTRUCTW
*)lParam
;
5135 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd
, pcs
->style
);
5136 return create_windowed_editor( hWnd
, pcs
, TRUE
);
5138 return RichEditANSIWndProc(hWnd
, msg
, wParam
, lParam
);
5141 void ME_SendOldNotify(ME_TextEditor
*editor
, int nCode
)
5143 ITextHost_TxNotify(editor
->texthost
, nCode
, NULL
);
5146 /* Fill buffer with srcChars unicode characters from the start cursor.
5148 * buffer: destination buffer
5149 * buflen: length of buffer in characters excluding the NULL terminator.
5150 * start: start of editor text to copy into buffer.
5151 * srcChars: Number of characters to use from the editor text.
5152 * bCRLF: if true, replaces all end of lines with \r\n pairs.
5154 * returns the number of characters written excluding the NULL terminator.
5156 * The written text is always NULL terminated.
5158 int ME_GetTextW(ME_TextEditor
*editor
, WCHAR
*buffer
, int buflen
,
5159 const ME_Cursor
*start
, int srcChars
, BOOL bCRLF
,
5162 ME_DisplayItem
*pRun
, *pNextRun
;
5163 const WCHAR
*pStart
= buffer
;
5164 const WCHAR cr_lf
[] = {'\r', '\n', 0};
5168 /* bCRLF flag is only honored in 2.0 and up. 1.0 must always return text verbatim */
5169 if (editor
->bEmulateVersion10
) bCRLF
= FALSE
;
5173 pNextRun
= ME_FindItemFwd(pRun
, diRun
);
5175 nLen
= pRun
->member
.run
.len
- start
->nOffset
;
5176 str
= get_text( &pRun
->member
.run
, start
->nOffset
);
5178 while (srcChars
&& buflen
&& pNextRun
)
5180 int nFlags
= pRun
->member
.run
.nFlags
;
5182 if (bCRLF
&& nFlags
& MERF_ENDPARA
&& ~nFlags
& MERF_ENDCELL
)
5184 if (buflen
== 1) break;
5185 /* FIXME: native fails to reduce srcChars here for WM_GETTEXT or
5186 * EM_GETTEXTEX, however, this is done for copying text which
5187 * also uses this function. */
5188 srcChars
-= min(nLen
, srcChars
);
5192 nLen
= min(nLen
, srcChars
);
5196 nLen
= min(nLen
, buflen
);
5199 CopyMemory(buffer
, str
, sizeof(WCHAR
) * nLen
);
5204 pNextRun
= ME_FindItemFwd(pRun
, diRun
);
5206 nLen
= pRun
->member
.run
.len
;
5207 str
= get_text( &pRun
->member
.run
, 0 );
5209 /* append '\r' to the last paragraph. */
5210 if (pRun
->next
->type
== diTextEnd
&& bEOP
)
5216 return buffer
- pStart
;
5219 static BOOL
ME_RegisterEditorClass(HINSTANCE hInstance
)
5224 wcW
.style
= CS_DBLCLKS
| CS_HREDRAW
| CS_VREDRAW
| CS_GLOBALCLASS
;
5225 wcW
.lpfnWndProc
= RichEditWndProcW
;
5227 wcW
.cbWndExtra
= sizeof(ME_TextEditor
*);
5228 wcW
.hInstance
= NULL
; /* hInstance would register DLL-local class */
5230 wcW
.hCursor
= LoadCursorW(NULL
, (LPWSTR
)IDC_IBEAM
);
5231 wcW
.hbrBackground
= GetStockObject(NULL_BRUSH
);
5232 wcW
.lpszMenuName
= NULL
;
5234 if (is_version_nt())
5236 wcW
.lpszClassName
= RICHEDIT_CLASS20W
;
5237 if (!RegisterClassW(&wcW
)) return FALSE
;
5238 wcW
.lpszClassName
= MSFTEDIT_CLASS
;
5239 if (!RegisterClassW(&wcW
)) return FALSE
;
5243 /* WNDCLASSA/W have the same layout */
5244 wcW
.lpszClassName
= (LPCWSTR
)"RichEdit20W";
5245 if (!RegisterClassA((WNDCLASSA
*)&wcW
)) return FALSE
;
5246 wcW
.lpszClassName
= (LPCWSTR
)"RichEdit50W";
5247 if (!RegisterClassA((WNDCLASSA
*)&wcW
)) return FALSE
;
5250 wcA
.style
= CS_DBLCLKS
| CS_HREDRAW
| CS_VREDRAW
| CS_GLOBALCLASS
;
5251 wcA
.lpfnWndProc
= RichEditWndProcA
;
5253 wcA
.cbWndExtra
= sizeof(ME_TextEditor
*);
5254 wcA
.hInstance
= NULL
; /* hInstance would register DLL-local class */
5256 wcA
.hCursor
= LoadCursorW(NULL
, (LPWSTR
)IDC_IBEAM
);
5257 wcA
.hbrBackground
= GetStockObject(NULL_BRUSH
);
5258 wcA
.lpszMenuName
= NULL
;
5259 wcA
.lpszClassName
= RICHEDIT_CLASS20A
;
5260 if (!RegisterClassA(&wcA
)) return FALSE
;
5261 wcA
.lpszClassName
= "RichEdit50A";
5262 if (!RegisterClassA(&wcA
)) return FALSE
;
5267 static LRESULT WINAPI
REComboWndProc(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
) {
5268 /* FIXME: Not implemented */
5269 TRACE("hWnd %p msg %04x (%s) %08lx %08lx\n",
5270 hWnd
, msg
, get_msg_name(msg
), wParam
, lParam
);
5271 return DefWindowProcW(hWnd
, msg
, wParam
, lParam
);
5274 static LRESULT WINAPI
REListWndProc(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
) {
5275 /* FIXME: Not implemented */
5276 TRACE("hWnd %p msg %04x (%s) %08lx %08lx\n",
5277 hWnd
, msg
, get_msg_name(msg
), wParam
, lParam
);
5278 return DefWindowProcW(hWnd
, msg
, wParam
, lParam
);
5281 /******************************************************************
5282 * REExtendedRegisterClass (RICHED20.8)
5284 * FIXME undocumented
5285 * Need to check for errors and implement controls and callbacks
5287 LRESULT WINAPI
REExtendedRegisterClass(void)
5292 FIXME("semi stub\n");
5296 wcW
.hInstance
= NULL
;
5299 wcW
.hbrBackground
= (HBRUSH
)(COLOR_WINDOW
+1);
5300 wcW
.lpszMenuName
= NULL
;
5302 if (!ME_ListBoxRegistered
)
5304 wcW
.style
= CS_PARENTDC
| CS_DBLCLKS
| CS_GLOBALCLASS
;
5305 wcW
.lpfnWndProc
= REListWndProc
;
5306 wcW
.lpszClassName
= REListBox20W
;
5307 if (RegisterClassW(&wcW
)) ME_ListBoxRegistered
= TRUE
;
5310 if (!ME_ComboBoxRegistered
)
5312 wcW
.style
= CS_PARENTDC
| CS_DBLCLKS
| CS_GLOBALCLASS
| CS_VREDRAW
| CS_HREDRAW
;
5313 wcW
.lpfnWndProc
= REComboWndProc
;
5314 wcW
.lpszClassName
= REComboBox20W
;
5315 if (RegisterClassW(&wcW
)) ME_ComboBoxRegistered
= TRUE
;
5319 if (ME_ListBoxRegistered
)
5321 if (ME_ComboBoxRegistered
)
5327 static int __cdecl
wchar_comp( const void *key
, const void *elem
)
5329 return *(const WCHAR
*)key
- *(const WCHAR
*)elem
;
5332 /* neutral characters end the url if the next non-neutral character is a space character,
5333 otherwise they are included in the url. */
5334 static BOOL
isurlneutral( WCHAR c
)
5336 /* NB this list is sorted */
5337 static const WCHAR neutral_chars
[] = {'!','\"','\'','(',')',',','-','.',':',';','<','>','?','[',']','{','}'};
5339 /* Some shortcuts */
5340 if (isalnum( c
)) return FALSE
;
5341 if (c
> neutral_chars
[ARRAY_SIZE( neutral_chars
) - 1]) return FALSE
;
5343 return !!bsearch( &c
, neutral_chars
, ARRAY_SIZE( neutral_chars
), sizeof(c
), wchar_comp
);
5347 * This proc takes a selection, and scans it forward in order to select the span
5348 * of a possible URL candidate. A possible URL candidate must start with isalnum
5349 * or one of the following special characters: *|/\+%#@ and must consist entirely
5350 * of the characters allowed to start the URL, plus : (colon) which may occur
5351 * at most once, and not at either end.
5353 static BOOL
ME_FindNextURLCandidate(ME_TextEditor
*editor
,
5354 const ME_Cursor
*start
,
5356 ME_Cursor
*candidate_min
,
5357 ME_Cursor
*candidate_max
)
5359 ME_Cursor cursor
= *start
, neutral_end
, space_end
;
5360 BOOL candidateStarted
= FALSE
, quoted
= FALSE
;
5365 WCHAR
*str
= get_text( &cursor
.pRun
->member
.run
, 0 );
5366 int run_len
= cursor
.pRun
->member
.run
.len
;
5368 nChars
-= run_len
- cursor
.nOffset
;
5370 /* Find start of candidate */
5371 if (!candidateStarted
)
5373 while (cursor
.nOffset
< run_len
)
5375 c
= str
[cursor
.nOffset
];
5376 if (!iswspace( c
) && !isurlneutral( c
))
5378 *candidate_min
= cursor
;
5379 candidateStarted
= TRUE
;
5380 neutral_end
.pPara
= NULL
;
5381 space_end
.pPara
= NULL
;
5385 quoted
= (c
== '<');
5390 /* Find end of candidate */
5391 if (candidateStarted
)
5393 while (cursor
.nOffset
< run_len
)
5395 c
= str
[cursor
.nOffset
];
5398 if (quoted
&& c
!= '\r')
5400 if (!space_end
.pPara
)
5402 if (neutral_end
.pPara
)
5403 space_end
= neutral_end
;
5411 else if (isurlneutral( c
))
5413 if (quoted
&& c
== '>')
5415 neutral_end
.pPara
= NULL
;
5416 space_end
.pPara
= NULL
;
5419 if (!neutral_end
.pPara
)
5420 neutral_end
= cursor
;
5423 neutral_end
.pPara
= NULL
;
5430 if (!ME_NextRun(&cursor
.pPara
, &cursor
.pRun
, TRUE
))
5435 if (candidateStarted
)
5437 if (space_end
.pPara
)
5438 *candidate_max
= space_end
;
5439 else if (neutral_end
.pPara
)
5440 *candidate_max
= neutral_end
;
5442 *candidate_max
= cursor
;
5445 *candidate_max
= *candidate_min
= cursor
;
5450 * This proc evaluates the selection and returns TRUE if it can be considered an URL
5452 static BOOL
ME_IsCandidateAnURL(ME_TextEditor
*editor
, const ME_Cursor
*start
, int nChars
)
5454 #define MAX_PREFIX_LEN 9
5456 const WCHAR text
[MAX_PREFIX_LEN
];
5459 {{'p','r','o','s','p','e','r','o',':'}, 9},
5460 {{'t','e','l','n','e','t',':'}, 7},
5461 {{'g','o','p','h','e','r',':'}, 7},
5462 {{'m','a','i','l','t','o',':'}, 7},
5463 {{'h','t','t','p','s',':'}, 6},
5464 {{'f','i','l','e',':'}, 5},
5465 {{'n','e','w','s',':'}, 5},
5466 {{'w','a','i','s',':'}, 5},
5467 {{'n','n','t','p',':'}, 5},
5468 {{'h','t','t','p',':'}, 5},
5469 {{'w','w','w','.'}, 4},
5470 {{'f','t','p',':'}, 4},
5472 WCHAR bufferW
[MAX_PREFIX_LEN
+ 1];
5475 ME_GetTextW(editor
, bufferW
, MAX_PREFIX_LEN
, start
, nChars
, FALSE
, FALSE
);
5476 for (i
= 0; i
< ARRAY_SIZE(prefixes
); i
++)
5478 if (nChars
< prefixes
[i
].length
) continue;
5479 if (!memcmp(prefixes
[i
].text
, bufferW
, prefixes
[i
].length
* sizeof(WCHAR
)))
5483 #undef MAX_PREFIX_LEN
5487 * This proc walks through the indicated selection and evaluates whether each
5488 * section identified by ME_FindNextURLCandidate and in-between sections have
5489 * their proper CFE_LINK attributes set or unset. If the CFE_LINK attribute is
5490 * not what it is supposed to be, this proc sets or unsets it as appropriate.
5492 * Since this function can cause runs to be split, do not depend on the value
5493 * of the start cursor at the end of the function.
5495 * nChars may be set to INT_MAX to update to the end of the text.
5497 * Returns TRUE if at least one section was modified.
5499 static BOOL
ME_UpdateLinkAttribute(ME_TextEditor
*editor
, ME_Cursor
*start
, int nChars
)
5501 BOOL modified
= FALSE
;
5502 ME_Cursor startCur
= *start
;
5504 if (!editor
->AutoURLDetect_bEnable
) return FALSE
;
5509 ME_Cursor candidateStart
, candidateEnd
;
5511 if (ME_FindNextURLCandidate(editor
, &startCur
, nChars
,
5512 &candidateStart
, &candidateEnd
))
5514 /* Section before candidate is not an URL */
5515 int cMin
= ME_GetCursorOfs(&candidateStart
);
5516 int cMax
= ME_GetCursorOfs(&candidateEnd
);
5518 if (!ME_IsCandidateAnURL(editor
, &candidateStart
, cMax
- cMin
))
5519 candidateStart
= candidateEnd
;
5520 nChars
-= cMax
- ME_GetCursorOfs(&startCur
);
5524 /* No more candidates until end of selection */
5528 if (startCur
.pRun
!= candidateStart
.pRun
||
5529 startCur
.nOffset
!= candidateStart
.nOffset
)
5531 /* CFE_LINK effect should be consistently unset */
5532 link
.cbSize
= sizeof(link
);
5533 ME_GetCharFormat(editor
, &startCur
, &candidateStart
, &link
);
5534 if (!(link
.dwMask
& CFM_LINK
) || (link
.dwEffects
& CFE_LINK
))
5536 /* CFE_LINK must be unset from this range */
5537 memset(&link
, 0, sizeof(CHARFORMAT2W
));
5538 link
.cbSize
= sizeof(link
);
5539 link
.dwMask
= CFM_LINK
;
5541 ME_SetCharFormat(editor
, &startCur
, &candidateStart
, &link
);
5542 /* Update candidateEnd since setting character formats may split
5543 * runs, which can cause a cursor to be at an invalid offset within
5545 while (candidateEnd
.nOffset
>= candidateEnd
.pRun
->member
.run
.len
)
5547 candidateEnd
.nOffset
-= candidateEnd
.pRun
->member
.run
.len
;
5548 candidateEnd
.pRun
= ME_FindItemFwd(candidateEnd
.pRun
, diRun
);
5553 if (candidateStart
.pRun
!= candidateEnd
.pRun
||
5554 candidateStart
.nOffset
!= candidateEnd
.nOffset
)
5556 /* CFE_LINK effect should be consistently set */
5557 link
.cbSize
= sizeof(link
);
5558 ME_GetCharFormat(editor
, &candidateStart
, &candidateEnd
, &link
);
5559 if (!(link
.dwMask
& CFM_LINK
) || !(link
.dwEffects
& CFE_LINK
))
5561 /* CFE_LINK must be set on this range */
5562 memset(&link
, 0, sizeof(CHARFORMAT2W
));
5563 link
.cbSize
= sizeof(link
);
5564 link
.dwMask
= CFM_LINK
;
5565 link
.dwEffects
= CFE_LINK
;
5566 ME_SetCharFormat(editor
, &candidateStart
, &candidateEnd
, &link
);
5570 startCur
= candidateEnd
;
5571 } while (nChars
> 0);