2 * RichEdit - functions dealing with editor object
4 * Copyright 2004 by Krzysztof Foltman
5 * Copyright 2005 by Cihan Altinay
6 * Copyright 2005 by Phil Krylov
7 * Copyright 2008 Eric Pouech
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
25 API implementation status:
27 Messages (ANSI versions not done yet)
28 + EM_AUTOURLDETECT 2.0
39 + EM_FINDTEXT (only FR_DOWN flag implemented)
40 + EM_FINDTEXTEX (only FR_DOWN flag implemented)
44 + EM_GETAUTOURLDETECT 2.0
45 - EM_GETBIDIOPTIONS 3.0
46 - EM_GETCHARFORMAT (partly done)
49 + EM_GETFIRSTVISIBLELINE (can be optimized if needed)
50 - EM_GETIMECOLOR 1.0asian
51 - EM_GETIMECOMPMODE 2.0
52 - EM_GETIMEOPTIONS 1.0asian
54 - EM_GETLANGOPTIONS 2.0
57 + EM_GETLINECOUNT returns number of rows, not of paragraphs
62 + EM_GETPASSWORDCHAR 2.0
63 - EM_GETPUNCTUATION 1.0asian
67 + EM_GETSELTEXT (ANSI&Unicode)
71 + EM_GETTEXTLENGTHEX (GTL_PRECISE unimplemented)
73 ? + EM_GETTEXTRANGE (ANSI&Unicode)
74 - EM_GETTYPOGRAPHYOPTIONS 3.0
77 - EM_GETWORDBREAKPROCEX
78 - EM_GETWORDWRAPMODE 1.0asian
81 + EM_LIMITTEXT (Also called EM_SETLIMITTEXT)
90 + EM_REPLACESEL (proper style?) ANSI&Unicode
94 - EM_SETBIDIOPTIONS 3.0
96 + EM_SETCHARFORMAT (partly done, no ANSI)
98 + EM_SETEVENTMASK (few notifications supported)
100 - EM_SETIMECOLOR 1.0asian
101 - EM_SETIMEOPTIONS 1.0asian
103 - EM_SETLANGOPTIONS 2.0
106 + EM_SETMODIFY (not sure if implementation is correct)
108 + EM_SETOPTIONS (partially implemented)
111 + EM_SETPASSWORDCHAR 2.0
112 - EM_SETPUNCTUATION 1.0asian
113 + EM_SETREADONLY no beep on modification attempt
115 + EM_SETRECTNP (EM_SETRECT without repainting)
117 + EM_SETSCROLLPOS 3.0
119 - EM_SETTARGETDEVICE (partial)
120 + EM_SETTEXTEX 3.0 (proper style?)
122 - EM_SETTYPOGRAPHYOPTIONS 3.0
123 + EM_SETUNDOLIMIT 2.0
124 + EM_SETWORDBREAKPROC (used only for word movement at the moment)
125 - EM_SETWORDBREAKPROCEX
126 - EM_SETWORDWRAPMODE 1.0asian
128 + EM_SHOWSCROLLBAR 2.0
129 + EM_STOPGROUPTYPING 2.0
137 + WM_GETDLGCODE (the current implementation is incomplete)
138 + WM_GETTEXT (ANSI&Unicode)
139 + WM_GETTEXTLENGTH (ANSI version sucks)
143 + WM_SETTEXT (resets undo stack !) (proper style?) ANSI&Unicode
144 + WM_STYLECHANGING (seems to do nothing)
145 + WM_STYLECHANGED (seems to do nothing)
151 * EN_CHANGE (sent from the wrong place)
168 * EN_UPDATE (sent from the wrong place)
176 + ES_DISABLENOSCROLL (scrollbar is always visible)
177 - ES_EX_NOCALLOLEINIT
181 - ES_READONLY (I'm not sure if beeping is the proper behaviour)
187 - ES_WANTRETURN (don't know how to do WM_GETDLGCODE part)
194 * RICHED20 TODO (incomplete):
196 * - messages/styles/notifications listed above
197 * - add remaining CHARFORMAT/PARAFORMAT fields
198 * - right/center align should strip spaces from the beginning
199 * - pictures/OLE objects (not just smiling faces that lack API support ;-) )
200 * - COM interface (looks like a major pain in the TODO list)
201 * - calculate heights of pictures (half-done)
202 * - hysteresis during wrapping (related to scrollbars appearing/disappearing)
204 * - how to implement EM_FORMATRANGE and EM_DISPLAYBAND ? (Mission Impossible)
205 * - italic caret with italic fonts
207 * - most notifications aren't sent at all (the most important ones are)
208 * - when should EN_SELCHANGE be sent after text change ? (before/after EN_UPDATE?)
209 * - WM_SETTEXT may use wrong style (but I'm 80% sure it's OK)
210 * - EM_GETCHARFORMAT with SCF_SELECTION may not behave 100% like in original (but very close)
211 * - full justification
214 * - ListBox & ComboBox not implemented
216 * Bugs that are probably fixed, but not so easy to verify:
217 * - EN_UPDATE/EN_CHANGE are handled very incorrectly (should be OK now)
218 * - undo for ME_JoinParagraphs doesn't store paragraph format ? (it does)
219 * - check/fix artificial EOL logic (bCursorAtEnd, hardly logical)
220 * - caret shouldn't be displayed when selection isn't empty
221 * - check refcounting in style management functions (looks perfect now, but no bugs is suspicious)
222 * - undo for setting default format (done, might be buggy)
223 * - styles might be not released properly (looks like they work like charm, but who knows?
227 #define NONAMELESSUNION
232 #define NO_SHLWAPI_STREAM
238 #define STACK_SIZE_DEFAULT 100
239 #define STACK_SIZE_MAX 1000
241 #define TEXT_LIMIT_DEFAULT 32767
243 WINE_DEFAULT_DEBUG_CHANNEL(richedit
);
245 static BOOL
ME_RegisterEditorClass(HINSTANCE
);
246 static BOOL
ME_UpdateLinkAttribute(ME_TextEditor
*editor
, ME_Cursor
*start
, int nChars
);
248 static const WCHAR REListBox20W
[] = {'R','E','L','i','s','t','B','o','x','2','0','W', 0};
249 static const WCHAR REComboBox20W
[] = {'R','E','C','o','m','b','o','B','o','x','2','0','W', 0};
250 static HCURSOR hLeft
;
252 BOOL me_debug
= FALSE
;
253 HANDLE me_heap
= NULL
;
255 static BOOL ME_ListBoxRegistered
= FALSE
;
256 static BOOL ME_ComboBoxRegistered
= FALSE
;
258 static inline BOOL
is_version_nt(void)
260 return !(GetVersion() & 0x80000000);
263 static ME_TextBuffer
*ME_MakeText(void) {
264 ME_TextBuffer
*buf
= heap_alloc(sizeof(*buf
));
265 ME_DisplayItem
*p1
= ME_MakeDI(diTextStart
);
266 ME_DisplayItem
*p2
= ME_MakeDI(diTextEnd
);
272 p1
->member
.para
.next_para
= p2
;
273 p2
->member
.para
.prev_para
= p1
;
274 p2
->member
.para
.nCharOfs
= 0;
278 buf
->pCharStyle
= NULL
;
284 static LRESULT
ME_StreamInText(ME_TextEditor
*editor
, DWORD dwFormat
, ME_InStream
*stream
, ME_Style
*style
)
287 LRESULT total_bytes_read
= 0;
288 BOOL is_read
= FALSE
;
289 DWORD cp
= CP_ACP
, copy
= 0;
290 char conv_buf
[4 + STREAMIN_BUFFER_SIZE
]; /* up to 4 additional UTF-8 bytes */
292 static const char bom_utf8
[] = {0xEF, 0xBB, 0xBF};
294 TRACE("%08x %p\n", dwFormat
, stream
);
298 WCHAR wszText
[STREAMIN_BUFFER_SIZE
+1];
302 ME_StreamInFill(stream
);
303 if (stream
->editstream
->dwError
)
307 total_bytes_read
+= stream
->dwSize
;
310 if (!(dwFormat
& SF_UNICODE
))
312 char * buf
= stream
->buffer
;
313 DWORD size
= stream
->dwSize
, end
;
318 if (stream
->dwSize
>= 3 && !memcmp(stream
->buffer
, bom_utf8
, 3))
330 memcpy(conv_buf
+ copy
, buf
, size
);
335 while ((buf
[end
-1] & 0xC0) == 0x80)
338 --total_bytes_read
; /* strange, but seems to match windows */
340 if (buf
[end
-1] & 0x80)
343 if ((buf
[end
-1] & 0xE0) == 0xC0)
345 if ((buf
[end
-1] & 0xF0) == 0xE0)
347 if ((buf
[end
-1] & 0xF8) == 0xF0)
350 if (size
- end
>= need
)
352 /* we have enough bytes for this sequence */
357 /* need more bytes, so don't transcode this sequence */
365 nWideChars
= MultiByteToWideChar(cp
, 0, buf
, end
, wszText
, STREAMIN_BUFFER_SIZE
);
372 memcpy(conv_buf
, buf
+ end
, size
- end
);
379 nWideChars
= stream
->dwSize
>> 1;
380 pText
= (WCHAR
*)stream
->buffer
;
383 ME_InsertTextFromCursor(editor
, 0, pText
, nWideChars
, style
);
384 if (stream
->dwSize
== 0)
388 return total_bytes_read
;
391 static void ME_ApplyBorderProperties(RTF_Info
*info
,
392 ME_BorderRect
*borderRect
,
393 RTFBorder
*borderDef
)
396 ME_Border
*pBorders
[] = {&borderRect
->top
,
400 for (i
= 0; i
< 4; i
++)
402 RTFColor
*colorDef
= info
->colorList
;
403 pBorders
[i
]->width
= borderDef
[i
].width
;
404 colorNum
= borderDef
[i
].color
;
405 while (colorDef
&& colorDef
->rtfCNum
!= colorNum
)
406 colorDef
= colorDef
->rtfNextColor
;
408 pBorders
[i
]->colorRef
= RGB(
409 colorDef
->rtfCRed
>= 0 ? colorDef
->rtfCRed
: 0,
410 colorDef
->rtfCGreen
>= 0 ? colorDef
->rtfCGreen
: 0,
411 colorDef
->rtfCBlue
>= 0 ? colorDef
->rtfCBlue
: 0);
413 pBorders
[i
]->colorRef
= RGB(0, 0, 0);
417 void ME_RTFCharAttrHook(RTF_Info
*info
)
420 fmt
.cbSize
= sizeof(fmt
);
424 switch(info
->rtfMinor
)
427 /* FIXME add more flags once they're implemented */
428 fmt
.dwMask
= CFM_BOLD
| CFM_ITALIC
| CFM_UNDERLINE
| CFM_UNDERLINETYPE
| CFM_STRIKEOUT
|
429 CFM_COLOR
| CFM_BACKCOLOR
| CFM_SIZE
| CFM_WEIGHT
;
430 fmt
.dwEffects
= CFE_AUTOCOLOR
| CFE_AUTOBACKCOLOR
;
431 fmt
.yHeight
= 12*20; /* 12pt */
432 fmt
.wWeight
= FW_NORMAL
;
433 fmt
.bUnderlineType
= CFU_UNDERLINE
;
436 fmt
.dwMask
= CFM_BOLD
| CFM_WEIGHT
;
437 fmt
.dwEffects
= info
->rtfParam
? CFE_BOLD
: 0;
438 fmt
.wWeight
= info
->rtfParam
? FW_BOLD
: FW_NORMAL
;
441 fmt
.dwMask
= CFM_ITALIC
;
442 fmt
.dwEffects
= info
->rtfParam
? fmt
.dwMask
: 0;
445 fmt
.dwMask
= CFM_UNDERLINETYPE
| CFM_UNDERLINE
;
446 fmt
.bUnderlineType
= CFU_UNDERLINE
;
447 fmt
.dwEffects
= info
->rtfParam
? CFE_UNDERLINE
: 0;
449 case rtfDotUnderline
:
450 fmt
.dwMask
= CFM_UNDERLINETYPE
| CFM_UNDERLINE
;
451 fmt
.bUnderlineType
= CFU_UNDERLINEDOTTED
;
452 fmt
.dwEffects
= info
->rtfParam
? CFE_UNDERLINE
: 0;
455 fmt
.dwMask
= CFM_UNDERLINETYPE
| CFM_UNDERLINE
;
456 fmt
.bUnderlineType
= CFU_UNDERLINEDOUBLE
;
457 fmt
.dwEffects
= info
->rtfParam
? CFE_UNDERLINE
: 0;
459 case rtfWordUnderline
:
460 fmt
.dwMask
= CFM_UNDERLINETYPE
| CFM_UNDERLINE
;
461 fmt
.bUnderlineType
= CFU_UNDERLINEWORD
;
462 fmt
.dwEffects
= info
->rtfParam
? CFE_UNDERLINE
: 0;
465 fmt
.dwMask
= CFM_UNDERLINE
;
469 fmt
.dwMask
= CFM_STRIKEOUT
;
470 fmt
.dwEffects
= info
->rtfParam
? fmt
.dwMask
: 0;
474 case rtfSubScrShrink
:
475 case rtfSuperScrShrink
:
477 fmt
.dwMask
= CFM_SUBSCRIPT
|CFM_SUPERSCRIPT
;
478 if (info
->rtfMinor
== rtfSubScrShrink
) fmt
.dwEffects
= CFE_SUBSCRIPT
;
479 if (info
->rtfMinor
== rtfSuperScrShrink
) fmt
.dwEffects
= CFE_SUPERSCRIPT
;
480 if (info
->rtfMinor
== rtfNoSuperSub
) fmt
.dwEffects
= 0;
483 fmt
.dwMask
= CFM_HIDDEN
;
484 fmt
.dwEffects
= info
->rtfParam
? fmt
.dwMask
: 0;
487 fmt
.dwMask
= CFM_BACKCOLOR
;
489 if (info
->rtfParam
== 0)
490 fmt
.dwEffects
= CFE_AUTOBACKCOLOR
;
491 else if (info
->rtfParam
!= rtfNoParam
)
493 RTFColor
*c
= RTFGetColor(info
, info
->rtfParam
);
494 if (c
&& c
->rtfCBlue
>= 0)
495 fmt
.crBackColor
= (c
->rtfCBlue
<<16)|(c
->rtfCGreen
<<8)|(c
->rtfCRed
);
497 fmt
.dwEffects
= CFE_AUTOBACKCOLOR
;
501 fmt
.dwMask
= CFM_COLOR
;
503 if (info
->rtfParam
== 0)
504 fmt
.dwEffects
= CFE_AUTOCOLOR
;
505 else if (info
->rtfParam
!= rtfNoParam
)
507 RTFColor
*c
= RTFGetColor(info
, info
->rtfParam
);
508 if (c
&& c
->rtfCBlue
>= 0)
509 fmt
.crTextColor
= (c
->rtfCBlue
<<16)|(c
->rtfCGreen
<<8)|(c
->rtfCRed
);
511 fmt
.dwEffects
= CFE_AUTOCOLOR
;
516 if (info
->rtfParam
!= rtfNoParam
)
518 RTFFont
*f
= RTFGetFont(info
, info
->rtfParam
);
521 MultiByteToWideChar(CP_ACP
, 0, f
->rtfFName
, -1, fmt
.szFaceName
, ARRAY_SIZE(fmt
.szFaceName
));
522 fmt
.szFaceName
[ARRAY_SIZE(fmt
.szFaceName
)-1] = '\0';
523 fmt
.bCharSet
= f
->rtfFCharSet
;
524 fmt
.dwMask
= CFM_FACE
| CFM_CHARSET
;
525 fmt
.bPitchAndFamily
= f
->rtfFPitch
| (f
->rtfFFamily
<< 4);
530 fmt
.dwMask
= CFM_SIZE
;
531 if (info
->rtfParam
!= rtfNoParam
)
532 fmt
.yHeight
= info
->rtfParam
*10;
537 RTFFlushOutputBuffer(info
);
538 /* FIXME too slow ? how come ? */
539 style2
= ME_ApplyStyle(info
->editor
, info
->style
, &fmt
);
540 ME_ReleaseStyle(info
->style
);
541 info
->style
= style2
;
542 info
->styleChanged
= TRUE
;
546 /* FIXME this function doesn't get any information about context of the RTF tag, which is very bad,
547 the same tags mean different things in different contexts */
548 void ME_RTFParAttrHook(RTF_Info
*info
)
550 switch(info
->rtfMinor
)
552 case rtfParDef
: /* restores default paragraph attributes */
553 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
554 info
->borderType
= RTFBorderParaLeft
;
555 else /* v1.0 - 3.0 */
556 info
->borderType
= RTFBorderParaTop
;
557 info
->fmt
.dwMask
= PFM_ALIGNMENT
| PFM_BORDER
| PFM_LINESPACING
| PFM_TABSTOPS
|
558 PFM_OFFSET
| PFM_RIGHTINDENT
| PFM_SPACEAFTER
| PFM_SPACEBEFORE
|
559 PFM_STARTINDENT
| PFM_RTLPARA
| PFM_NUMBERING
| PFM_NUMBERINGSTART
|
560 PFM_NUMBERINGSTYLE
| PFM_NUMBERINGTAB
;
562 info
->fmt
.wAlignment
= PFA_LEFT
;
563 info
->fmt
.cTabCount
= 0;
564 info
->fmt
.dxOffset
= info
->fmt
.dxStartIndent
= info
->fmt
.dxRightIndent
= 0;
565 info
->fmt
.wBorderWidth
= info
->fmt
.wBorders
= 0;
566 info
->fmt
.wBorderSpace
= 0;
567 info
->fmt
.bLineSpacingRule
= 0;
568 info
->fmt
.dySpaceBefore
= info
->fmt
.dySpaceAfter
= 0;
569 info
->fmt
.dyLineSpacing
= 0;
570 info
->fmt
.wEffects
&= ~PFE_RTLPARA
;
571 info
->fmt
.wNumbering
= 0;
572 info
->fmt
.wNumberingStart
= 0;
573 info
->fmt
.wNumberingStyle
= 0;
574 info
->fmt
.wNumberingTab
= 0;
576 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
578 if (info
->tableDef
&& info
->tableDef
->tableRowStart
&&
579 info
->tableDef
->tableRowStart
->member
.para
.nFlags
& MEPF_ROWEND
)
582 ME_DisplayItem
*para
;
583 /* We are just after a table row. */
584 RTFFlushOutputBuffer(info
);
585 cursor
= info
->editor
->pCursors
[0];
587 if (para
== info
->tableDef
->tableRowStart
->member
.para
.next_para
588 && !cursor
.nOffset
&& !cursor
.pRun
->member
.run
.nCharOfs
)
590 /* Since the table row end, no text has been inserted, and the \intbl
591 * control word has not be used. We can confirm that we are not in a
594 info
->tableDef
->tableRowStart
= NULL
;
595 info
->canInheritInTbl
= FALSE
;
598 } else { /* v1.0 - v3.0 */
599 info
->fmt
.dwMask
|= PFM_TABLE
;
600 info
->fmt
.wEffects
&= ~PFE_TABLE
;
604 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
606 while (info
->rtfParam
> info
->nestingLevel
) {
607 RTFTable
*tableDef
= heap_alloc_zero(sizeof(*tableDef
));
608 tableDef
->parent
= info
->tableDef
;
609 info
->tableDef
= tableDef
;
611 RTFFlushOutputBuffer(info
);
612 if (tableDef
->tableRowStart
&&
613 tableDef
->tableRowStart
->member
.para
.nFlags
& MEPF_ROWEND
)
615 ME_DisplayItem
*para
= tableDef
->tableRowStart
;
616 para
= para
->member
.para
.next_para
;
617 para
= ME_InsertTableRowStartAtParagraph(info
->editor
, para
);
618 tableDef
->tableRowStart
= para
;
622 cursor
= info
->editor
->pCursors
[0];
623 if (cursor
.nOffset
|| cursor
.pRun
->member
.run
.nCharOfs
)
624 ME_InsertTextFromCursor(info
->editor
, 0, &endl
, 1, info
->style
);
625 tableDef
->tableRowStart
= ME_InsertTableRowStartFromCursor(info
->editor
);
628 info
->nestingLevel
++;
630 info
->canInheritInTbl
= FALSE
;
635 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
637 if (info
->nestingLevel
< 1)
641 info
->tableDef
= heap_alloc_zero(sizeof(*info
->tableDef
));
642 tableDef
= info
->tableDef
;
643 RTFFlushOutputBuffer(info
);
644 if (tableDef
->tableRowStart
&&
645 tableDef
->tableRowStart
->member
.para
.nFlags
& MEPF_ROWEND
)
647 ME_DisplayItem
*para
= tableDef
->tableRowStart
;
648 para
= para
->member
.para
.next_para
;
649 para
= ME_InsertTableRowStartAtParagraph(info
->editor
, para
);
650 tableDef
->tableRowStart
= para
;
654 cursor
= info
->editor
->pCursors
[0];
655 if (cursor
.nOffset
|| cursor
.pRun
->member
.run
.nCharOfs
)
656 ME_InsertTextFromCursor(info
->editor
, 0, &endl
, 1, info
->style
);
657 tableDef
->tableRowStart
= ME_InsertTableRowStartFromCursor(info
->editor
);
659 info
->nestingLevel
= 1;
660 info
->canInheritInTbl
= TRUE
;
663 } else { /* v1.0 - v3.0 */
664 info
->fmt
.dwMask
|= PFM_TABLE
;
665 info
->fmt
.wEffects
|= PFE_TABLE
;
671 if ((info
->fmt
.dwMask
& (PFM_STARTINDENT
| PFM_OFFSET
)) != (PFM_STARTINDENT
| PFM_OFFSET
))
674 fmt
.cbSize
= sizeof(fmt
);
675 ME_GetSelectionParaFormat(info
->editor
, &fmt
);
676 info
->fmt
.dwMask
|= PFM_STARTINDENT
| PFM_OFFSET
;
677 info
->fmt
.dxStartIndent
= fmt
.dxStartIndent
;
678 info
->fmt
.dxOffset
= fmt
.dxOffset
;
680 if (info
->rtfMinor
== rtfFirstIndent
)
682 info
->fmt
.dxStartIndent
+= info
->fmt
.dxOffset
+ info
->rtfParam
;
683 info
->fmt
.dxOffset
= -info
->rtfParam
;
686 info
->fmt
.dxStartIndent
= info
->rtfParam
- info
->fmt
.dxOffset
;
689 info
->fmt
.dwMask
|= PFM_RIGHTINDENT
;
690 info
->fmt
.dxRightIndent
= info
->rtfParam
;
694 info
->fmt
.dwMask
|= PFM_ALIGNMENT
;
695 info
->fmt
.wAlignment
= PFA_LEFT
;
698 info
->fmt
.dwMask
|= PFM_ALIGNMENT
;
699 info
->fmt
.wAlignment
= PFA_RIGHT
;
702 info
->fmt
.dwMask
|= PFM_ALIGNMENT
;
703 info
->fmt
.wAlignment
= PFA_CENTER
;
706 if (!(info
->fmt
.dwMask
& PFM_TABSTOPS
))
709 fmt
.cbSize
= sizeof(fmt
);
710 ME_GetSelectionParaFormat(info
->editor
, &fmt
);
711 memcpy(info
->fmt
.rgxTabs
, fmt
.rgxTabs
,
712 fmt
.cTabCount
* sizeof(fmt
.rgxTabs
[0]));
713 info
->fmt
.cTabCount
= fmt
.cTabCount
;
714 info
->fmt
.dwMask
|= PFM_TABSTOPS
;
716 if (info
->fmt
.cTabCount
< MAX_TAB_STOPS
&& info
->rtfParam
< 0x1000000)
717 info
->fmt
.rgxTabs
[info
->fmt
.cTabCount
++] = info
->rtfParam
;
720 info
->fmt
.dwMask
|= PFM_KEEP
;
721 info
->fmt
.wEffects
|= PFE_KEEP
;
723 case rtfNoWidowControl
:
724 info
->fmt
.dwMask
|= PFM_NOWIDOWCONTROL
;
725 info
->fmt
.wEffects
|= PFE_NOWIDOWCONTROL
;
728 info
->fmt
.dwMask
|= PFM_KEEPNEXT
;
729 info
->fmt
.wEffects
|= PFE_KEEPNEXT
;
732 info
->fmt
.dwMask
|= PFM_SPACEAFTER
;
733 info
->fmt
.dySpaceAfter
= info
->rtfParam
;
736 info
->fmt
.dwMask
|= PFM_SPACEBEFORE
;
737 info
->fmt
.dySpaceBefore
= info
->rtfParam
;
739 case rtfSpaceBetween
:
740 info
->fmt
.dwMask
|= PFM_LINESPACING
;
741 if ((int)info
->rtfParam
> 0)
743 info
->fmt
.dyLineSpacing
= info
->rtfParam
;
744 info
->fmt
.bLineSpacingRule
= 3;
748 info
->fmt
.dyLineSpacing
= info
->rtfParam
;
749 info
->fmt
.bLineSpacingRule
= 4;
752 case rtfSpaceMultiply
:
753 info
->fmt
.dwMask
|= PFM_LINESPACING
;
754 info
->fmt
.dyLineSpacing
= info
->rtfParam
* 20;
755 info
->fmt
.bLineSpacingRule
= 5;
758 info
->fmt
.dwMask
|= PFM_NUMBERING
;
759 info
->fmt
.wNumbering
= PFN_BULLET
;
762 info
->fmt
.dwMask
|= PFM_NUMBERING
;
763 info
->fmt
.wNumbering
= 2; /* FIXME: MSDN says it's not used ?? */
766 info
->borderType
= RTFBorderParaLeft
;
767 info
->fmt
.wBorders
|= 1;
768 info
->fmt
.dwMask
|= PFM_BORDER
;
771 info
->borderType
= RTFBorderParaRight
;
772 info
->fmt
.wBorders
|= 2;
773 info
->fmt
.dwMask
|= PFM_BORDER
;
776 info
->borderType
= RTFBorderParaTop
;
777 info
->fmt
.wBorders
|= 4;
778 info
->fmt
.dwMask
|= PFM_BORDER
;
780 case rtfBorderBottom
:
781 info
->borderType
= RTFBorderParaBottom
;
782 info
->fmt
.wBorders
|= 8;
783 info
->fmt
.dwMask
|= PFM_BORDER
;
785 case rtfBorderSingle
:
786 info
->fmt
.wBorders
&= ~0x700;
787 info
->fmt
.wBorders
|= 1 << 8;
788 info
->fmt
.dwMask
|= PFM_BORDER
;
791 info
->fmt
.wBorders
&= ~0x700;
792 info
->fmt
.wBorders
|= 2 << 8;
793 info
->fmt
.dwMask
|= PFM_BORDER
;
795 case rtfBorderShadow
:
796 info
->fmt
.wBorders
&= ~0x700;
797 info
->fmt
.wBorders
|= 10 << 8;
798 info
->fmt
.dwMask
|= PFM_BORDER
;
800 case rtfBorderDouble
:
801 info
->fmt
.wBorders
&= ~0x700;
802 info
->fmt
.wBorders
|= 7 << 8;
803 info
->fmt
.dwMask
|= PFM_BORDER
;
806 info
->fmt
.wBorders
&= ~0x700;
807 info
->fmt
.wBorders
|= 11 << 8;
808 info
->fmt
.dwMask
|= PFM_BORDER
;
812 int borderSide
= info
->borderType
& RTFBorderSideMask
;
813 RTFTable
*tableDef
= info
->tableDef
;
814 if ((info
->borderType
& RTFBorderTypeMask
) == RTFBorderTypeCell
)
817 if (!tableDef
|| tableDef
->numCellsDefined
>= MAX_TABLE_CELLS
)
819 border
= &tableDef
->cells
[tableDef
->numCellsDefined
].border
[borderSide
];
820 border
->width
= info
->rtfParam
;
823 info
->fmt
.wBorderWidth
= info
->rtfParam
;
824 info
->fmt
.dwMask
|= PFM_BORDER
;
828 info
->fmt
.wBorderSpace
= info
->rtfParam
;
829 info
->fmt
.dwMask
|= PFM_BORDER
;
833 RTFTable
*tableDef
= info
->tableDef
;
834 int borderSide
= info
->borderType
& RTFBorderSideMask
;
835 int borderType
= info
->borderType
& RTFBorderTypeMask
;
838 case RTFBorderTypePara
:
839 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
841 /* v1.0 - 3.0 treat paragraph and row borders the same. */
842 case RTFBorderTypeRow
:
844 tableDef
->border
[borderSide
].color
= info
->rtfParam
;
847 case RTFBorderTypeCell
:
848 if (tableDef
&& tableDef
->numCellsDefined
< MAX_TABLE_CELLS
) {
849 tableDef
->cells
[tableDef
->numCellsDefined
].border
[borderSide
].color
= info
->rtfParam
;
856 info
->fmt
.dwMask
|= PFM_RTLPARA
;
857 info
->fmt
.wEffects
|= PFE_RTLPARA
;
860 info
->fmt
.dwMask
|= PFM_RTLPARA
;
861 info
->fmt
.wEffects
&= ~PFE_RTLPARA
;
866 void ME_RTFTblAttrHook(RTF_Info
*info
)
868 switch (info
->rtfMinor
)
872 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
873 info
->borderType
= 0; /* Not sure */
874 else /* v1.0 - 3.0 */
875 info
->borderType
= RTFBorderRowTop
;
876 if (!info
->tableDef
) {
877 info
->tableDef
= ME_MakeTableDef(info
->editor
);
879 ME_InitTableDef(info
->editor
, info
->tableDef
);
888 info
->tableDef
= ME_MakeTableDef(info
->editor
);
890 cellNum
= info
->tableDef
->numCellsDefined
;
891 if (cellNum
>= MAX_TABLE_CELLS
)
893 info
->tableDef
->cells
[cellNum
].rightBoundary
= info
->rtfParam
;
894 if (cellNum
< MAX_TAB_STOPS
) {
895 /* Tab stops were used to store cell positions before v4.1 but v4.1
896 * still seems to set the tabstops without using them. */
897 ME_DisplayItem
*para
= info
->editor
->pCursors
[0].pPara
;
898 PARAFORMAT2
*pFmt
= ¶
->member
.para
.fmt
;
899 pFmt
->rgxTabs
[cellNum
] &= ~0x00FFFFFF;
900 pFmt
->rgxTabs
[cellNum
] |= 0x00FFFFFF & info
->rtfParam
;
902 info
->tableDef
->numCellsDefined
++;
906 info
->borderType
= RTFBorderRowTop
;
909 info
->borderType
= RTFBorderRowLeft
;
911 case rtfRowBordBottom
:
912 info
->borderType
= RTFBorderRowBottom
;
914 case rtfRowBordRight
:
915 info
->borderType
= RTFBorderRowRight
;
918 info
->borderType
= RTFBorderCellTop
;
920 case rtfCellBordLeft
:
921 info
->borderType
= RTFBorderCellLeft
;
923 case rtfCellBordBottom
:
924 info
->borderType
= RTFBorderCellBottom
;
926 case rtfCellBordRight
:
927 info
->borderType
= RTFBorderCellRight
;
931 info
->tableDef
->gapH
= info
->rtfParam
;
935 info
->tableDef
->leftEdge
= info
->rtfParam
;
940 void ME_RTFSpecialCharHook(RTF_Info
*info
)
942 RTFTable
*tableDef
= info
->tableDef
;
943 switch (info
->rtfMinor
)
946 if (info
->editor
->bEmulateVersion10
) /* v1.0 - v3.0 */
948 /* else fall through since v4.1 treats rtfNestCell and rtfCell the same */
952 RTFFlushOutputBuffer(info
);
953 if (!info
->editor
->bEmulateVersion10
) { /* v4.1 */
954 if (tableDef
->tableRowStart
)
956 if (!info
->nestingLevel
&&
957 tableDef
->tableRowStart
->member
.para
.nFlags
& MEPF_ROWEND
)
959 ME_DisplayItem
*para
= tableDef
->tableRowStart
;
960 para
= para
->member
.para
.next_para
;
961 para
= ME_InsertTableRowStartAtParagraph(info
->editor
, para
);
962 tableDef
->tableRowStart
= para
;
963 info
->nestingLevel
= 1;
965 ME_InsertTableCellFromCursor(info
->editor
);
967 } else { /* v1.0 - v3.0 */
968 ME_DisplayItem
*para
= info
->editor
->pCursors
[0].pPara
;
969 PARAFORMAT2
*pFmt
= ¶
->member
.para
.fmt
;
970 if (pFmt
->dwMask
& PFM_TABLE
&& pFmt
->wEffects
& PFE_TABLE
&&
971 tableDef
->numCellsInserted
< tableDef
->numCellsDefined
)
974 ME_InsertTextFromCursor(info
->editor
, 0, &tab
, 1, info
->style
);
975 tableDef
->numCellsInserted
++;
980 if (info
->editor
->bEmulateVersion10
) /* v1.0 - v3.0 */
982 /* else fall through since v4.1 treats rtfNestRow and rtfRow the same */
985 ME_DisplayItem
*para
, *cell
, *run
;
990 RTFFlushOutputBuffer(info
);
991 if (!info
->editor
->bEmulateVersion10
) { /* v4.1 */
992 if (!tableDef
->tableRowStart
)
994 if (!info
->nestingLevel
&&
995 tableDef
->tableRowStart
->member
.para
.nFlags
& MEPF_ROWEND
)
997 para
= tableDef
->tableRowStart
;
998 para
= para
->member
.para
.next_para
;
999 para
= ME_InsertTableRowStartAtParagraph(info
->editor
, para
);
1000 tableDef
->tableRowStart
= para
;
1001 info
->nestingLevel
++;
1003 para
= tableDef
->tableRowStart
;
1004 cell
= ME_FindItemFwd(para
, diCell
);
1005 assert(cell
&& !cell
->member
.cell
.prev_cell
);
1006 if (tableDef
->numCellsDefined
< 1)
1008 /* 2000 twips appears to be the cell size that native richedit uses
1009 * when no cell sizes are specified. */
1010 const int defaultCellSize
= 2000;
1011 int nRightBoundary
= defaultCellSize
;
1012 cell
->member
.cell
.nRightBoundary
= nRightBoundary
;
1013 while (cell
->member
.cell
.next_cell
) {
1014 cell
= cell
->member
.cell
.next_cell
;
1015 nRightBoundary
+= defaultCellSize
;
1016 cell
->member
.cell
.nRightBoundary
= nRightBoundary
;
1018 para
= ME_InsertTableCellFromCursor(info
->editor
);
1019 cell
= para
->member
.para
.pCell
;
1020 cell
->member
.cell
.nRightBoundary
= nRightBoundary
;
1022 for (i
= 0; i
< tableDef
->numCellsDefined
; i
++)
1024 RTFCell
*cellDef
= &tableDef
->cells
[i
];
1025 cell
->member
.cell
.nRightBoundary
= cellDef
->rightBoundary
;
1026 ME_ApplyBorderProperties(info
, &cell
->member
.cell
.border
,
1028 cell
= cell
->member
.cell
.next_cell
;
1031 para
= ME_InsertTableCellFromCursor(info
->editor
);
1032 cell
= para
->member
.para
.pCell
;
1035 /* Cell for table row delimiter is empty */
1036 cell
->member
.cell
.nRightBoundary
= tableDef
->cells
[i
-1].rightBoundary
;
1039 run
= ME_FindItemFwd(cell
, diRun
);
1040 if (info
->editor
->pCursors
[0].pRun
!= run
||
1041 info
->editor
->pCursors
[0].nOffset
)
1044 /* Delete inserted cells that aren't defined. */
1045 info
->editor
->pCursors
[1].pRun
= run
;
1046 info
->editor
->pCursors
[1].pPara
= ME_GetParagraph(run
);
1047 info
->editor
->pCursors
[1].nOffset
= 0;
1048 nOfs
= ME_GetCursorOfs(&info
->editor
->pCursors
[1]);
1049 nChars
= ME_GetCursorOfs(&info
->editor
->pCursors
[0]) - nOfs
;
1050 ME_InternalDeleteText(info
->editor
, &info
->editor
->pCursors
[1],
1054 para
= ME_InsertTableRowEndFromCursor(info
->editor
);
1055 para
->member
.para
.fmt
.dxOffset
= abs(info
->tableDef
->gapH
);
1056 para
->member
.para
.fmt
.dxStartIndent
= info
->tableDef
->leftEdge
;
1057 ME_ApplyBorderProperties(info
, ¶
->member
.para
.border
,
1059 info
->nestingLevel
--;
1060 if (!info
->nestingLevel
)
1062 if (info
->canInheritInTbl
) {
1063 tableDef
->tableRowStart
= para
;
1065 while (info
->tableDef
) {
1066 tableDef
= info
->tableDef
;
1067 info
->tableDef
= tableDef
->parent
;
1068 heap_free(tableDef
);
1072 info
->tableDef
= tableDef
->parent
;
1073 heap_free(tableDef
);
1075 } else { /* v1.0 - v3.0 */
1077 ME_DisplayItem
*para
= info
->editor
->pCursors
[0].pPara
;
1078 PARAFORMAT2
*pFmt
= ¶
->member
.para
.fmt
;
1079 pFmt
->dxOffset
= info
->tableDef
->gapH
;
1080 pFmt
->dxStartIndent
= info
->tableDef
->leftEdge
;
1082 ME_ApplyBorderProperties(info
, ¶
->member
.para
.border
,
1084 while (tableDef
->numCellsInserted
< tableDef
->numCellsDefined
)
1087 ME_InsertTextFromCursor(info
->editor
, 0, &tab
, 1, info
->style
);
1088 tableDef
->numCellsInserted
++;
1090 pFmt
->cTabCount
= min(tableDef
->numCellsDefined
, MAX_TAB_STOPS
);
1091 if (!tableDef
->numCellsDefined
)
1092 pFmt
->wEffects
&= ~PFE_TABLE
;
1093 ME_InsertTextFromCursor(info
->editor
, 0, &endl
, 1, info
->style
);
1094 tableDef
->numCellsInserted
= 0;
1100 if (info
->editor
->bEmulateVersion10
) { /* v1.0 - 3.0 */
1101 ME_DisplayItem
*para
;
1103 RTFFlushOutputBuffer(info
);
1104 para
= info
->editor
->pCursors
[0].pPara
;
1105 pFmt
= ¶
->member
.para
.fmt
;
1106 if (pFmt
->dwMask
& PFM_TABLE
&& pFmt
->wEffects
& PFE_TABLE
)
1108 /* rtfPar is treated like a space within a table. */
1109 info
->rtfClass
= rtfText
;
1110 info
->rtfMajor
= ' ';
1112 else if (info
->rtfMinor
== rtfPar
&& tableDef
)
1113 tableDef
->numCellsInserted
= 0;
1119 static HRESULT
insert_static_object(ME_TextEditor
*editor
, HENHMETAFILE hemf
, HBITMAP hbmp
,
1122 LPOLEOBJECT lpObject
= NULL
;
1123 LPSTORAGE lpStorage
= NULL
;
1124 LPOLECLIENTSITE lpClientSite
= NULL
;
1125 LPDATAOBJECT lpDataObject
= NULL
;
1126 LPOLECACHE lpOleCache
= NULL
;
1127 LPRICHEDITOLE lpReOle
= NULL
;
1131 HRESULT hr
= E_FAIL
;
1136 stgm
.tymed
= TYMED_ENHMF
;
1137 stgm
.u
.hEnhMetaFile
= hemf
;
1138 fm
.cfFormat
= CF_ENHMETAFILE
;
1142 stgm
.tymed
= TYMED_GDI
;
1143 stgm
.u
.hBitmap
= hbmp
;
1144 fm
.cfFormat
= CF_BITMAP
;
1146 stgm
.pUnkForRelease
= NULL
;
1149 fm
.dwAspect
= DVASPECT_CONTENT
;
1151 fm
.tymed
= stgm
.tymed
;
1155 if (!CreateIRichEditOle(NULL
, editor
, (LPVOID
*)&editor
->reOle
))
1159 if (OleCreateDefaultHandler(&CLSID_NULL
, NULL
, &IID_IOleObject
, (void**)&lpObject
) == S_OK
&&
1160 IUnknown_QueryInterface(editor
->reOle
, &IID_IRichEditOle
, (void**)&lpReOle
) == S_OK
&&
1161 IRichEditOle_GetClientSite(lpReOle
, &lpClientSite
) == S_OK
&&
1162 IOleObject_SetClientSite(lpObject
, lpClientSite
) == S_OK
&&
1163 IOleObject_GetUserClassID(lpObject
, &clsid
) == S_OK
&&
1164 IOleObject_QueryInterface(lpObject
, &IID_IOleCache
, (void**)&lpOleCache
) == S_OK
&&
1165 IOleCache_Cache(lpOleCache
, &fm
, 0, &conn
) == S_OK
&&
1166 IOleObject_QueryInterface(lpObject
, &IID_IDataObject
, (void**)&lpDataObject
) == S_OK
&&
1167 IDataObject_SetData(lpDataObject
, &fm
, &stgm
, TRUE
) == S_OK
)
1171 reobject
.cbStruct
= sizeof(reobject
);
1172 reobject
.cp
= REO_CP_SELECTION
;
1173 reobject
.clsid
= clsid
;
1174 reobject
.poleobj
= lpObject
;
1175 reobject
.pstg
= lpStorage
;
1176 reobject
.polesite
= lpClientSite
;
1177 /* convert from twips to .01 mm */
1178 reobject
.sizel
.cx
= MulDiv(sz
->cx
, 254, 144);
1179 reobject
.sizel
.cy
= MulDiv(sz
->cy
, 254, 144);
1180 reobject
.dvaspect
= DVASPECT_CONTENT
;
1181 reobject
.dwFlags
= 0; /* FIXME */
1182 reobject
.dwUser
= 0;
1184 ME_InsertOLEFromCursor(editor
, &reobject
, 0);
1188 if (lpObject
) IOleObject_Release(lpObject
);
1189 if (lpClientSite
) IOleClientSite_Release(lpClientSite
);
1190 if (lpStorage
) IStorage_Release(lpStorage
);
1191 if (lpDataObject
) IDataObject_Release(lpDataObject
);
1192 if (lpOleCache
) IOleCache_Release(lpOleCache
);
1193 if (lpReOle
) IRichEditOle_Release(lpReOle
);
1198 static void ME_RTFReadShpPictGroup( RTF_Info
*info
)
1206 if (info
->rtfClass
== rtfEOF
) return;
1207 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1209 if (--level
== 0) break;
1211 else if (RTFCheckCM( info
, rtfGroup
, rtfBeginGroup
))
1217 RTFRouteToken( info
);
1218 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1223 RTFRouteToken( info
); /* feed "}" back to router */
1227 static DWORD
read_hex_data( RTF_Info
*info
, BYTE
**out
)
1229 DWORD read
= 0, size
= 1024;
1235 if (info
->rtfClass
!= rtfText
)
1237 ERR("Called with incorrect token\n");
1241 buf
= HeapAlloc( GetProcessHeap(), 0, size
);
1244 val
= info
->rtfMajor
;
1245 for (flip
= TRUE
;; flip
= !flip
)
1247 RTFGetToken( info
);
1248 if (info
->rtfClass
== rtfEOF
)
1250 HeapFree( GetProcessHeap(), 0, buf
);
1253 if (info
->rtfClass
!= rtfText
) break;
1259 buf
= HeapReAlloc( GetProcessHeap(), 0, buf
, size
);
1262 buf
[read
++] = RTFCharToHex(val
) * 16 + RTFCharToHex(info
->rtfMajor
);
1265 val
= info
->rtfMajor
;
1267 if (flip
) FIXME("wrong hex string\n");
1273 static void ME_RTFReadPictGroup(RTF_Info
*info
)
1276 BYTE
*buffer
= NULL
;
1281 enum gfxkind
{gfx_unknown
= 0, gfx_enhmetafile
, gfx_metafile
, gfx_dib
} gfx
= gfx_unknown
;
1289 RTFGetToken( info
);
1291 if (info
->rtfClass
== rtfText
)
1296 size
= read_hex_data( info
, &buffer
);
1300 RTFSkipGroup( info
);
1302 } /* We potentially have a new token so fall through. */
1304 if (info
->rtfClass
== rtfEOF
) return;
1306 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1308 if (--level
== 0) break;
1311 if (RTFCheckCM( info
, rtfGroup
, rtfBeginGroup
))
1316 if (!RTFCheckCM( info
, rtfControl
, rtfPictAttr
))
1318 RTFRouteToken( info
);
1319 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1324 if (RTFCheckMM( info
, rtfPictAttr
, rtfWinMetafile
))
1326 mfp
.mm
= info
->rtfParam
;
1329 else if (RTFCheckMM( info
, rtfPictAttr
, rtfDevIndBitmap
))
1331 if (info
->rtfParam
!= 0) FIXME("dibitmap should be 0 (%d)\n", info
->rtfParam
);
1334 else if (RTFCheckMM( info
, rtfPictAttr
, rtfEmfBlip
))
1335 gfx
= gfx_enhmetafile
;
1336 else if (RTFCheckMM( info
, rtfPictAttr
, rtfPicWid
))
1337 mfp
.xExt
= info
->rtfParam
;
1338 else if (RTFCheckMM( info
, rtfPictAttr
, rtfPicHt
))
1339 mfp
.yExt
= info
->rtfParam
;
1340 else if (RTFCheckMM( info
, rtfPictAttr
, rtfPicGoalWid
))
1341 sz
.cx
= info
->rtfParam
;
1342 else if (RTFCheckMM( info
, rtfPictAttr
, rtfPicGoalHt
))
1343 sz
.cy
= info
->rtfParam
;
1345 FIXME("Non supported attribute: %d %d %d\n", info
->rtfClass
, info
->rtfMajor
, info
->rtfMinor
);
1352 case gfx_enhmetafile
:
1353 if ((hemf
= SetEnhMetaFileBits( size
, buffer
)))
1354 insert_static_object( info
->editor
, hemf
, NULL
, &sz
);
1357 if ((hemf
= SetWinMetaFileBits( size
, buffer
, NULL
, &mfp
)))
1358 insert_static_object( info
->editor
, hemf
, NULL
, &sz
);
1362 BITMAPINFO
*bi
= (BITMAPINFO
*)buffer
;
1364 unsigned nc
= bi
->bmiHeader
.biClrUsed
;
1366 /* not quite right, especially for bitfields type of compression */
1367 if (!nc
&& bi
->bmiHeader
.biBitCount
<= 8)
1368 nc
= 1 << bi
->bmiHeader
.biBitCount
;
1369 if ((hbmp
= CreateDIBitmap( hdc
, &bi
->bmiHeader
,
1370 CBM_INIT
, (char*)(bi
+ 1) + nc
* sizeof(RGBQUAD
),
1371 bi
, DIB_RGB_COLORS
)) )
1372 insert_static_object( info
->editor
, NULL
, hbmp
, &sz
);
1373 ReleaseDC( 0, hdc
);
1380 HeapFree( GetProcessHeap(), 0, buffer
);
1381 RTFRouteToken( info
); /* feed "}" back to router */
1385 /* for now, lookup the \result part and use it, whatever the object */
1386 static void ME_RTFReadObjectGroup(RTF_Info
*info
)
1391 if (info
->rtfClass
== rtfEOF
)
1393 if (RTFCheckCM(info
, rtfGroup
, rtfEndGroup
))
1395 if (RTFCheckCM(info
, rtfGroup
, rtfBeginGroup
))
1398 if (info
->rtfClass
== rtfEOF
)
1400 if (RTFCheckCMM(info
, rtfControl
, rtfDestination
, rtfObjResult
))
1404 while (RTFGetToken (info
) != rtfEOF
)
1406 if (info
->rtfClass
== rtfGroup
)
1408 if (info
->rtfMajor
== rtfBeginGroup
) level
++;
1409 else if (info
->rtfMajor
== rtfEndGroup
&& --level
< 0) break;
1411 RTFRouteToken(info
);
1414 else RTFSkipGroup(info
);
1417 if (!RTFCheckCM (info
, rtfControl
, rtfObjAttr
))
1419 FIXME("Non supported attribute: %d %d %d\n", info
->rtfClass
, info
->rtfMajor
, info
->rtfMinor
);
1423 RTFRouteToken(info
); /* feed "}" back to router */
1426 static void ME_RTFReadParnumGroup( RTF_Info
*info
)
1428 int level
= 1, type
= -1;
1429 WORD indent
= 0, start
= 1;
1430 WCHAR txt_before
= 0, txt_after
= 0;
1434 RTFGetToken( info
);
1436 if (RTFCheckCMM( info
, rtfControl
, rtfDestination
, rtfParNumTextBefore
) ||
1437 RTFCheckCMM( info
, rtfControl
, rtfDestination
, rtfParNumTextAfter
))
1439 int loc
= info
->rtfMinor
;
1441 RTFGetToken( info
);
1442 if (info
->rtfClass
== rtfText
)
1444 if (loc
== rtfParNumTextBefore
)
1445 txt_before
= info
->rtfMajor
;
1447 txt_after
= info
->rtfMajor
;
1450 /* falling through to catch EOFs and group level changes */
1453 if (info
->rtfClass
== rtfEOF
)
1456 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1458 if (--level
== 0) break;
1462 if (RTFCheckCM( info
, rtfGroup
, rtfBeginGroup
))
1468 /* Ignore non para-attr */
1469 if (!RTFCheckCM( info
, rtfControl
, rtfParAttr
))
1472 switch (info
->rtfMinor
)
1474 case rtfParLevel
: /* Para level is ignored */
1481 case rtfParNumDecimal
:
1484 case rtfParNumULetter
:
1485 type
= PFN_UCLETTER
;
1487 case rtfParNumURoman
:
1490 case rtfParNumLLetter
:
1491 type
= PFN_LCLETTER
;
1493 case rtfParNumLRoman
:
1497 case rtfParNumIndent
:
1498 indent
= info
->rtfParam
;
1500 case rtfParNumStartAt
:
1501 start
= info
->rtfParam
;
1508 info
->fmt
.dwMask
|= (PFM_NUMBERING
| PFM_NUMBERINGSTART
| PFM_NUMBERINGSTYLE
| PFM_NUMBERINGTAB
);
1509 info
->fmt
.wNumbering
= type
;
1510 info
->fmt
.wNumberingStart
= start
;
1511 info
->fmt
.wNumberingStyle
= PFNS_PAREN
;
1512 if (type
!= PFN_BULLET
)
1514 if (txt_before
== 0 && txt_after
== 0)
1515 info
->fmt
.wNumberingStyle
= PFNS_PLAIN
;
1516 else if (txt_after
== '.')
1517 info
->fmt
.wNumberingStyle
= PFNS_PERIOD
;
1518 else if (txt_before
== '(' && txt_after
== ')')
1519 info
->fmt
.wNumberingStyle
= PFNS_PARENS
;
1521 info
->fmt
.wNumberingTab
= indent
;
1524 TRACE("type %d indent %d start %d txt before %04x txt after %04x\n",
1525 type
, indent
, start
, txt_before
, txt_after
);
1527 RTFRouteToken( info
); /* feed "}" back to router */
1530 static void ME_RTFReadHook(RTF_Info
*info
)
1532 switch(info
->rtfClass
)
1535 switch(info
->rtfMajor
)
1538 if (info
->stackTop
< maxStack
) {
1539 info
->stack
[info
->stackTop
].style
= info
->style
;
1540 ME_AddRefStyle(info
->style
);
1541 info
->stack
[info
->stackTop
].codePage
= info
->codePage
;
1542 info
->stack
[info
->stackTop
].unicodeLength
= info
->unicodeLength
;
1545 info
->styleChanged
= FALSE
;
1549 RTFFlushOutputBuffer(info
);
1551 if (info
->stackTop
<= 0)
1552 info
->rtfClass
= rtfEOF
;
1553 if (info
->stackTop
< 0)
1556 ME_ReleaseStyle(info
->style
);
1557 info
->style
= info
->stack
[info
->stackTop
].style
;
1558 info
->codePage
= info
->stack
[info
->stackTop
].codePage
;
1559 info
->unicodeLength
= info
->stack
[info
->stackTop
].unicodeLength
;
1568 ME_StreamInFill(ME_InStream
*stream
)
1570 stream
->editstream
->dwError
= stream
->editstream
->pfnCallback(stream
->editstream
->dwCookie
,
1571 (BYTE
*)stream
->buffer
,
1572 sizeof(stream
->buffer
),
1573 (LONG
*)&stream
->dwSize
);
1577 static LRESULT
ME_StreamIn(ME_TextEditor
*editor
, DWORD format
, EDITSTREAM
*stream
, BOOL stripLastCR
)
1581 int from
, to
, nUndoMode
;
1582 int nEventMask
= editor
->nEventMask
;
1583 ME_InStream inStream
;
1584 BOOL invalidRTF
= FALSE
;
1585 ME_Cursor
*selStart
, *selEnd
;
1586 LRESULT num_read
= 0; /* bytes read for SF_TEXT, non-control chars inserted for SF_RTF */
1588 TRACE("stream==%p editor==%p format==0x%X\n", stream
, editor
, format
);
1589 editor
->nEventMask
= 0;
1591 ME_GetSelectionOfs(editor
, &from
, &to
);
1592 if (format
& SFF_SELECTION
&& editor
->mode
& TM_RICHTEXT
)
1594 ME_GetSelection(editor
, &selStart
, &selEnd
);
1595 style
= ME_GetSelectionInsertStyle(editor
);
1597 ME_InternalDeleteText(editor
, selStart
, to
- from
, FALSE
);
1599 /* Don't insert text at the end of the table row */
1600 if (!editor
->bEmulateVersion10
) { /* v4.1 */
1601 ME_DisplayItem
*para
= editor
->pCursors
->pPara
;
1602 if (para
->member
.para
.nFlags
& MEPF_ROWEND
)
1604 para
= para
->member
.para
.next_para
;
1605 editor
->pCursors
[0].pPara
= para
;
1606 editor
->pCursors
[0].pRun
= ME_FindItemFwd(para
, diRun
);
1607 editor
->pCursors
[0].nOffset
= 0;
1609 if (para
->member
.para
.nFlags
& MEPF_ROWSTART
)
1611 para
= para
->member
.para
.next_para
;
1612 editor
->pCursors
[0].pPara
= para
;
1613 editor
->pCursors
[0].pRun
= ME_FindItemFwd(para
, diRun
);
1614 editor
->pCursors
[0].nOffset
= 0;
1616 editor
->pCursors
[1] = editor
->pCursors
[0];
1617 } else { /* v1.0 - 3.0 */
1618 if (editor
->pCursors
[0].pRun
->member
.run
.nFlags
& MERF_ENDPARA
&&
1619 ME_IsInTable(editor
->pCursors
[0].pRun
))
1623 style
= editor
->pBuffer
->pDefaultStyle
;
1624 ME_AddRefStyle(style
);
1625 set_selection_cursors(editor
, 0, 0);
1626 ME_InternalDeleteText(editor
, &editor
->pCursors
[1],
1627 ME_GetTextLength(editor
), FALSE
);
1629 ME_ClearTempStyle(editor
);
1630 ME_SetDefaultParaFormat(editor
, &editor
->pCursors
[0].pPara
->member
.para
.fmt
);
1634 /* Back up undo mode to a local variable */
1635 nUndoMode
= editor
->nUndoMode
;
1637 /* Only create an undo if SFF_SELECTION is set */
1638 if (!(format
& SFF_SELECTION
))
1639 editor
->nUndoMode
= umIgnore
;
1641 inStream
.editstream
= stream
;
1642 inStream
.editstream
->dwError
= 0;
1643 inStream
.dwSize
= 0;
1644 inStream
.dwUsed
= 0;
1646 if (format
& SF_RTF
)
1648 /* Check if it's really RTF, and if it is not, use plain text */
1649 ME_StreamInFill(&inStream
);
1650 if (!inStream
.editstream
->dwError
)
1652 if ((!editor
->bEmulateVersion10
&& strncmp(inStream
.buffer
, "{\\rtf", 5) && strncmp(inStream
.buffer
, "{\\urtf", 6))
1653 || (editor
->bEmulateVersion10
&& *inStream
.buffer
!= '{'))
1656 inStream
.editstream
->dwError
= -16;
1661 if (!invalidRTF
&& !inStream
.editstream
->dwError
)
1664 from
= ME_GetCursorOfs(&editor
->pCursors
[0]);
1665 if (format
& SF_RTF
) {
1667 /* setup the RTF parser */
1668 memset(&parser
, 0, sizeof parser
);
1669 RTFSetEditStream(&parser
, &inStream
);
1670 parser
.rtfFormat
= format
&(SF_TEXT
|SF_RTF
);
1671 parser
.editor
= editor
;
1672 parser
.style
= style
;
1673 WriterInit(&parser
);
1675 RTFSetReadHook(&parser
, ME_RTFReadHook
);
1676 RTFSetDestinationCallback(&parser
, rtfShpPict
, ME_RTFReadShpPictGroup
);
1677 RTFSetDestinationCallback(&parser
, rtfPict
, ME_RTFReadPictGroup
);
1678 RTFSetDestinationCallback(&parser
, rtfObject
, ME_RTFReadObjectGroup
);
1679 RTFSetDestinationCallback(&parser
, rtfParNumbering
, ME_RTFReadParnumGroup
);
1680 if (!parser
.editor
->bEmulateVersion10
) /* v4.1 */
1682 RTFSetDestinationCallback(&parser
, rtfNoNestTables
, RTFSkipGroup
);
1683 RTFSetDestinationCallback(&parser
, rtfNestTableProps
, RTFReadGroup
);
1687 /* do the parsing */
1689 RTFFlushOutputBuffer(&parser
);
1690 if (!editor
->bEmulateVersion10
) { /* v4.1 */
1691 if (parser
.tableDef
&& parser
.tableDef
->tableRowStart
&&
1692 (parser
.nestingLevel
> 0 || parser
.canInheritInTbl
))
1694 /* Delete any incomplete table row at the end of the rich text. */
1696 ME_DisplayItem
*para
;
1698 parser
.rtfMinor
= rtfRow
;
1699 /* Complete the table row before deleting it.
1700 * By doing it this way we will have the current paragraph format set
1701 * properly to reflect that is not in the complete table, and undo items
1702 * will be added for this change to the current paragraph format. */
1703 if (parser
.nestingLevel
> 0)
1705 while (parser
.nestingLevel
> 1)
1706 ME_RTFSpecialCharHook(&parser
); /* Decrements nestingLevel */
1707 para
= parser
.tableDef
->tableRowStart
;
1708 ME_RTFSpecialCharHook(&parser
);
1710 para
= parser
.tableDef
->tableRowStart
;
1711 ME_RTFSpecialCharHook(&parser
);
1712 assert(para
->member
.para
.nFlags
& MEPF_ROWEND
);
1713 para
= para
->member
.para
.next_para
;
1716 editor
->pCursors
[1].pPara
= para
;
1717 editor
->pCursors
[1].pRun
= ME_FindItemFwd(para
, diRun
);
1718 editor
->pCursors
[1].nOffset
= 0;
1719 nOfs
= ME_GetCursorOfs(&editor
->pCursors
[1]);
1720 nChars
= ME_GetCursorOfs(&editor
->pCursors
[0]) - nOfs
;
1721 ME_InternalDeleteText(editor
, &editor
->pCursors
[1], nChars
, TRUE
);
1722 if (parser
.tableDef
)
1723 parser
.tableDef
->tableRowStart
= NULL
;
1726 ME_CheckTablesForCorruption(editor
);
1727 RTFDestroy(&parser
);
1729 if (parser
.stackTop
> 0)
1731 while (--parser
.stackTop
>= 0)
1733 ME_ReleaseStyle(parser
.style
);
1734 parser
.style
= parser
.stack
[parser
.stackTop
].style
;
1736 if (!inStream
.editstream
->dwError
)
1737 inStream
.editstream
->dwError
= HRESULT_FROM_WIN32(ERROR_HANDLE_EOF
);
1740 /* Remove last line break, as mandated by tests. This is not affected by
1741 CR/LF counters, since RTF streaming presents only \para tokens, which
1742 are converted according to the standard rules: \r for 2.0, \r\n for 1.0
1744 if (stripLastCR
&& !(format
& SFF_SELECTION
)) {
1746 ME_GetSelection(editor
, &selStart
, &selEnd
);
1747 newto
= ME_GetCursorOfs(selEnd
);
1748 if (newto
> to
+ (editor
->bEmulateVersion10
? 1 : 0)) {
1749 WCHAR lastchar
[3] = {'\0', '\0'};
1750 int linebreakSize
= editor
->bEmulateVersion10
? 2 : 1;
1751 ME_Cursor linebreakCursor
= *selEnd
, lastcharCursor
= *selEnd
;
1754 /* Set the final eop to the char fmt of the last char */
1755 cf
.cbSize
= sizeof(cf
);
1756 cf
.dwMask
= CFM_ALL2
;
1757 ME_MoveCursorChars(editor
, &lastcharCursor
, -1, FALSE
);
1758 ME_GetCharFormat(editor
, &lastcharCursor
, &linebreakCursor
, &cf
);
1759 set_selection_cursors(editor
, newto
, -1);
1760 ME_SetSelectionCharFormat(editor
, &cf
);
1761 set_selection_cursors(editor
, newto
, newto
);
1763 ME_MoveCursorChars(editor
, &linebreakCursor
, -linebreakSize
, FALSE
);
1764 ME_GetTextW(editor
, lastchar
, 2, &linebreakCursor
, linebreakSize
, FALSE
, FALSE
);
1765 if (lastchar
[0] == '\r' && (lastchar
[1] == '\n' || lastchar
[1] == '\0')) {
1766 ME_InternalDeleteText(editor
, &linebreakCursor
, linebreakSize
, FALSE
);
1770 to
= ME_GetCursorOfs(&editor
->pCursors
[0]);
1771 num_read
= to
- from
;
1773 style
= parser
.style
;
1775 else if (format
& SF_TEXT
)
1777 num_read
= ME_StreamInText(editor
, format
, &inStream
, style
);
1778 to
= ME_GetCursorOfs(&editor
->pCursors
[0]);
1781 ERR("EM_STREAMIN without SF_TEXT or SF_RTF\n");
1782 /* put the cursor at the top */
1783 if (!(format
& SFF_SELECTION
))
1784 set_selection_cursors(editor
, 0, 0);
1785 ME_CursorFromCharOfs(editor
, from
, &start
);
1786 ME_UpdateLinkAttribute(editor
, &start
, to
- from
);
1789 /* Restore saved undo mode */
1790 editor
->nUndoMode
= nUndoMode
;
1792 /* even if we didn't add an undo, we need to commit anything on the stack */
1793 ME_CommitUndo(editor
);
1795 /* If SFF_SELECTION isn't set, delete any undos from before we started too */
1796 if (!(format
& SFF_SELECTION
))
1797 ME_EmptyUndoStack(editor
);
1799 ME_ReleaseStyle(style
);
1800 editor
->nEventMask
= nEventMask
;
1801 ME_UpdateRepaint(editor
, FALSE
);
1802 if (!(format
& SFF_SELECTION
)) {
1803 ME_ClearTempStyle(editor
);
1805 update_caret(editor
);
1806 ME_SendSelChange(editor
);
1807 ME_SendRequestResize(editor
, FALSE
);
1813 typedef struct tagME_RTFStringStreamStruct
1818 } ME_RTFStringStreamStruct
;
1820 static DWORD CALLBACK
ME_ReadFromRTFString(DWORD_PTR dwCookie
, LPBYTE lpBuff
, LONG cb
, LONG
*pcb
)
1822 ME_RTFStringStreamStruct
*pStruct
= (ME_RTFStringStreamStruct
*)dwCookie
;
1825 count
= min(cb
, pStruct
->length
- pStruct
->pos
);
1826 memmove(lpBuff
, pStruct
->string
+ pStruct
->pos
, count
);
1827 pStruct
->pos
+= count
;
1833 ME_StreamInRTFString(ME_TextEditor
*editor
, BOOL selection
, char *string
)
1836 ME_RTFStringStreamStruct data
;
1838 data
.string
= string
;
1839 data
.length
= strlen(string
);
1841 es
.dwCookie
= (DWORD_PTR
)&data
;
1842 es
.pfnCallback
= ME_ReadFromRTFString
;
1843 ME_StreamIn(editor
, SF_RTF
| (selection
? SFF_SELECTION
: 0), &es
, TRUE
);
1848 ME_FindText(ME_TextEditor
*editor
, DWORD flags
, const CHARRANGE
*chrg
, const WCHAR
*text
, CHARRANGE
*chrgText
)
1850 const int nLen
= lstrlenW(text
);
1851 const int nTextLen
= ME_GetTextLength(editor
);
1854 WCHAR wLastChar
= ' ';
1856 TRACE("flags==0x%08x, chrg->cpMin==%d, chrg->cpMax==%d text==%s\n",
1857 flags
, chrg
->cpMin
, chrg
->cpMax
, debugstr_w(text
));
1859 if (flags
& ~(FR_DOWN
| FR_MATCHCASE
| FR_WHOLEWORD
))
1860 FIXME("Flags 0x%08x not implemented\n",
1861 flags
& ~(FR_DOWN
| FR_MATCHCASE
| FR_WHOLEWORD
));
1864 if (chrg
->cpMax
== -1)
1867 nMax
= chrg
->cpMax
> nTextLen
? nTextLen
: chrg
->cpMax
;
1869 /* In 1.0 emulation, if cpMax reaches end of text, add the FR_DOWN flag */
1870 if (editor
->bEmulateVersion10
&& nMax
== nTextLen
)
1875 /* In 1.0 emulation, cpMin must always be no greater than cpMax */
1876 if (editor
->bEmulateVersion10
&& nMax
< nMin
)
1880 chrgText
->cpMin
= -1;
1881 chrgText
->cpMax
= -1;
1886 /* when searching up, if cpMin < cpMax, then instead of searching
1887 * on [cpMin,cpMax], we search on [0,cpMin], otherwise, search on
1888 * [cpMax, cpMin]. The exception is when cpMax is -1, in which
1889 * case, it is always bigger than cpMin.
1891 if (!editor
->bEmulateVersion10
&& !(flags
& FR_DOWN
))
1895 nMax
= nMin
> nTextLen
? nTextLen
: nMin
;
1896 if (nMin
< nSwap
|| chrg
->cpMax
== -1)
1902 if (!nLen
|| nMin
< 0 || nMax
< 0 || nMax
< nMin
)
1905 chrgText
->cpMin
= chrgText
->cpMax
= -1;
1909 if (flags
& FR_DOWN
) /* Forward search */
1911 /* If possible, find the character before where the search starts */
1912 if ((flags
& FR_WHOLEWORD
) && nMin
)
1914 ME_CursorFromCharOfs(editor
, nMin
- 1, &cursor
);
1915 wLastChar
= *get_text( &cursor
.pRun
->member
.run
, cursor
.nOffset
);
1916 ME_MoveCursorChars(editor
, &cursor
, 1, FALSE
);
1918 ME_CursorFromCharOfs(editor
, nMin
, &cursor
);
1921 while (cursor
.pRun
&& ME_GetCursorOfs(&cursor
) + nLen
<= nMax
)
1923 ME_DisplayItem
*pCurItem
= cursor
.pRun
;
1924 int nCurStart
= cursor
.nOffset
;
1927 while (pCurItem
&& ME_CharCompare( *get_text( &pCurItem
->member
.run
, nCurStart
+ nMatched
), text
[nMatched
], (flags
& FR_MATCHCASE
)))
1929 if ((flags
& FR_WHOLEWORD
) && iswalnum(wLastChar
))
1933 if (nMatched
== nLen
)
1935 ME_DisplayItem
*pNextItem
= pCurItem
;
1936 int nNextStart
= nCurStart
;
1939 /* Check to see if next character is a whitespace */
1940 if (flags
& FR_WHOLEWORD
)
1942 if (nCurStart
+ nMatched
== pCurItem
->member
.run
.len
)
1944 pNextItem
= ME_FindItemFwd(pCurItem
, diRun
);
1945 nNextStart
= -nMatched
;
1949 wNextChar
= *get_text( &pNextItem
->member
.run
, nNextStart
+ nMatched
);
1953 if (iswalnum(wNextChar
))
1957 cursor
.nOffset
+= cursor
.pPara
->member
.para
.nCharOfs
+ cursor
.pRun
->member
.run
.nCharOfs
;
1960 chrgText
->cpMin
= cursor
.nOffset
;
1961 chrgText
->cpMax
= cursor
.nOffset
+ nLen
;
1963 TRACE("found at %d-%d\n", cursor
.nOffset
, cursor
.nOffset
+ nLen
);
1964 return cursor
.nOffset
;
1966 if (nCurStart
+ nMatched
== pCurItem
->member
.run
.len
)
1968 pCurItem
= ME_FindItemFwd(pCurItem
, diRun
);
1969 nCurStart
= -nMatched
;
1973 wLastChar
= *get_text( &pCurItem
->member
.run
, nCurStart
+ nMatched
);
1978 if (cursor
.nOffset
== cursor
.pRun
->member
.run
.len
)
1980 ME_NextRun(&cursor
.pPara
, &cursor
.pRun
, TRUE
);
1985 else /* Backward search */
1987 /* If possible, find the character after where the search ends */
1988 if ((flags
& FR_WHOLEWORD
) && nMax
< nTextLen
- 1)
1990 ME_CursorFromCharOfs(editor
, nMax
+ 1, &cursor
);
1991 wLastChar
= *get_text( &cursor
.pRun
->member
.run
, cursor
.nOffset
);
1992 ME_MoveCursorChars(editor
, &cursor
, -1, FALSE
);
1994 ME_CursorFromCharOfs(editor
, nMax
, &cursor
);
1997 while (cursor
.pRun
&& ME_GetCursorOfs(&cursor
) - nLen
>= nMin
)
1999 ME_DisplayItem
*pCurItem
= cursor
.pRun
;
2000 ME_DisplayItem
*pCurPara
= cursor
.pPara
;
2001 int nCurEnd
= cursor
.nOffset
;
2006 ME_PrevRun(&pCurPara
, &pCurItem
, TRUE
);
2007 nCurEnd
= pCurItem
->member
.run
.len
;
2010 while (pCurItem
&& ME_CharCompare( *get_text( &pCurItem
->member
.run
, nCurEnd
- nMatched
- 1 ),
2011 text
[nLen
- nMatched
- 1], (flags
& FR_MATCHCASE
) ))
2013 if ((flags
& FR_WHOLEWORD
) && iswalnum(wLastChar
))
2017 if (nMatched
== nLen
)
2019 ME_DisplayItem
*pPrevItem
= pCurItem
;
2020 int nPrevEnd
= nCurEnd
;
2024 /* Check to see if previous character is a whitespace */
2025 if (flags
& FR_WHOLEWORD
)
2027 if (nPrevEnd
- nMatched
== 0)
2029 pPrevItem
= ME_FindItemBack(pCurItem
, diRun
);
2031 nPrevEnd
= pPrevItem
->member
.run
.len
+ nMatched
;
2035 wPrevChar
= *get_text( &pPrevItem
->member
.run
, nPrevEnd
- nMatched
- 1 );
2039 if (iswalnum(wPrevChar
))
2043 nStart
= pCurPara
->member
.para
.nCharOfs
2044 + pCurItem
->member
.run
.nCharOfs
+ nCurEnd
- nMatched
;
2047 chrgText
->cpMin
= nStart
;
2048 chrgText
->cpMax
= nStart
+ nLen
;
2050 TRACE("found at %d-%d\n", nStart
, nStart
+ nLen
);
2053 if (nCurEnd
- nMatched
== 0)
2055 ME_PrevRun(&pCurPara
, &pCurItem
, TRUE
);
2056 /* Don't care about pCurItem becoming NULL here; it's already taken
2057 * care of in the exterior loop condition */
2058 nCurEnd
= pCurItem
->member
.run
.len
+ nMatched
;
2062 wLastChar
= *get_text( &pCurItem
->member
.run
, nCurEnd
- nMatched
- 1 );
2067 if (cursor
.nOffset
< 0)
2069 ME_PrevRun(&cursor
.pPara
, &cursor
.pRun
, TRUE
);
2070 cursor
.nOffset
= cursor
.pRun
->member
.run
.len
;
2074 TRACE("not found\n");
2076 chrgText
->cpMin
= chrgText
->cpMax
= -1;
2080 static int ME_GetTextEx(ME_TextEditor
*editor
, GETTEXTEX
*ex
, LPARAM pText
)
2085 if (!ex
->cb
|| !pText
) return 0;
2087 if (ex
->flags
& ~(GT_SELECTION
| GT_USECRLF
))
2088 FIXME("GETTEXTEX flags 0x%08x not supported\n", ex
->flags
& ~(GT_SELECTION
| GT_USECRLF
));
2090 if (ex
->flags
& GT_SELECTION
)
2093 int nStartCur
= ME_GetSelectionOfs(editor
, &from
, &to
);
2094 start
= editor
->pCursors
[nStartCur
];
2099 ME_SetCursorToStart(editor
, &start
);
2102 if (ex
->codepage
== CP_UNICODE
)
2104 return ME_GetTextW(editor
, (LPWSTR
)pText
, ex
->cb
/ sizeof(WCHAR
) - 1,
2105 &start
, nChars
, ex
->flags
& GT_USECRLF
, FALSE
);
2109 /* potentially each char may be a CR, why calculate the exact value with O(N) when
2110 we can just take a bigger buffer? :)
2111 The above assumption still holds with CR/LF counters, since CR->CRLF expansion
2112 occurs only in richedit 2.0 mode, in which line breaks have only one CR
2114 int crlfmul
= (ex
->flags
& GT_USECRLF
) ? 2 : 1;
2119 buflen
= min(crlfmul
* nChars
, ex
->cb
- 1);
2120 buffer
= heap_alloc((buflen
+ 1) * sizeof(WCHAR
));
2122 nChars
= ME_GetTextW(editor
, buffer
, buflen
, &start
, nChars
, ex
->flags
& GT_USECRLF
, FALSE
);
2123 rc
= WideCharToMultiByte(ex
->codepage
, 0, buffer
, nChars
+ 1,
2124 (LPSTR
)pText
, ex
->cb
, ex
->lpDefaultChar
, ex
->lpUsedDefChar
);
2125 if (rc
) rc
--; /* do not count 0 terminator */
2132 static int ME_GetTextRange(ME_TextEditor
*editor
, WCHAR
*strText
,
2133 const ME_Cursor
*start
, int nLen
, BOOL unicode
)
2135 if (!strText
) return 0;
2137 return ME_GetTextW(editor
, strText
, INT_MAX
, start
, nLen
, FALSE
, FALSE
);
2140 WCHAR
*p
= heap_alloc((nLen
+1) * sizeof(*p
));
2142 nChars
= ME_GetTextW(editor
, p
, nLen
, start
, nLen
, FALSE
, FALSE
);
2143 WideCharToMultiByte(CP_ACP
, 0, p
, nChars
+1, (char *)strText
,
2144 nLen
+1, NULL
, NULL
);
2150 int set_selection( ME_TextEditor
*editor
, int to
, int from
)
2154 TRACE("%d - %d\n", to
, from
);
2156 if (!editor
->bHideSelection
) ME_InvalidateSelection( editor
);
2157 end
= set_selection_cursors( editor
, to
, from
);
2158 if (!editor
->bHideSelection
) ME_InvalidateSelection( editor
);
2159 update_caret( editor
);
2160 ME_SendSelChange( editor
);
2165 typedef struct tagME_GlobalDestStruct
2169 } ME_GlobalDestStruct
;
2171 static DWORD CALLBACK
ME_ReadFromHGLOBALUnicode(DWORD_PTR dwCookie
, LPBYTE lpBuff
, LONG cb
, LONG
*pcb
)
2173 ME_GlobalDestStruct
*pData
= (ME_GlobalDestStruct
*)dwCookie
;
2178 pDest
= (WORD
*)lpBuff
;
2179 pSrc
= GlobalLock(pData
->hData
);
2180 for (i
= 0; i
<cb
&& pSrc
[pData
->nLength
+i
]; i
++) {
2181 pDest
[i
] = pSrc
[pData
->nLength
+i
];
2183 pData
->nLength
+= i
;
2185 GlobalUnlock(pData
->hData
);
2189 static DWORD CALLBACK
ME_ReadFromHGLOBALRTF(DWORD_PTR dwCookie
, LPBYTE lpBuff
, LONG cb
, LONG
*pcb
)
2191 ME_GlobalDestStruct
*pData
= (ME_GlobalDestStruct
*)dwCookie
;
2196 pSrc
= GlobalLock(pData
->hData
);
2197 for (i
= 0; i
<cb
&& pSrc
[pData
->nLength
+i
]; i
++) {
2198 pDest
[i
] = pSrc
[pData
->nLength
+i
];
2200 pData
->nLength
+= i
;
2202 GlobalUnlock(pData
->hData
);
2206 static const WCHAR rtfW
[] = {'R','i','c','h',' ','T','e','x','t',' ','F','o','r','m','a','t',0};
2208 static HRESULT
paste_rtf(ME_TextEditor
*editor
, FORMATETC
*fmt
, STGMEDIUM
*med
)
2211 ME_GlobalDestStruct gds
;
2214 gds
.hData
= med
->u
.hGlobal
;
2216 es
.dwCookie
= (DWORD_PTR
)&gds
;
2217 es
.pfnCallback
= ME_ReadFromHGLOBALRTF
;
2218 hr
= ME_StreamIn( editor
, SF_RTF
| SFF_SELECTION
, &es
, FALSE
) == 0 ? E_FAIL
: S_OK
;
2219 ReleaseStgMedium( med
);
2223 static HRESULT
paste_text(ME_TextEditor
*editor
, FORMATETC
*fmt
, STGMEDIUM
*med
)
2226 ME_GlobalDestStruct gds
;
2229 gds
.hData
= med
->u
.hGlobal
;
2231 es
.dwCookie
= (DWORD_PTR
)&gds
;
2232 es
.pfnCallback
= ME_ReadFromHGLOBALUnicode
;
2233 hr
= ME_StreamIn( editor
, SF_TEXT
| SF_UNICODE
| SFF_SELECTION
, &es
, FALSE
) == 0 ? E_FAIL
: S_OK
;
2234 ReleaseStgMedium( med
);
2238 static HRESULT
paste_emf(ME_TextEditor
*editor
, FORMATETC
*fmt
, STGMEDIUM
*med
)
2243 hr
= insert_static_object( editor
, med
->u
.hEnhMetaFile
, NULL
, &sz
);
2246 ME_CommitUndo( editor
);
2247 ME_UpdateRepaint( editor
, FALSE
);
2250 ReleaseStgMedium( med
);
2255 static struct paste_format
2258 HRESULT (*paste
)(ME_TextEditor
*, FORMATETC
*, STGMEDIUM
*);
2262 {{ -1, NULL
, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
}, paste_rtf
, rtfW
},
2263 {{ CF_UNICODETEXT
, NULL
, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
}, paste_text
},
2264 {{ CF_ENHMETAFILE
, NULL
, DVASPECT_CONTENT
, -1, TYMED_ENHMF
}, paste_emf
},
2268 static void init_paste_formats(void)
2270 struct paste_format
*format
;
2275 for (format
= paste_formats
; format
->fmt
.cfFormat
; format
++)
2278 format
->fmt
.cfFormat
= RegisterClipboardFormatW( format
->name
);
2284 static BOOL
paste_special(ME_TextEditor
*editor
, UINT cf
, REPASTESPECIAL
*ps
, BOOL check_only
)
2288 struct paste_format
*format
;
2291 /* Protect read-only edit control from modification */
2292 if (editor
->styleFlags
& ES_READONLY
)
2295 MessageBeep(MB_ICONERROR
);
2299 init_paste_formats();
2301 if (ps
&& ps
->dwAspect
!= DVASPECT_CONTENT
)
2302 FIXME("Ignoring aspect %x\n", ps
->dwAspect
);
2304 hr
= OleGetClipboard( &data
);
2305 if (hr
!= S_OK
) return FALSE
;
2307 if (cf
== CF_TEXT
) cf
= CF_UNICODETEXT
;
2310 for (format
= paste_formats
; format
->fmt
.cfFormat
; format
++)
2312 if (cf
&& cf
!= format
->fmt
.cfFormat
) continue;
2313 hr
= IDataObject_QueryGetData( data
, &format
->fmt
);
2318 hr
= IDataObject_GetData( data
, &format
->fmt
, &med
);
2319 if (hr
!= S_OK
) goto done
;
2320 hr
= format
->paste( editor
, &format
->fmt
, &med
);
2327 IDataObject_Release( data
);
2332 static BOOL
ME_Copy(ME_TextEditor
*editor
, const ME_Cursor
*start
, int nChars
)
2334 LPDATAOBJECT dataObj
= NULL
;
2337 if (editor
->cPasswordMask
)
2338 return FALSE
; /* Copying or Cutting masked text isn't allowed */
2340 if(editor
->lpOleCallback
)
2343 range
.cpMin
= ME_GetCursorOfs(start
);
2344 range
.cpMax
= range
.cpMin
+ nChars
;
2345 hr
= IRichEditOleCallback_GetClipboardData(editor
->lpOleCallback
, &range
, RECO_COPY
, &dataObj
);
2347 if(FAILED(hr
) || !dataObj
)
2348 hr
= ME_GetDataObject(editor
, start
, nChars
, &dataObj
);
2350 hr
= OleSetClipboard(dataObj
);
2351 IDataObject_Release(dataObj
);
2353 return SUCCEEDED(hr
);
2356 static BOOL
copy_or_cut(ME_TextEditor
*editor
, BOOL cut
)
2359 int offs
, num_chars
;
2360 int start_cursor
= ME_GetSelectionOfs(editor
, &offs
, &num_chars
);
2361 ME_Cursor
*sel_start
= &editor
->pCursors
[start_cursor
];
2363 if (cut
&& (editor
->styleFlags
& ES_READONLY
))
2365 MessageBeep(MB_ICONERROR
);
2370 result
= ME_Copy(editor
, sel_start
, num_chars
);
2373 ME_InternalDeleteText(editor
, sel_start
, num_chars
, FALSE
);
2374 ME_CommitUndo(editor
);
2375 ME_UpdateRepaint(editor
, TRUE
);
2380 /* helper to send a msg filter notification */
2382 ME_FilterEvent(ME_TextEditor
*editor
, UINT msg
, WPARAM
* wParam
, LPARAM
* lParam
)
2386 if (!editor
->hWnd
|| !editor
->hwndParent
) return FALSE
;
2387 msgf
.nmhdr
.hwndFrom
= editor
->hWnd
;
2388 msgf
.nmhdr
.idFrom
= GetWindowLongW(editor
->hWnd
, GWLP_ID
);
2389 msgf
.nmhdr
.code
= EN_MSGFILTER
;
2391 msgf
.wParam
= *wParam
;
2392 msgf
.lParam
= *lParam
;
2393 if (SendMessageW(editor
->hwndParent
, WM_NOTIFY
, msgf
.nmhdr
.idFrom
, (LPARAM
)&msgf
))
2395 *wParam
= msgf
.wParam
;
2396 *lParam
= msgf
.lParam
;
2397 msgf
.wParam
= *wParam
;
2402 static void ME_UpdateSelectionLinkAttribute(ME_TextEditor
*editor
)
2404 ME_DisplayItem
*startPara
, *endPara
;
2405 ME_DisplayItem
*prev_para
;
2406 ME_Cursor
*from
, *to
;
2410 if (!editor
->AutoURLDetect_bEnable
) return;
2412 ME_GetSelection(editor
, &from
, &to
);
2414 /* Find paragraph previous to the one that contains start cursor */
2415 startPara
= from
->pPara
;
2416 prev_para
= startPara
->member
.para
.prev_para
;
2417 if (prev_para
->type
== diParagraph
) startPara
= prev_para
;
2419 /* Find paragraph that contains end cursor */
2420 endPara
= to
->pPara
->member
.para
.next_para
;
2422 start
.pPara
= startPara
;
2423 start
.pRun
= ME_FindItemFwd(startPara
, diRun
);
2425 nChars
= endPara
->member
.para
.nCharOfs
- startPara
->member
.para
.nCharOfs
;
2427 ME_UpdateLinkAttribute(editor
, &start
, nChars
);
2430 static BOOL
handle_enter(ME_TextEditor
*editor
)
2432 BOOL ctrl_is_down
= GetKeyState(VK_CONTROL
) & 0x8000;
2433 BOOL shift_is_down
= GetKeyState(VK_SHIFT
) & 0x8000;
2435 if (editor
->bDialogMode
)
2440 if (!(editor
->styleFlags
& ES_WANTRETURN
))
2442 if (editor
->hwndParent
)
2445 dw
= SendMessageW(editor
->hwndParent
, DM_GETDEFID
, 0, 0);
2446 if (HIWORD(dw
) == DC_HASDEFID
)
2448 HWND hwDefCtrl
= GetDlgItem(editor
->hwndParent
, LOWORD(dw
));
2451 SendMessageW(editor
->hwndParent
, WM_NEXTDLGCTL
, (WPARAM
)hwDefCtrl
, TRUE
);
2452 PostMessageW(hwDefCtrl
, WM_KEYDOWN
, VK_RETURN
, 0);
2460 if (editor
->styleFlags
& ES_MULTILINE
)
2462 static const WCHAR endl
= '\r';
2463 static const WCHAR endlv10
[] = {'\r','\n'};
2464 ME_Cursor cursor
= editor
->pCursors
[0];
2465 ME_DisplayItem
*para
= cursor
.pPara
;
2467 ME_Style
*style
, *eop_style
;
2469 if (editor
->styleFlags
& ES_READONLY
)
2471 MessageBeep(MB_ICONERROR
);
2475 ME_GetSelectionOfs(editor
, &from
, &to
);
2476 if (editor
->nTextLimit
> ME_GetTextLength(editor
) - (to
-from
))
2478 if (!editor
->bEmulateVersion10
) /* v4.1 */
2480 if (para
->member
.para
.nFlags
& MEPF_ROWEND
)
2482 /* Add a new table row after this row. */
2483 para
= ME_AppendTableRow(editor
, para
);
2484 para
= para
->member
.para
.next_para
;
2485 editor
->pCursors
[0].pPara
= para
;
2486 editor
->pCursors
[0].pRun
= ME_FindItemFwd(para
, diRun
);
2487 editor
->pCursors
[0].nOffset
= 0;
2488 editor
->pCursors
[1] = editor
->pCursors
[0];
2489 ME_CommitUndo(editor
);
2490 ME_CheckTablesForCorruption(editor
);
2491 ME_UpdateRepaint(editor
, FALSE
);
2494 else if (para
== editor
->pCursors
[1].pPara
&&
2495 cursor
.nOffset
+ cursor
.pRun
->member
.run
.nCharOfs
== 0 &&
2496 para
->member
.para
.prev_para
->member
.para
.nFlags
& MEPF_ROWSTART
&&
2497 !para
->member
.para
.prev_para
->member
.para
.nCharOfs
)
2499 /* Insert a newline before the table. */
2500 para
= para
->member
.para
.prev_para
;
2501 para
->member
.para
.nFlags
&= ~MEPF_ROWSTART
;
2502 editor
->pCursors
[0].pPara
= para
;
2503 editor
->pCursors
[0].pRun
= ME_FindItemFwd(para
, diRun
);
2504 editor
->pCursors
[1] = editor
->pCursors
[0];
2505 ME_InsertTextFromCursor(editor
, 0, &endl
, 1,
2506 editor
->pCursors
[0].pRun
->member
.run
.style
);
2507 para
= editor
->pBuffer
->pFirst
->member
.para
.next_para
;
2508 ME_SetDefaultParaFormat(editor
, ¶
->member
.para
.fmt
);
2509 para
->member
.para
.nFlags
= 0;
2510 mark_para_rewrap(editor
, para
);
2511 editor
->pCursors
[0].pPara
= para
;
2512 editor
->pCursors
[0].pRun
= ME_FindItemFwd(para
, diRun
);
2513 editor
->pCursors
[1] = editor
->pCursors
[0];
2514 para
->member
.para
.next_para
->member
.para
.nFlags
|= MEPF_ROWSTART
;
2515 ME_CommitCoalescingUndo(editor
);
2516 ME_CheckTablesForCorruption(editor
);
2517 ME_UpdateRepaint(editor
, FALSE
);
2521 else /* v1.0 - 3.0 */
2523 ME_DisplayItem
*para
= cursor
.pPara
;
2524 if (ME_IsInTable(para
))
2526 if (cursor
.pRun
->member
.run
.nFlags
& MERF_ENDPARA
)
2530 ME_ContinueCoalescingTransaction(editor
);
2531 para
= ME_AppendTableRow(editor
, para
);
2532 editor
->pCursors
[0].pPara
= para
;
2533 editor
->pCursors
[0].pRun
= ME_FindItemFwd(para
, diRun
);
2534 editor
->pCursors
[0].nOffset
= 0;
2535 editor
->pCursors
[1] = editor
->pCursors
[0];
2536 ME_CommitCoalescingUndo(editor
);
2537 ME_UpdateRepaint(editor
, FALSE
);
2543 ME_ContinueCoalescingTransaction(editor
);
2544 if (cursor
.pRun
->member
.run
.nCharOfs
+ cursor
.nOffset
== 0 &&
2545 !ME_IsInTable(para
->member
.para
.prev_para
))
2547 /* Insert newline before table */
2548 cursor
.pRun
= ME_FindItemBack(para
, diRun
);
2551 editor
->pCursors
[0].pRun
= cursor
.pRun
;
2552 editor
->pCursors
[0].pPara
= para
->member
.para
.prev_para
;
2554 editor
->pCursors
[0].nOffset
= 0;
2555 editor
->pCursors
[1] = editor
->pCursors
[0];
2556 ME_InsertTextFromCursor(editor
, 0, &endl
, 1,
2557 editor
->pCursors
[0].pRun
->member
.run
.style
);
2561 editor
->pCursors
[1] = editor
->pCursors
[0];
2562 para
= ME_AppendTableRow(editor
, para
);
2563 editor
->pCursors
[0].pPara
= para
;
2564 editor
->pCursors
[0].pRun
= ME_FindItemFwd(para
, diRun
);
2565 editor
->pCursors
[0].nOffset
= 0;
2566 editor
->pCursors
[1] = editor
->pCursors
[0];
2568 ME_CommitCoalescingUndo(editor
);
2569 ME_UpdateRepaint(editor
, FALSE
);
2575 style
= ME_GetInsertStyle(editor
, 0);
2577 /* Normally the new eop style is the insert style, however in a list it is copied from the existing
2578 eop style (this prevents the list label style changing when the new eop is inserted).
2579 No extra ref is taken here on eop_style. */
2580 if (para
->member
.para
.fmt
.wNumbering
)
2581 eop_style
= para
->member
.para
.eop_run
->style
;
2584 ME_ContinueCoalescingTransaction(editor
);
2586 ME_InsertEndRowFromCursor(editor
, 0);
2588 if (!editor
->bEmulateVersion10
)
2589 ME_InsertTextFromCursor(editor
, 0, &endl
, 1, eop_style
);
2591 ME_InsertTextFromCursor(editor
, 0, endlv10
, 2, eop_style
);
2592 ME_CommitCoalescingUndo(editor
);
2595 ME_UpdateSelectionLinkAttribute(editor
);
2596 ME_UpdateRepaint(editor
, FALSE
);
2597 ME_SaveTempStyle(editor
, style
); /* set the temp insert style for the new para */
2598 ME_ReleaseStyle(style
);
2606 ME_KeyDown(ME_TextEditor
*editor
, WORD nKey
)
2608 BOOL ctrl_is_down
= GetKeyState(VK_CONTROL
) & 0x8000;
2609 BOOL shift_is_down
= GetKeyState(VK_SHIFT
) & 0x8000;
2611 if (editor
->bMouseCaptured
)
2613 if (nKey
!= VK_SHIFT
&& nKey
!= VK_CONTROL
&& nKey
!= VK_MENU
)
2614 editor
->nSelectionType
= stPosition
;
2622 editor
->nUDArrowX
= -1;
2628 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
2629 ME_ArrowKey(editor
, nKey
, shift_is_down
, ctrl_is_down
);
2633 editor
->nUDArrowX
= -1;
2634 /* FIXME backspace and delete aren't the same, they act different wrt paragraph style of the merged paragraph */
2635 if (editor
->styleFlags
& ES_READONLY
)
2637 if (ME_IsSelection(editor
))
2639 ME_DeleteSelection(editor
);
2640 ME_CommitUndo(editor
);
2642 else if (nKey
== VK_DELETE
)
2644 /* Delete stops group typing.
2645 * (See MSDN remarks on EM_STOPGROUPTYPING message) */
2646 ME_DeleteTextAtCursor(editor
, 1, 1);
2647 ME_CommitUndo(editor
);
2649 else if (ME_ArrowKey(editor
, VK_LEFT
, FALSE
, FALSE
))
2651 BOOL bDeletionSucceeded
;
2652 /* Backspace can be grouped for a single undo */
2653 ME_ContinueCoalescingTransaction(editor
);
2654 bDeletionSucceeded
= ME_DeleteTextAtCursor(editor
, 1, 1);
2655 if (!bDeletionSucceeded
&& !editor
->bEmulateVersion10
) { /* v4.1 */
2656 /* Deletion was prevented so the cursor is moved back to where it was.
2657 * (e.g. this happens when trying to delete cell boundaries)
2659 ME_ArrowKey(editor
, VK_RIGHT
, FALSE
, FALSE
);
2661 ME_CommitCoalescingUndo(editor
);
2665 ME_MoveCursorFromTableRowStartParagraph(editor
);
2666 ME_UpdateSelectionLinkAttribute(editor
);
2667 ME_UpdateRepaint(editor
, FALSE
);
2668 ME_SendRequestResize(editor
, FALSE
);
2671 if (!editor
->bEmulateVersion10
)
2672 return handle_enter(editor
);
2675 if (editor
->bDialogMode
&& editor
->hwndParent
)
2676 PostMessageW(editor
->hwndParent
, WM_CLOSE
, 0, 0);
2679 if (editor
->bDialogMode
&& editor
->hwndParent
)
2680 SendMessageW(editor
->hwndParent
, WM_NEXTDLGCTL
, shift_is_down
, 0);
2685 set_selection( editor
, 0, -1 );
2691 return paste_special( editor
, 0, NULL
, FALSE
);
2696 return copy_or_cut(editor
, nKey
== 'X');
2714 if (nKey
!= VK_SHIFT
&& nKey
!= VK_CONTROL
&& nKey
&& nKey
!= VK_MENU
)
2715 editor
->nUDArrowX
= -1;
2722 chf
.cbSize
= sizeof(chf
);
2724 ME_GetSelectionCharFormat(editor
, &chf
);
2725 ME_DumpStyleToBuf(&chf
, buf
);
2726 MessageBoxA(NULL
, buf
, "Style dump", MB_OK
);
2730 ME_CheckCharOffsets(editor
);
2737 static LRESULT
ME_Char(ME_TextEditor
*editor
, WPARAM charCode
,
2738 LPARAM flags
, BOOL unicode
)
2742 if (editor
->bMouseCaptured
)
2745 if (editor
->styleFlags
& ES_READONLY
)
2747 MessageBeep(MB_ICONERROR
);
2748 return 0; /* FIXME really 0 ? */
2752 wstr
= (WCHAR
)charCode
;
2755 CHAR charA
= charCode
;
2756 MultiByteToWideChar(CP_ACP
, 0, &charA
, 1, &wstr
, 1);
2759 if (editor
->bEmulateVersion10
&& wstr
== '\r')
2760 handle_enter(editor
);
2762 if ((unsigned)wstr
>= ' ' || wstr
== '\t')
2764 ME_Cursor cursor
= editor
->pCursors
[0];
2765 ME_DisplayItem
*para
= cursor
.pPara
;
2767 BOOL ctrl_is_down
= GetKeyState(VK_CONTROL
) & 0x8000;
2768 ME_GetSelectionOfs(editor
, &from
, &to
);
2770 /* v4.1 allows tabs to be inserted with ctrl key down */
2771 !(ctrl_is_down
&& !editor
->bEmulateVersion10
))
2773 ME_DisplayItem
*para
;
2774 BOOL bSelectedRow
= FALSE
;
2776 para
= cursor
.pPara
;
2777 if (ME_IsSelection(editor
) &&
2778 cursor
.pRun
->member
.run
.nCharOfs
+ cursor
.nOffset
== 0 &&
2779 to
== ME_GetCursorOfs(&editor
->pCursors
[0]) &&
2780 para
->member
.para
.prev_para
->type
== diParagraph
)
2782 para
= para
->member
.para
.prev_para
;
2783 bSelectedRow
= TRUE
;
2785 if (ME_IsInTable(para
))
2787 ME_TabPressedInTable(editor
, bSelectedRow
);
2788 ME_CommitUndo(editor
);
2791 } else if (!editor
->bEmulateVersion10
) { /* v4.1 */
2792 if (para
->member
.para
.nFlags
& MEPF_ROWEND
) {
2794 para
= para
->member
.para
.next_para
;
2795 if (para
->member
.para
.nFlags
& MEPF_ROWSTART
)
2796 para
= para
->member
.para
.next_para
;
2797 editor
->pCursors
[0].pPara
= para
;
2798 editor
->pCursors
[0].pRun
= ME_FindItemFwd(para
, diRun
);
2799 editor
->pCursors
[0].nOffset
= 0;
2800 editor
->pCursors
[1] = editor
->pCursors
[0];
2803 } else { /* v1.0 - 3.0 */
2804 if (ME_IsInTable(cursor
.pRun
) &&
2805 cursor
.pRun
->member
.run
.nFlags
& MERF_ENDPARA
&&
2808 /* Text should not be inserted at the end of the table. */
2813 /* FIXME maybe it would make sense to call EM_REPLACESEL instead ? */
2814 /* WM_CHAR is restricted to nTextLimit */
2815 if(editor
->nTextLimit
> ME_GetTextLength(editor
) - (to
-from
))
2817 ME_Style
*style
= ME_GetInsertStyle(editor
, 0);
2818 ME_ContinueCoalescingTransaction(editor
);
2819 ME_InsertTextFromCursor(editor
, 0, &wstr
, 1, style
);
2820 ME_ReleaseStyle(style
);
2821 ME_CommitCoalescingUndo(editor
);
2822 ITextHost_TxSetCursor(editor
->texthost
, NULL
, FALSE
);
2825 ME_UpdateSelectionLinkAttribute(editor
);
2826 ME_UpdateRepaint(editor
, FALSE
);
2831 /* Process the message and calculate the new click count.
2833 * returns: The click count if it is mouse down event, else returns 0. */
2834 static int ME_CalculateClickCount(ME_TextEditor
*editor
, UINT msg
, WPARAM wParam
,
2837 static int clickNum
= 0;
2838 if (msg
< WM_MOUSEFIRST
|| msg
> WM_MOUSELAST
)
2841 if ((msg
== WM_LBUTTONDBLCLK
) ||
2842 (msg
== WM_RBUTTONDBLCLK
) ||
2843 (msg
== WM_MBUTTONDBLCLK
) ||
2844 (msg
== WM_XBUTTONDBLCLK
))
2846 msg
-= (WM_LBUTTONDBLCLK
- WM_LBUTTONDOWN
);
2849 if ((msg
== WM_LBUTTONDOWN
) ||
2850 (msg
== WM_RBUTTONDOWN
) ||
2851 (msg
== WM_MBUTTONDOWN
) ||
2852 (msg
== WM_XBUTTONDOWN
))
2854 static MSG prevClickMsg
;
2856 /* Compare the editor instead of the hwnd so that the this
2857 * can still be done for windowless richedit controls. */
2858 clickMsg
.hwnd
= (HWND
)editor
;
2859 clickMsg
.message
= msg
;
2860 clickMsg
.wParam
= wParam
;
2861 clickMsg
.lParam
= lParam
;
2862 clickMsg
.time
= GetMessageTime();
2863 clickMsg
.pt
.x
= (short)LOWORD(lParam
);
2864 clickMsg
.pt
.y
= (short)HIWORD(lParam
);
2865 if ((clickNum
!= 0) &&
2866 (clickMsg
.message
== prevClickMsg
.message
) &&
2867 (clickMsg
.hwnd
== prevClickMsg
.hwnd
) &&
2868 (clickMsg
.wParam
== prevClickMsg
.wParam
) &&
2869 (clickMsg
.time
- prevClickMsg
.time
< GetDoubleClickTime()) &&
2870 (abs(clickMsg
.pt
.x
- prevClickMsg
.pt
.x
) < GetSystemMetrics(SM_CXDOUBLECLK
)/2) &&
2871 (abs(clickMsg
.pt
.y
- prevClickMsg
.pt
.y
) < GetSystemMetrics(SM_CYDOUBLECLK
)/2))
2877 prevClickMsg
= clickMsg
;
2884 static BOOL
is_link( ME_Run
*run
)
2886 return (run
->style
->fmt
.dwMask
& CFM_LINK
) && (run
->style
->fmt
.dwEffects
& CFE_LINK
);
2889 static BOOL
ME_SetCursor(ME_TextEditor
*editor
)
2895 DWORD messagePos
= GetMessagePos();
2896 pt
.x
= (short)LOWORD(messagePos
);
2897 pt
.y
= (short)HIWORD(messagePos
);
2901 sbi
.cbSize
= sizeof(sbi
);
2902 GetScrollBarInfo(editor
->hWnd
, OBJID_HSCROLL
, &sbi
);
2903 if (!(sbi
.rgstate
[0] & (STATE_SYSTEM_INVISIBLE
|STATE_SYSTEM_OFFSCREEN
)) &&
2904 PtInRect(&sbi
.rcScrollBar
, pt
))
2906 ITextHost_TxSetCursor(editor
->texthost
,
2907 LoadCursorW(NULL
, (WCHAR
*)IDC_ARROW
), FALSE
);
2910 sbi
.cbSize
= sizeof(sbi
);
2911 GetScrollBarInfo(editor
->hWnd
, OBJID_VSCROLL
, &sbi
);
2912 if (!(sbi
.rgstate
[0] & (STATE_SYSTEM_INVISIBLE
|STATE_SYSTEM_OFFSCREEN
)) &&
2913 PtInRect(&sbi
.rcScrollBar
, pt
))
2915 ITextHost_TxSetCursor(editor
->texthost
,
2916 LoadCursorW(NULL
, (WCHAR
*)IDC_ARROW
), FALSE
);
2920 ITextHost_TxScreenToClient(editor
->texthost
, &pt
);
2922 if (editor
->nSelectionType
== stLine
&& editor
->bMouseCaptured
) {
2923 ITextHost_TxSetCursor(editor
->texthost
, hLeft
, FALSE
);
2926 if (!editor
->bEmulateVersion10
/* v4.1 */ &&
2927 pt
.y
< editor
->rcFormat
.top
&&
2928 pt
.x
< editor
->rcFormat
.left
)
2930 ITextHost_TxSetCursor(editor
->texthost
, hLeft
, FALSE
);
2933 if (pt
.y
< editor
->rcFormat
.top
|| pt
.y
> editor
->rcFormat
.bottom
)
2935 if (editor
->bEmulateVersion10
) /* v1.0 - 3.0 */
2936 ITextHost_TxSetCursor(editor
->texthost
,
2937 LoadCursorW(NULL
, (WCHAR
*)IDC_ARROW
), FALSE
);
2939 ITextHost_TxSetCursor(editor
->texthost
,
2940 LoadCursorW(NULL
, (WCHAR
*)IDC_IBEAM
), TRUE
);
2943 if (pt
.x
< editor
->rcFormat
.left
)
2945 ITextHost_TxSetCursor(editor
->texthost
, hLeft
, FALSE
);
2948 ME_CharFromPos(editor
, pt
.x
, pt
.y
, &cursor
, &isExact
);
2953 run
= &cursor
.pRun
->member
.run
;
2956 ITextHost_TxSetCursor(editor
->texthost
,
2957 LoadCursorW(NULL
, (WCHAR
*)IDC_HAND
),
2962 if (ME_IsSelection(editor
))
2964 int selStart
, selEnd
;
2965 int offset
= ME_GetCursorOfs(&cursor
);
2967 ME_GetSelectionOfs(editor
, &selStart
, &selEnd
);
2968 if (selStart
<= offset
&& selEnd
>= offset
) {
2969 ITextHost_TxSetCursor(editor
->texthost
,
2970 LoadCursorW(NULL
, (WCHAR
*)IDC_ARROW
),
2976 ITextHost_TxSetCursor(editor
->texthost
,
2977 LoadCursorW(NULL
, (WCHAR
*)IDC_IBEAM
), TRUE
);
2981 static void ME_SetDefaultFormatRect(ME_TextEditor
*editor
)
2983 ITextHost_TxGetClientRect(editor
->texthost
, &editor
->rcFormat
);
2984 editor
->rcFormat
.top
+= editor
->exStyleFlags
& WS_EX_CLIENTEDGE
? 1 : 0;
2985 editor
->rcFormat
.left
+= 1 + editor
->selofs
;
2986 editor
->rcFormat
.right
-= 1;
2989 static LONG
ME_GetSelectionType(ME_TextEditor
*editor
)
2991 LONG sel_type
= SEL_EMPTY
;
2994 ME_GetSelectionOfs(editor
, &start
, &end
);
2996 sel_type
= SEL_EMPTY
;
2999 LONG object_count
= 0, character_count
= 0;
3002 for (i
= 0; i
< end
- start
; i
++)
3006 ME_CursorFromCharOfs(editor
, start
+ i
, &cursor
);
3007 if (cursor
.pRun
->member
.run
.reobj
)
3011 if (character_count
>= 2 && object_count
>= 2)
3012 return (SEL_TEXT
| SEL_MULTICHAR
| SEL_OBJECT
| SEL_MULTIOBJECT
);
3014 if (character_count
)
3016 sel_type
|= SEL_TEXT
;
3017 if (character_count
>= 2)
3018 sel_type
|= SEL_MULTICHAR
;
3022 sel_type
|= SEL_OBJECT
;
3023 if (object_count
>= 2)
3024 sel_type
|= SEL_MULTIOBJECT
;
3030 static BOOL
ME_ShowContextMenu(ME_TextEditor
*editor
, int x
, int y
)
3036 if(!editor
->lpOleCallback
|| !editor
->hWnd
)
3038 ME_GetSelectionOfs(editor
, &selrange
.cpMin
, &selrange
.cpMax
);
3039 seltype
= ME_GetSelectionType(editor
);
3040 if(SUCCEEDED(IRichEditOleCallback_GetContextMenu(editor
->lpOleCallback
, seltype
, NULL
, &selrange
, &menu
)))
3042 TrackPopupMenu(menu
, TPM_LEFTALIGN
| TPM_RIGHTBUTTON
, x
, y
, 0, editor
->hwndParent
, NULL
);
3048 ME_TextEditor
*ME_MakeEditor(ITextHost
*texthost
, BOOL bEmulateVersion10
)
3050 ME_TextEditor
*ed
= heap_alloc(sizeof(*ed
));
3056 ed
->hwndParent
= NULL
;
3057 ed
->sizeWindow
.cx
= ed
->sizeWindow
.cy
= 0;
3058 ed
->texthost
= texthost
;
3060 ed
->bEmulateVersion10
= bEmulateVersion10
;
3062 ed
->exStyleFlags
= 0;
3063 ed
->first_marked_para
= NULL
;
3065 ITextHost_TxGetPropertyBits(texthost
,
3066 (TXTBIT_RICHTEXT
|TXTBIT_MULTILINE
|
3067 TXTBIT_READONLY
|TXTBIT_USEPASSWORD
|
3068 TXTBIT_HIDESELECTION
|TXTBIT_SAVESELECTION
|
3069 TXTBIT_AUTOWORDSEL
|TXTBIT_VERTICAL
|
3070 TXTBIT_WORDWRAP
|TXTBIT_DISABLEDRAG
),
3072 ITextHost_TxGetScrollBars(texthost
, &ed
->styleFlags
);
3073 ed
->styleFlags
&= (WS_VSCROLL
|WS_HSCROLL
|ES_AUTOVSCROLL
|
3074 ES_AUTOHSCROLL
|ES_DISABLENOSCROLL
);
3075 ed
->pBuffer
= ME_MakeText();
3076 ed
->nZoomNumerator
= ed
->nZoomDenominator
= 0;
3077 ed
->nAvailWidth
= 0; /* wrap to client area */
3078 list_init( &ed
->style_list
);
3079 ME_MakeFirstParagraph(ed
);
3080 /* The four cursors are for:
3081 * 0 - The position where the caret is shown
3082 * 1 - The anchored end of the selection (for normal selection)
3083 * 2 & 3 - The anchored start and end respectively for word, line,
3084 * or paragraph selection.
3087 ed
->pCursors
= heap_alloc(ed
->nCursors
* sizeof(*ed
->pCursors
));
3088 ME_SetCursorToStart(ed
, &ed
->pCursors
[0]);
3089 ed
->pCursors
[1] = ed
->pCursors
[0];
3090 ed
->pCursors
[2] = ed
->pCursors
[0];
3091 ed
->pCursors
[3] = ed
->pCursors
[1];
3092 ed
->nLastTotalLength
= ed
->nTotalLength
= 0;
3093 ed
->nLastTotalWidth
= ed
->nTotalWidth
= 0;
3095 ed
->rgbBackColor
= -1;
3096 ed
->hbrBackground
= GetSysColorBrush(COLOR_WINDOW
);
3097 ed
->bCaretAtEnd
= FALSE
;
3099 ed
->nModifyStep
= 0;
3100 ed
->nTextLimit
= TEXT_LIMIT_DEFAULT
;
3101 list_init( &ed
->undo_stack
);
3102 list_init( &ed
->redo_stack
);
3103 ed
->nUndoStackSize
= 0;
3104 ed
->nUndoLimit
= STACK_SIZE_DEFAULT
;
3105 ed
->nUndoMode
= umAddToUndo
;
3106 ed
->nParagraphs
= 1;
3107 ed
->nLastSelStart
= ed
->nLastSelEnd
= 0;
3108 ed
->pLastSelStartPara
= ed
->pLastSelEndPara
= ed
->pCursors
[0].pPara
;
3109 ed
->bHideSelection
= FALSE
;
3110 ed
->pfnWordBreak
= NULL
;
3111 ed
->lpOleCallback
= NULL
;
3112 ed
->mode
= TM_MULTILEVELUNDO
| TM_MULTICODEPAGE
;
3113 ed
->mode
|= (props
& TXTBIT_RICHTEXT
) ? TM_RICHTEXT
: TM_PLAINTEXT
;
3114 ed
->AutoURLDetect_bEnable
= FALSE
;
3115 ed
->bHaveFocus
= FALSE
;
3116 ed
->bDialogMode
= FALSE
;
3117 ed
->bMouseCaptured
= FALSE
;
3118 ed
->caret_hidden
= FALSE
;
3119 ed
->caret_height
= 0;
3120 for (i
=0; i
<HFONT_CACHE_SIZE
; i
++)
3122 ed
->pFontCache
[i
].nRefs
= 0;
3123 ed
->pFontCache
[i
].nAge
= 0;
3124 ed
->pFontCache
[i
].hFont
= NULL
;
3127 ME_CheckCharOffsets(ed
);
3128 SetRectEmpty(&ed
->rcFormat
);
3129 ed
->bDefaultFormatRect
= TRUE
;
3130 ITextHost_TxGetSelectionBarWidth(ed
->texthost
, &selbarwidth
);
3132 /* FIXME: Convert selbarwidth from HIMETRIC to pixels */
3133 ed
->selofs
= SELECTIONBAR_WIDTH
;
3134 ed
->styleFlags
|= ES_SELECTIONBAR
;
3138 ed
->nSelectionType
= stPosition
;
3140 ed
->cPasswordMask
= 0;
3141 if (props
& TXTBIT_USEPASSWORD
)
3142 ITextHost_TxGetPasswordChar(texthost
, &ed
->cPasswordMask
);
3144 if (props
& TXTBIT_AUTOWORDSEL
)
3145 ed
->styleFlags
|= ECO_AUTOWORDSELECTION
;
3146 if (props
& TXTBIT_MULTILINE
) {
3147 ed
->styleFlags
|= ES_MULTILINE
;
3148 ed
->bWordWrap
= (props
& TXTBIT_WORDWRAP
) != 0;
3150 ed
->bWordWrap
= FALSE
;
3152 if (props
& TXTBIT_READONLY
)
3153 ed
->styleFlags
|= ES_READONLY
;
3154 if (!(props
& TXTBIT_HIDESELECTION
))
3155 ed
->styleFlags
|= ES_NOHIDESEL
;
3156 if (props
& TXTBIT_SAVESELECTION
)
3157 ed
->styleFlags
|= ES_SAVESEL
;
3158 if (props
& TXTBIT_VERTICAL
)
3159 ed
->styleFlags
|= ES_VERTICAL
;
3160 if (props
& TXTBIT_DISABLEDRAG
)
3161 ed
->styleFlags
|= ES_NOOLEDRAGDROP
;
3163 ed
->notified_cr
.cpMin
= ed
->notified_cr
.cpMax
= 0;
3165 /* Default scrollbar information */
3166 ed
->vert_si
.cbSize
= sizeof(SCROLLINFO
);
3167 ed
->vert_si
.nMin
= 0;
3168 ed
->vert_si
.nMax
= 0;
3169 ed
->vert_si
.nPage
= 0;
3170 ed
->vert_si
.nPos
= 0;
3172 ed
->horz_si
.cbSize
= sizeof(SCROLLINFO
);
3173 ed
->horz_si
.nMin
= 0;
3174 ed
->horz_si
.nMax
= 0;
3175 ed
->horz_si
.nPage
= 0;
3176 ed
->horz_si
.nPos
= 0;
3178 ed
->wheel_remain
= 0;
3180 list_init( &ed
->reobj_list
);
3181 OleInitialize(NULL
);
3186 void ME_DestroyEditor(ME_TextEditor
*editor
)
3188 ME_DisplayItem
*p
= editor
->pBuffer
->pFirst
, *pNext
= NULL
;
3189 ME_Style
*s
, *cursor2
;
3192 ME_ClearTempStyle(editor
);
3193 ME_EmptyUndoStack(editor
);
3194 editor
->pBuffer
->pFirst
= NULL
;
3197 if (p
->type
== diParagraph
)
3198 destroy_para(editor
, p
);
3200 ME_DestroyDisplayItem(p
);
3204 LIST_FOR_EACH_ENTRY_SAFE( s
, cursor2
, &editor
->style_list
, ME_Style
, entry
)
3205 ME_DestroyStyle( s
);
3207 ME_ReleaseStyle(editor
->pBuffer
->pDefaultStyle
);
3208 for (i
=0; i
<HFONT_CACHE_SIZE
; i
++)
3210 if (editor
->pFontCache
[i
].hFont
)
3211 DeleteObject(editor
->pFontCache
[i
].hFont
);
3213 if (editor
->rgbBackColor
!= -1)
3214 DeleteObject(editor
->hbrBackground
);
3215 if(editor
->lpOleCallback
)
3216 IRichEditOleCallback_Release(editor
->lpOleCallback
);
3217 ITextHost_Release(editor
->texthost
);
3220 IUnknown_Release(editor
->reOle
);
3221 editor
->reOle
= NULL
;
3225 heap_free(editor
->pBuffer
);
3226 heap_free(editor
->pCursors
);
3230 BOOL WINAPI
DllMain(HINSTANCE hinstDLL
, DWORD fdwReason
, LPVOID lpvReserved
)
3235 case DLL_PROCESS_ATTACH
:
3236 DisableThreadLibraryCalls(hinstDLL
);
3237 me_heap
= HeapCreate (0, 0x10000, 0);
3238 if (!ME_RegisterEditorClass(hinstDLL
)) return FALSE
;
3239 hLeft
= LoadCursorW(hinstDLL
, MAKEINTRESOURCEW(OCR_REVERSE
));
3243 case DLL_PROCESS_DETACH
:
3244 if (lpvReserved
) break;
3245 UnregisterClassW(RICHEDIT_CLASS20W
, 0);
3246 UnregisterClassW(MSFTEDIT_CLASS
, 0);
3247 UnregisterClassA(RICHEDIT_CLASS20A
, 0);
3248 UnregisterClassA("RichEdit50A", 0);
3249 if (ME_ListBoxRegistered
)
3250 UnregisterClassW(REListBox20W
, 0);
3251 if (ME_ComboBoxRegistered
)
3252 UnregisterClassW(REComboBox20W
, 0);
3254 HeapDestroy (me_heap
);
3261 static inline int get_default_line_height( ME_TextEditor
*editor
)
3265 if (editor
->pBuffer
&& editor
->pBuffer
->pDefaultStyle
)
3266 height
= editor
->pBuffer
->pDefaultStyle
->tm
.tmHeight
;
3267 if (height
<= 0) height
= 24;
3272 static inline int calc_wheel_change( int *remain
, int amount_per_click
)
3274 int change
= amount_per_click
* (float)*remain
/ WHEEL_DELTA
;
3275 *remain
-= WHEEL_DELTA
* change
/ amount_per_click
;
3279 static const char * const edit_messages
[] = {
3308 "EM_SETPASSWORDCHAR",
3309 "EM_EMPTYUNDOBUFFER",
3310 "EM_GETFIRSTVISIBLELINE",
3312 "EM_SETWORDBREAKPROC",
3313 "EM_GETWORDBREAKPROC",
3314 "EM_GETPASSWORDCHAR",
3324 static const char * const richedit_messages
[] = {
3329 "EM_EXLINEFROMCHAR",
3335 "EM_GETOLEINTERFACE",
3345 "EM_SETOLECALLBACK",
3347 "EM_SETTARGETDEVICE",
3355 "EM_GETWORDBREAKPROCEX",
3356 "EM_SETWORDBREAKPROCEX",
3358 "EM_UNKNOWN_USER_83",
3363 "EM_STOPGROUPTYPING",
3367 "EM_GETAUTOURLDETECT",
3370 "EM_GETTEXTLENGTHEX",
3373 "EM_UNKNOWN_USER_98",
3374 "EM_UNKNOWN_USER_99",
3375 "EM_SETPUNCTUATION",
3376 "EM_GETPUNCTUATION",
3377 "EM_SETWORDWRAPMODE",
3378 "EM_GETWORDWRAPMODE",
3384 "EM_UNKNOWN_USER_109",
3385 "EM_UNKNOWN_USER_110",
3386 "EM_UNKNOWN_USER_111",
3387 "EM_UNKNOWN_USER_112",
3388 "EM_UNKNOWN_USER_113",
3389 "EM_UNKNOWN_USER_114",
3390 "EM_UNKNOWN_USER_115",
3391 "EM_UNKNOWN_USER_116",
3392 "EM_UNKNOWN_USER_117",
3393 "EM_UNKNOWN_USER_118",
3394 "EM_UNKNOWN_USER_119",
3395 "EM_SETLANGOPTIONS",
3396 "EM_GETLANGOPTIONS",
3397 "EM_GETIMECOMPMODE",
3401 "EM_SETIMEMODEBIAS",
3406 get_msg_name(UINT msg
)
3408 if (msg
>= EM_GETSEL
&& msg
<= EM_CHARFROMPOS
)
3409 return edit_messages
[msg
- EM_GETSEL
];
3410 if (msg
>= EM_CANPASTE
&& msg
<= EM_GETIMEMODEBIAS
)
3411 return richedit_messages
[msg
- EM_CANPASTE
];
3415 static void ME_LinkNotify(ME_TextEditor
*editor
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
3419 ME_Cursor cursor
; /* The start of the clicked text. */
3422 x
= (short)LOWORD(lParam
);
3423 y
= (short)HIWORD(lParam
);
3424 ME_CharFromPos(editor
, x
, y
, &cursor
, &isExact
);
3425 if (!isExact
) return;
3427 if (is_link( &cursor
.pRun
->member
.run
))
3428 { /* The clicked run has CFE_LINK set */
3431 info
.nmhdr
.hwndFrom
= NULL
;
3432 info
.nmhdr
.idFrom
= 0;
3433 info
.nmhdr
.code
= EN_LINK
;
3435 info
.wParam
= wParam
;
3436 info
.lParam
= lParam
;
3439 /* find the first contiguous run with CFE_LINK set */
3440 info
.chrg
.cpMin
= ME_GetCursorOfs(&cursor
);
3442 while (ME_PrevRun( NULL
, &di
, FALSE
) && is_link( &di
->member
.run
))
3443 info
.chrg
.cpMin
-= di
->member
.run
.len
;
3445 /* find the last contiguous run with CFE_LINK set */
3446 info
.chrg
.cpMax
= ME_GetCursorOfs(&cursor
) + cursor
.pRun
->member
.run
.len
;
3448 while (ME_NextRun( NULL
, &di
, FALSE
) && is_link( &di
->member
.run
))
3449 info
.chrg
.cpMax
+= di
->member
.run
.len
;
3451 ITextHost_TxNotify(editor
->texthost
, info
.nmhdr
.code
, &info
);
3455 void ME_ReplaceSel(ME_TextEditor
*editor
, BOOL can_undo
, const WCHAR
*str
, int len
)
3457 int from
, to
, nStartCursor
;
3460 nStartCursor
= ME_GetSelectionOfs(editor
, &from
, &to
);
3461 style
= ME_GetSelectionInsertStyle(editor
);
3462 ME_InternalDeleteText(editor
, &editor
->pCursors
[nStartCursor
], to
-from
, FALSE
);
3463 ME_InsertTextFromCursor(editor
, 0, str
, len
, style
);
3464 ME_ReleaseStyle(style
);
3465 /* drop temporary style if line end */
3467 * FIXME question: does abc\n mean: put abc,
3468 * clear temp style, put \n? (would require a change)
3470 if (len
>0 && str
[len
-1] == '\n')
3471 ME_ClearTempStyle(editor
);
3472 ME_CommitUndo(editor
);
3473 ME_UpdateSelectionLinkAttribute(editor
);
3475 ME_EmptyUndoStack(editor
);
3476 ME_UpdateRepaint(editor
, FALSE
);
3479 static void ME_SetText(ME_TextEditor
*editor
, void *text
, BOOL unicode
)
3481 LONG codepage
= unicode
? CP_UNICODE
: CP_ACP
;
3484 LPWSTR wszText
= ME_ToUnicode(codepage
, text
, &textLen
);
3485 ME_InsertTextFromCursor(editor
, 0, wszText
, textLen
, editor
->pBuffer
->pDefaultStyle
);
3486 ME_EndToUnicode(codepage
, wszText
);
3489 static LRESULT
ME_WmCreate(ME_TextEditor
*editor
, LPARAM lParam
, BOOL unicode
)
3491 CREATESTRUCTW
*createW
= (CREATESTRUCTW
*)lParam
;
3492 CREATESTRUCTA
*createA
= (CREATESTRUCTA
*)lParam
;
3497 text
= unicode
? (void*)createW
->lpszName
: (void*)createA
->lpszName
;
3499 ME_SetDefaultFormatRect(editor
);
3501 max
= (editor
->styleFlags
& ES_DISABLENOSCROLL
) ? 1 : 0;
3502 if (~editor
->styleFlags
& ES_DISABLENOSCROLL
|| editor
->styleFlags
& WS_VSCROLL
)
3503 ITextHost_TxSetScrollRange(editor
->texthost
, SB_VERT
, 0, max
, TRUE
);
3505 if (~editor
->styleFlags
& ES_DISABLENOSCROLL
|| editor
->styleFlags
& WS_HSCROLL
)
3506 ITextHost_TxSetScrollRange(editor
->texthost
, SB_HORZ
, 0, max
, TRUE
);
3508 if (editor
->styleFlags
& ES_DISABLENOSCROLL
)
3510 if (editor
->styleFlags
& WS_VSCROLL
)
3512 ITextHost_TxEnableScrollBar(editor
->texthost
, SB_VERT
, ESB_DISABLE_BOTH
);
3513 ITextHost_TxShowScrollBar(editor
->texthost
, SB_VERT
, TRUE
);
3515 if (editor
->styleFlags
& WS_HSCROLL
)
3517 ITextHost_TxEnableScrollBar(editor
->texthost
, SB_HORZ
, ESB_DISABLE_BOTH
);
3518 ITextHost_TxShowScrollBar(editor
->texthost
, SB_HORZ
, TRUE
);
3524 ME_SetText(editor
, text
, unicode
);
3525 ME_SetCursorToStart(editor
, &editor
->pCursors
[0]);
3526 ME_SetCursorToStart(editor
, &editor
->pCursors
[1]);
3529 ME_CommitUndo(editor
);
3530 ME_WrapMarkedParagraphs(editor
);
3531 update_caret(editor
);
3535 static LRESULT
handle_EM_SETCHARFORMAT( ME_TextEditor
*editor
, WPARAM flags
, const CHARFORMAT2W
*fmt_in
)
3538 BOOL changed
= TRUE
;
3539 ME_Cursor start
, end
;
3541 if (!cfany_to_cf2w( &fmt
, fmt_in
)) return 0;
3543 if (flags
& SCF_ALL
)
3545 if (editor
->mode
& TM_PLAINTEXT
)
3547 ME_SetDefaultCharFormat( editor
, &fmt
);
3551 ME_SetCursorToStart( editor
, &start
);
3552 ME_SetCharFormat( editor
, &start
, NULL
, &fmt
);
3553 editor
->nModifyStep
= 1;
3556 else if (flags
& SCF_SELECTION
)
3558 if (editor
->mode
& TM_PLAINTEXT
) return 0;
3559 if (flags
& SCF_WORD
)
3561 end
= editor
->pCursors
[0];
3562 ME_MoveCursorWords( editor
, &end
, +1 );
3564 ME_MoveCursorWords( editor
, &start
, -1 );
3565 ME_SetCharFormat( editor
, &start
, &end
, &fmt
);
3567 changed
= ME_IsSelection( editor
);
3568 ME_SetSelectionCharFormat( editor
, &fmt
);
3569 if (changed
) editor
->nModifyStep
= 1;
3571 else /* SCF_DEFAULT */
3573 ME_SetDefaultCharFormat( editor
, &fmt
);
3576 ME_CommitUndo( editor
);
3579 ME_WrapMarkedParagraphs( editor
);
3580 ME_UpdateScrollBar( editor
);
3585 #define UNSUPPORTED_MSG(e) \
3587 FIXME(#e ": stub\n"); \
3588 *phresult = S_FALSE; \
3591 /* Handle messages for windowless and windowed richedit controls.
3593 * The LRESULT that is returned is a return value for window procs,
3594 * and the phresult parameter is the COM return code needed by the
3595 * text services interface. */
3596 LRESULT
ME_HandleMessage(ME_TextEditor
*editor
, UINT msg
, WPARAM wParam
,
3597 LPARAM lParam
, BOOL unicode
, HRESULT
* phresult
)
3603 UNSUPPORTED_MSG(EM_DISPLAYBAND
)
3604 UNSUPPORTED_MSG(EM_FINDWORDBREAK
)
3605 UNSUPPORTED_MSG(EM_FMTLINES
)
3606 UNSUPPORTED_MSG(EM_FORMATRANGE
)
3607 UNSUPPORTED_MSG(EM_GETBIDIOPTIONS
)
3608 UNSUPPORTED_MSG(EM_GETEDITSTYLE
)
3609 UNSUPPORTED_MSG(EM_GETIMECOMPMODE
)
3610 UNSUPPORTED_MSG(EM_GETIMESTATUS
)
3611 UNSUPPORTED_MSG(EM_SETIMESTATUS
)
3612 UNSUPPORTED_MSG(EM_GETLANGOPTIONS
)
3613 UNSUPPORTED_MSG(EM_GETREDONAME
)
3614 UNSUPPORTED_MSG(EM_GETTYPOGRAPHYOPTIONS
)
3615 UNSUPPORTED_MSG(EM_GETUNDONAME
)
3616 UNSUPPORTED_MSG(EM_GETWORDBREAKPROCEX
)
3617 UNSUPPORTED_MSG(EM_SETBIDIOPTIONS
)
3618 UNSUPPORTED_MSG(EM_SETEDITSTYLE
)
3619 UNSUPPORTED_MSG(EM_SETLANGOPTIONS
)
3620 UNSUPPORTED_MSG(EM_SETMARGINS
)
3621 UNSUPPORTED_MSG(EM_SETPALETTE
)
3622 UNSUPPORTED_MSG(EM_SETTABSTOPS
)
3623 UNSUPPORTED_MSG(EM_SETTYPOGRAPHYOPTIONS
)
3624 UNSUPPORTED_MSG(EM_SETWORDBREAKPROCEX
)
3626 /* Messages specific to Richedit controls */
3629 return ME_StreamIn(editor
, wParam
, (EDITSTREAM
*)lParam
, TRUE
);
3631 return ME_StreamOut(editor
, wParam
, (EDITSTREAM
*)lParam
);
3634 UINT code
= DLGC_WANTCHARS
|DLGC_WANTTAB
|DLGC_WANTARROWS
;
3637 editor
->bDialogMode
= TRUE
;
3638 if (editor
->styleFlags
& ES_MULTILINE
)
3639 code
|= DLGC_WANTMESSAGE
;
3640 if (!(editor
->styleFlags
& ES_SAVESEL
))
3641 code
|= DLGC_HASSETSEL
;
3644 case EM_EMPTYUNDOBUFFER
:
3645 ME_EmptyUndoStack(editor
);
3649 /* Note: wParam/lParam can be NULL */
3651 PUINT pfrom
= wParam
? (PUINT
)wParam
: &from
;
3652 PUINT pto
= lParam
? (PUINT
)lParam
: &to
;
3653 ME_GetSelectionOfs(editor
, (int *)pfrom
, (int *)pto
);
3654 if ((*pfrom
|*pto
) & 0xFFFF0000)
3656 return MAKELONG(*pfrom
,*pto
);
3660 CHARRANGE
*pRange
= (CHARRANGE
*)lParam
;
3661 ME_GetSelectionOfs(editor
, &pRange
->cpMin
, &pRange
->cpMax
);
3662 TRACE("EM_EXGETSEL = (%d,%d)\n", pRange
->cpMin
, pRange
->cpMax
);
3665 case EM_SETUNDOLIMIT
:
3667 if ((int)wParam
< 0)
3668 editor
->nUndoLimit
= STACK_SIZE_DEFAULT
;
3670 editor
->nUndoLimit
= min(wParam
, STACK_SIZE_MAX
);
3671 /* Setting a max stack size keeps wine from getting killed
3672 for hogging memory. Windows allocates all this memory at once, so
3673 no program would realistically set a value above our maximum. */
3674 return editor
->nUndoLimit
;
3677 return !list_empty( &editor
->undo_stack
);
3679 return !list_empty( &editor
->redo_stack
);
3680 case WM_UNDO
: /* FIXME: actually not the same */
3682 return ME_Undo(editor
);
3684 return ME_Redo(editor
);
3687 /* these flags are equivalent to the ES_* counterparts */
3688 DWORD mask
= ECO_VERTICAL
| ECO_AUTOHSCROLL
| ECO_AUTOVSCROLL
|
3689 ECO_NOHIDESEL
| ECO_READONLY
| ECO_WANTRETURN
| ECO_SELECTIONBAR
;
3690 DWORD settings
= editor
->styleFlags
& mask
;
3694 case EM_SETFONTSIZE
:
3697 LONG tmp_size
, size
;
3698 BOOL is_increase
= ((LONG
)wParam
> 0);
3700 if (editor
->mode
& TM_PLAINTEXT
)
3703 cf
.cbSize
= sizeof(cf
);
3704 cf
.dwMask
= CFM_SIZE
;
3705 ME_GetSelectionCharFormat(editor
, &cf
);
3706 tmp_size
= (cf
.yHeight
/ 20) + wParam
;
3710 else if (tmp_size
> 12 && tmp_size
< 28 && tmp_size
% 2)
3711 size
= tmp_size
+ (is_increase
? 1 : -1);
3712 else if (tmp_size
> 28 && tmp_size
< 36)
3713 size
= is_increase
? 36 : 28;
3714 else if (tmp_size
> 36 && tmp_size
< 48)
3715 size
= is_increase
? 48 : 36;
3716 else if (tmp_size
> 48 && tmp_size
< 72)
3717 size
= is_increase
? 72 : 48;
3718 else if (tmp_size
> 72 && tmp_size
< 80)
3719 size
= is_increase
? 80 : 72;
3720 else if (tmp_size
> 80 && tmp_size
< 1638)
3721 size
= 10 * (is_increase
? (tmp_size
/ 10 + 1) : (tmp_size
/ 10));
3722 else if (tmp_size
>= 1638)
3727 cf
.yHeight
= size
* 20; /* convert twips to points */
3728 ME_SetSelectionCharFormat(editor
, &cf
);
3729 ME_CommitUndo(editor
);
3730 ME_WrapMarkedParagraphs(editor
);
3731 ME_UpdateScrollBar(editor
);
3737 /* these flags are equivalent to ES_* counterparts, except for
3738 * ECO_AUTOWORDSELECTION that doesn't have an ES_* counterpart,
3739 * but is still stored in editor->styleFlags. */
3740 const DWORD mask
= ECO_VERTICAL
| ECO_AUTOHSCROLL
| ECO_AUTOVSCROLL
|
3741 ECO_NOHIDESEL
| ECO_READONLY
| ECO_WANTRETURN
|
3742 ECO_SELECTIONBAR
| ECO_AUTOWORDSELECTION
;
3743 DWORD settings
= mask
& editor
->styleFlags
;
3744 DWORD oldSettings
= settings
;
3745 DWORD changedSettings
;
3761 changedSettings
= oldSettings
^ settings
;
3763 if (changedSettings
) {
3764 editor
->styleFlags
= (editor
->styleFlags
& ~mask
) | (settings
& mask
);
3766 if (changedSettings
& ECO_SELECTIONBAR
)
3768 ITextHost_TxInvalidateRect(editor
->texthost
, &editor
->rcFormat
, TRUE
);
3769 if (settings
& ECO_SELECTIONBAR
) {
3770 assert(!editor
->selofs
);
3771 editor
->selofs
= SELECTIONBAR_WIDTH
;
3772 editor
->rcFormat
.left
+= editor
->selofs
;
3774 editor
->rcFormat
.left
-= editor
->selofs
;
3777 ME_RewrapRepaint(editor
);
3780 if ((changedSettings
& settings
& ES_NOHIDESEL
) && !editor
->bHaveFocus
)
3781 ME_InvalidateSelection( editor
);
3783 if (changedSettings
& settings
& ECO_VERTICAL
)
3784 FIXME("ECO_VERTICAL not implemented yet!\n");
3785 if (changedSettings
& settings
& ECO_AUTOHSCROLL
)
3786 FIXME("ECO_AUTOHSCROLL not implemented yet!\n");
3787 if (changedSettings
& settings
& ECO_AUTOVSCROLL
)
3788 FIXME("ECO_AUTOVSCROLL not implemented yet!\n");
3789 if (changedSettings
& settings
& ECO_WANTRETURN
)
3790 FIXME("ECO_WANTRETURN not implemented yet!\n");
3791 if (changedSettings
& settings
& ECO_AUTOWORDSELECTION
)
3792 FIXME("ECO_AUTOWORDSELECTION not implemented yet!\n");
3799 return set_selection( editor
, wParam
, lParam
);
3801 case EM_SETSCROLLPOS
:
3803 POINT
*point
= (POINT
*)lParam
;
3804 ME_ScrollAbs(editor
, point
->x
, point
->y
);
3807 case EM_AUTOURLDETECT
:
3809 if (wParam
==1 || wParam
==0)
3811 editor
->AutoURLDetect_bEnable
= (BOOL
)wParam
;
3814 return E_INVALIDARG
;
3816 case EM_GETAUTOURLDETECT
:
3818 return editor
->AutoURLDetect_bEnable
;
3822 CHARRANGE range
= *(CHARRANGE
*)lParam
;
3824 return set_selection( editor
, range
.cpMin
, range
.cpMax
);
3826 case EM_SHOWSCROLLBAR
:
3839 flags
= WS_HSCROLL
|WS_VSCROLL
;
3846 editor
->styleFlags
|= flags
;
3847 if (flags
& WS_HSCROLL
)
3848 ITextHost_TxShowScrollBar(editor
->texthost
, SB_HORZ
,
3849 editor
->nTotalWidth
> editor
->sizeWindow
.cx
);
3850 if (flags
& WS_VSCROLL
)
3851 ITextHost_TxShowScrollBar(editor
->texthost
, SB_VERT
,
3852 editor
->nTotalLength
> editor
->sizeWindow
.cy
);
3854 editor
->styleFlags
&= ~flags
;
3855 ITextHost_TxShowScrollBar(editor
->texthost
, wParam
, FALSE
);
3862 SETTEXTEX
*pStruct
= (SETTEXTEX
*)wParam
;
3865 BOOL bRtf
, bUnicode
, bSelection
, bUTF8
;
3866 int oldModify
= editor
->nModifyStep
;
3867 static const char utf8_bom
[] = {0xef, 0xbb, 0xbf};
3869 if (!pStruct
) return 0;
3871 /* If we detect ascii rtf at the start of the string,
3872 * we know it isn't unicode. */
3873 bRtf
= (lParam
&& (!strncmp((char *)lParam
, "{\\rtf", 5) ||
3874 !strncmp((char *)lParam
, "{\\urtf", 6)));
3875 bUnicode
= !bRtf
&& pStruct
->codepage
== CP_UNICODE
;
3876 bUTF8
= (lParam
&& (!strncmp((char *)lParam
, utf8_bom
, 3)));
3878 TRACE("EM_SETTEXTEX - %s, flags %d, cp %d\n",
3879 bUnicode
? debugstr_w((LPCWSTR
)lParam
) : debugstr_a((LPCSTR
)lParam
),
3880 pStruct
->flags
, pStruct
->codepage
);
3882 bSelection
= (pStruct
->flags
& ST_SELECTION
) != 0;
3884 int nStartCursor
= ME_GetSelectionOfs(editor
, &from
, &to
);
3885 style
= ME_GetSelectionInsertStyle(editor
);
3886 ME_InternalDeleteText(editor
, &editor
->pCursors
[nStartCursor
], to
- from
, FALSE
);
3889 ME_SetCursorToStart(editor
, &start
);
3890 ME_InternalDeleteText(editor
, &start
, ME_GetTextLength(editor
), FALSE
);
3891 style
= editor
->pBuffer
->pDefaultStyle
;
3895 ME_StreamInRTFString(editor
, bSelection
, (char *)lParam
);
3897 /* FIXME: The length returned doesn't include the rtf control
3898 * characters, only the actual text. */
3899 len
= lParam
? strlen((char *)lParam
) : 0;
3902 if (bUTF8
&& !bUnicode
) {
3903 wszText
= ME_ToUnicode(CP_UTF8
, (void *)(lParam
+3), &len
);
3904 ME_InsertTextFromCursor(editor
, 0, wszText
, len
, style
);
3905 ME_EndToUnicode(CP_UTF8
, wszText
);
3907 wszText
= ME_ToUnicode(pStruct
->codepage
, (void *)lParam
, &len
);
3908 ME_InsertTextFromCursor(editor
, 0, wszText
, len
, style
);
3909 ME_EndToUnicode(pStruct
->codepage
, wszText
);
3914 ME_ReleaseStyle(style
);
3915 ME_UpdateSelectionLinkAttribute(editor
);
3919 ME_SetCursorToStart(editor
, &cursor
);
3920 ME_UpdateLinkAttribute(editor
, &cursor
, INT_MAX
);
3922 ME_CommitUndo(editor
);
3923 if (!(pStruct
->flags
& ST_KEEPUNDO
))
3925 editor
->nModifyStep
= oldModify
;
3926 ME_EmptyUndoStack(editor
);
3928 ME_UpdateRepaint(editor
, FALSE
);
3931 case EM_SELECTIONTYPE
:
3932 return ME_GetSelectionType(editor
);
3933 case EM_SETBKGNDCOLOR
:
3936 if (editor
->rgbBackColor
!= -1) {
3937 DeleteObject(editor
->hbrBackground
);
3938 lColor
= editor
->rgbBackColor
;
3940 else lColor
= ITextHost_TxGetSysColor(editor
->texthost
, COLOR_WINDOW
);
3944 editor
->rgbBackColor
= -1;
3945 editor
->hbrBackground
= GetSysColorBrush(COLOR_WINDOW
);
3949 editor
->rgbBackColor
= lParam
;
3950 editor
->hbrBackground
= CreateSolidBrush(editor
->rgbBackColor
);
3952 ITextHost_TxInvalidateRect(editor
->texthost
, NULL
, TRUE
);
3956 return editor
->nModifyStep
== 0 ? 0 : -1;
3960 editor
->nModifyStep
= 1;
3962 editor
->nModifyStep
= 0;
3966 case EM_SETREADONLY
:
3969 editor
->styleFlags
|= ES_READONLY
;
3971 editor
->styleFlags
&= ~ES_READONLY
;
3974 case EM_SETEVENTMASK
:
3976 DWORD nOldMask
= editor
->nEventMask
;
3978 editor
->nEventMask
= lParam
;
3981 case EM_GETEVENTMASK
:
3982 return editor
->nEventMask
;
3983 case EM_SETCHARFORMAT
:
3984 return handle_EM_SETCHARFORMAT( editor
, wParam
, (CHARFORMAT2W
*)lParam
);
3985 case EM_GETCHARFORMAT
:
3987 CHARFORMAT2W tmp
, *dst
= (CHARFORMAT2W
*)lParam
;
3988 if (dst
->cbSize
!= sizeof(CHARFORMATA
) &&
3989 dst
->cbSize
!= sizeof(CHARFORMATW
) &&
3990 dst
->cbSize
!= sizeof(CHARFORMAT2A
) &&
3991 dst
->cbSize
!= sizeof(CHARFORMAT2W
))
3993 tmp
.cbSize
= sizeof(tmp
);
3995 ME_GetDefaultCharFormat(editor
, &tmp
);
3997 ME_GetSelectionCharFormat(editor
, &tmp
);
3998 cf2w_to_cfany(dst
, &tmp
);
4001 case EM_SETPARAFORMAT
:
4003 BOOL result
= ME_SetSelectionParaFormat(editor
, (PARAFORMAT2
*)lParam
);
4004 ME_WrapMarkedParagraphs(editor
);
4005 ME_UpdateScrollBar(editor
);
4006 ME_CommitUndo(editor
);
4009 case EM_GETPARAFORMAT
:
4010 ME_GetSelectionParaFormat(editor
, (PARAFORMAT2
*)lParam
);
4011 return ((PARAFORMAT2
*)lParam
)->dwMask
;
4012 case EM_GETFIRSTVISIBLELINE
:
4014 ME_DisplayItem
*p
= editor
->pBuffer
->pFirst
;
4015 int y
= editor
->vert_si
.nPos
;
4020 p
= ME_FindItemFwd(p
, diStartRowOrParagraphOrEnd
);
4021 if (p
->type
== diTextEnd
)
4023 if (p
->type
== diParagraph
) {
4024 ypara
= p
->member
.para
.pt
.y
;
4027 ystart
= ypara
+ p
->member
.row
.pt
.y
;
4028 yend
= ystart
+ p
->member
.row
.nHeight
;
4036 case EM_HIDESELECTION
:
4038 editor
->bHideSelection
= (wParam
!= 0);
4039 ME_InvalidateSelection(editor
);
4044 if (!(editor
->styleFlags
& ES_MULTILINE
))
4046 ME_ScrollDown( editor
, lParam
* get_default_line_height( editor
) );
4052 int nStartCursor
= ME_GetSelectionOfs(editor
, &from
, &to
);
4053 ME_InternalDeleteText(editor
, &editor
->pCursors
[nStartCursor
], to
-from
, FALSE
);
4054 ME_CommitUndo(editor
);
4055 ME_UpdateRepaint(editor
, TRUE
);
4061 LONG codepage
= unicode
? CP_UNICODE
: CP_ACP
;
4062 LPWSTR wszText
= ME_ToUnicode(codepage
, (void *)lParam
, &len
);
4064 TRACE("EM_REPLACESEL - %s\n", debugstr_w(wszText
));
4066 ME_ReplaceSel(editor
, !!wParam
, wszText
, len
);
4067 ME_EndToUnicode(codepage
, wszText
);
4070 case EM_SCROLLCARET
:
4071 ME_EnsureVisible(editor
, &editor
->pCursors
[0]);
4078 BOOL bRepaint
= LOWORD(lParam
);
4081 wParam
= (WPARAM
)GetStockObject(SYSTEM_FONT
);
4083 if (!GetObjectW((HGDIOBJ
)wParam
, sizeof(LOGFONTW
), &lf
))
4086 hDC
= ITextHost_TxGetDC(editor
->texthost
);
4087 ME_CharFormatFromLogFont(hDC
, &lf
, &fmt
);
4088 ITextHost_TxReleaseDC(editor
->texthost
, hDC
);
4089 if (editor
->mode
& TM_RICHTEXT
) {
4091 ME_SetCursorToStart(editor
, &start
);
4092 ME_SetCharFormat(editor
, &start
, NULL
, &fmt
);
4094 ME_SetDefaultCharFormat(editor
, &fmt
);
4096 ME_CommitUndo(editor
);
4097 ME_MarkAllForWrapping(editor
);
4098 ME_WrapMarkedParagraphs(editor
);
4099 ME_UpdateScrollBar(editor
);
4107 ME_SetCursorToStart(editor
, &cursor
);
4108 ME_InternalDeleteText(editor
, &cursor
, ME_GetTextLength(editor
), FALSE
);
4111 TRACE("WM_SETTEXT lParam==%lx\n",lParam
);
4112 if (!strncmp((char *)lParam
, "{\\rtf", 5) ||
4113 !strncmp((char *)lParam
, "{\\urtf", 6))
4115 /* Undocumented: WM_SETTEXT supports RTF text */
4116 ME_StreamInRTFString(editor
, 0, (char *)lParam
);
4119 ME_SetText(editor
, (void*)lParam
, unicode
);
4122 TRACE("WM_SETTEXT - NULL\n");
4123 ME_SetCursorToStart(editor
, &cursor
);
4124 ME_UpdateLinkAttribute(editor
, &cursor
, INT_MAX
);
4125 set_selection_cursors(editor
, 0, 0);
4126 editor
->nModifyStep
= 0;
4127 ME_CommitUndo(editor
);
4128 ME_EmptyUndoStack(editor
);
4129 ME_UpdateRepaint(editor
, FALSE
);
4133 return paste_special( editor
, 0, NULL
, TRUE
);
4135 case WM_MBUTTONDOWN
:
4139 case EM_PASTESPECIAL
:
4140 paste_special( editor
, wParam
, (REPASTESPECIAL
*)lParam
, FALSE
);
4144 copy_or_cut(editor
, msg
== WM_CUT
);
4146 case WM_GETTEXTLENGTH
:
4148 GETTEXTLENGTHEX how
;
4150 /* CR/LF conversion required in 2.0 mode, verbatim in 1.0 mode */
4151 how
.flags
= GTL_CLOSE
| (editor
->bEmulateVersion10
? 0 : GTL_USECRLF
) | GTL_NUMCHARS
;
4152 how
.codepage
= unicode
? CP_UNICODE
: CP_ACP
;
4153 return ME_GetTextLengthEx(editor
, &how
);
4155 case EM_GETTEXTLENGTHEX
:
4156 return ME_GetTextLengthEx(editor
, (GETTEXTLENGTHEX
*)wParam
);
4160 ex
.cb
= wParam
* (unicode
? sizeof(WCHAR
) : sizeof(CHAR
));
4161 ex
.flags
= GT_USECRLF
;
4162 ex
.codepage
= unicode
? CP_UNICODE
: CP_ACP
;
4163 ex
.lpDefaultChar
= NULL
;
4164 ex
.lpUsedDefChar
= NULL
;
4165 return ME_GetTextEx(editor
, &ex
, lParam
);
4168 return ME_GetTextEx(editor
, (GETTEXTEX
*)wParam
, lParam
);
4171 int nFrom
, nTo
, nStartCur
= ME_GetSelectionOfs(editor
, &nFrom
, &nTo
);
4172 ME_Cursor
*from
= &editor
->pCursors
[nStartCur
];
4173 return ME_GetTextRange(editor
, (WCHAR
*)lParam
, from
,
4174 nTo
- nFrom
, unicode
);
4176 case EM_GETSCROLLPOS
:
4178 POINT
*point
= (POINT
*)lParam
;
4179 point
->x
= editor
->horz_si
.nPos
;
4180 point
->y
= editor
->vert_si
.nPos
;
4181 /* 16-bit scaled value is returned as stored in scrollinfo */
4182 if (editor
->horz_si
.nMax
> 0xffff)
4183 point
->x
= MulDiv(point
->x
, 0xffff, editor
->horz_si
.nMax
);
4184 if (editor
->vert_si
.nMax
> 0xffff)
4185 point
->y
= MulDiv(point
->y
, 0xffff, editor
->vert_si
.nMax
);
4188 case EM_GETTEXTRANGE
:
4190 TEXTRANGEW
*rng
= (TEXTRANGEW
*)lParam
;
4192 int nStart
= rng
->chrg
.cpMin
;
4193 int nEnd
= rng
->chrg
.cpMax
;
4194 int textlength
= ME_GetTextLength(editor
);
4196 TRACE("EM_GETTEXTRANGE min=%d max=%d unicode=%d textlength=%d\n",
4197 rng
->chrg
.cpMin
, rng
->chrg
.cpMax
, unicode
, textlength
);
4198 if (nStart
< 0) return 0;
4199 if ((nStart
== 0 && nEnd
== -1) || nEnd
> textlength
)
4201 if (nStart
>= nEnd
) return 0;
4203 ME_CursorFromCharOfs(editor
, nStart
, &start
);
4204 return ME_GetTextRange(editor
, rng
->lpstrText
, &start
, nEnd
- nStart
, unicode
);
4208 ME_DisplayItem
*run
;
4209 const unsigned int nMaxChars
= *(WORD
*) lParam
;
4210 unsigned int nCharsLeft
= nMaxChars
;
4211 char *dest
= (char *) lParam
;
4212 BOOL wroteNull
= FALSE
;
4214 TRACE("EM_GETLINE: row=%d, nMaxChars=%d (%s)\n", (int) wParam
, nMaxChars
,
4215 unicode
? "Unicode" : "Ansi");
4217 run
= ME_FindRowWithNumber(editor
, wParam
);
4221 while (nCharsLeft
&& (run
= ME_FindItemFwd(run
, diRunOrStartRow
))
4222 && run
->type
== diRun
)
4224 WCHAR
*str
= get_text( &run
->member
.run
, 0 );
4227 nCopy
= min(nCharsLeft
, run
->member
.run
.len
);
4230 memcpy(dest
, str
, nCopy
* sizeof(WCHAR
));
4232 nCopy
= WideCharToMultiByte(CP_ACP
, 0, str
, nCopy
, dest
,
4233 nCharsLeft
, NULL
, NULL
);
4234 dest
+= nCopy
* (unicode
? sizeof(WCHAR
) : 1);
4235 nCharsLeft
-= nCopy
;
4238 /* append line termination, space allowing */
4242 *((WCHAR
*)dest
) = '\0';
4249 TRACE("EM_GETLINE: got %u characters\n", nMaxChars
- nCharsLeft
);
4250 return nMaxChars
- nCharsLeft
- (wroteNull
? 1 : 0);
4252 case EM_GETLINECOUNT
:
4254 ME_DisplayItem
*item
= editor
->pBuffer
->pLast
;
4255 int nRows
= editor
->total_rows
;
4256 ME_DisplayItem
*prev_para
= NULL
, *last_para
= NULL
;
4258 last_para
= ME_FindItemBack(item
, diRun
);
4259 prev_para
= ME_FindItemBack(last_para
, diRun
);
4261 assert(last_para
->member
.run
.nFlags
& MERF_ENDPARA
);
4262 if (editor
->bEmulateVersion10
&& prev_para
&&
4263 last_para
->member
.run
.nCharOfs
== 0 &&
4264 prev_para
->member
.run
.len
== 1 &&
4265 *get_text( &prev_para
->member
.run
, 0 ) == '\r')
4267 /* In 1.0 emulation, the last solitary \r at the very end of the text
4268 (if one exists) is NOT a line break.
4269 FIXME: this is an ugly hack. This should have a more regular model. */
4273 TRACE("EM_GETLINECOUNT: nRows==%d\n", nRows
);
4274 return max(1, nRows
);
4276 case EM_LINEFROMCHAR
:
4279 return ME_RowNumberFromCharOfs(editor
, ME_GetCursorOfs(&editor
->pCursors
[1]));
4281 return ME_RowNumberFromCharOfs(editor
, wParam
);
4283 case EM_EXLINEFROMCHAR
:
4286 return ME_RowNumberFromCharOfs(editor
, ME_GetCursorOfs(&editor
->pCursors
[1]));
4288 return ME_RowNumberFromCharOfs(editor
, lParam
);
4292 ME_DisplayItem
*item
, *para
;
4296 item
= ME_FindItemBack(editor
->pCursors
[0].pRun
, diStartRow
);
4298 item
= ME_FindRowWithNumber(editor
, wParam
);
4301 para
= ME_GetParagraph(item
);
4302 item
= ME_FindItemFwd(item
, diRun
);
4303 nCharOfs
= para
->member
.para
.nCharOfs
+ item
->member
.run
.nCharOfs
;
4304 TRACE("EM_LINEINDEX: nCharOfs==%d\n", nCharOfs
);
4309 ME_DisplayItem
*item
, *item_end
;
4310 int nChars
= 0, nThisLineOfs
= 0, nNextLineOfs
= 0;
4311 ME_DisplayItem
*para
, *run
;
4313 if (wParam
> ME_GetTextLength(editor
))
4317 FIXME("EM_LINELENGTH: returning number of unselected characters on lines with selection unsupported.\n");
4320 ME_RunOfsFromCharOfs(editor
, wParam
, ¶
, &run
, NULL
);
4321 item
= ME_RowStart(run
);
4322 nThisLineOfs
= ME_CharOfsFromRunOfs(editor
, para
, ME_FindItemFwd(item
, diRun
), 0);
4323 item_end
= ME_FindItemFwd(item
, diStartRowOrParagraphOrEnd
);
4324 if (item_end
->type
== diStartRow
) {
4325 nNextLineOfs
= ME_CharOfsFromRunOfs(editor
, para
, ME_FindItemFwd(item_end
, diRun
), 0);
4327 ME_DisplayItem
*endRun
= ME_FindItemBack(item_end
, diRun
);
4328 assert(endRun
&& endRun
->member
.run
.nFlags
& MERF_ENDPARA
);
4329 nNextLineOfs
= item_end
->member
.para
.nCharOfs
- endRun
->member
.run
.len
;
4331 nChars
= nNextLineOfs
- nThisLineOfs
;
4332 TRACE("EM_LINELENGTH(%ld)==%d\n",wParam
, nChars
);
4335 case EM_EXLIMITTEXT
:
4337 if ((int)lParam
< 0)
4340 editor
->nTextLimit
= 65536;
4342 editor
->nTextLimit
= (int) lParam
;
4348 editor
->nTextLimit
= 65536;
4350 editor
->nTextLimit
= (int) wParam
;
4353 case EM_GETLIMITTEXT
:
4355 return editor
->nTextLimit
;
4361 FINDTEXTA
*ft
= (FINDTEXTA
*)lParam
;
4362 int nChars
= MultiByteToWideChar(CP_ACP
, 0, ft
->lpstrText
, -1, NULL
, 0);
4365 if ((tmp
= heap_alloc(nChars
* sizeof(*tmp
))) != NULL
)
4366 MultiByteToWideChar(CP_ACP
, 0, ft
->lpstrText
, -1, tmp
, nChars
);
4367 r
= ME_FindText(editor
, wParam
, &ft
->chrg
, tmp
, NULL
);
4370 FINDTEXTW
*ft
= (FINDTEXTW
*)lParam
;
4371 r
= ME_FindText(editor
, wParam
, &ft
->chrg
, ft
->lpstrText
, NULL
);
4379 FINDTEXTEXA
*ex
= (FINDTEXTEXA
*)lParam
;
4380 int nChars
= MultiByteToWideChar(CP_ACP
, 0, ex
->lpstrText
, -1, NULL
, 0);
4383 if ((tmp
= heap_alloc(nChars
* sizeof(*tmp
))) != NULL
)
4384 MultiByteToWideChar(CP_ACP
, 0, ex
->lpstrText
, -1, tmp
, nChars
);
4385 r
= ME_FindText(editor
, wParam
, &ex
->chrg
, tmp
, &ex
->chrgText
);
4388 FINDTEXTEXW
*ex
= (FINDTEXTEXW
*)lParam
;
4389 r
= ME_FindText(editor
, wParam
, &ex
->chrg
, ex
->lpstrText
, &ex
->chrgText
);
4395 FINDTEXTW
*ft
= (FINDTEXTW
*)lParam
;
4396 return ME_FindText(editor
, wParam
, &ft
->chrg
, ft
->lpstrText
, NULL
);
4398 case EM_FINDTEXTEXW
:
4400 FINDTEXTEXW
*ex
= (FINDTEXTEXW
*)lParam
;
4401 return ME_FindText(editor
, wParam
, &ex
->chrg
, ex
->lpstrText
, &ex
->chrgText
);
4404 if (!wParam
|| !lParam
)
4406 *(int *)wParam
= editor
->nZoomNumerator
;
4407 *(int *)lParam
= editor
->nZoomDenominator
;
4410 return ME_SetZoom(editor
, wParam
, lParam
);
4411 case EM_CHARFROMPOS
:
4414 if (ME_CharFromPos(editor
, ((POINTL
*)lParam
)->x
, ((POINTL
*)lParam
)->y
,
4416 return ME_GetCursorOfs(&cursor
);
4420 case EM_POSFROMCHAR
:
4422 ME_DisplayItem
*pPara
, *pRun
;
4423 int nCharOfs
, nOffset
, nLength
;
4427 /* detect which API version we're dealing with */
4428 if (wParam
>= 0x40000)
4430 nLength
= ME_GetTextLength(editor
);
4431 nCharOfs
= min(nCharOfs
, nLength
);
4432 nCharOfs
= max(nCharOfs
, 0);
4434 ME_RunOfsFromCharOfs(editor
, nCharOfs
, &pPara
, &pRun
, &nOffset
);
4435 assert(pRun
->type
== diRun
);
4436 pt
.y
= pRun
->member
.run
.pt
.y
;
4437 pt
.x
= pRun
->member
.run
.pt
.x
+ ME_PointFromChar(editor
, &pRun
->member
.run
, nOffset
, TRUE
);
4438 pt
.y
+= pPara
->member
.para
.pt
.y
+ editor
->rcFormat
.top
;
4439 pt
.x
+= editor
->rcFormat
.left
;
4441 pt
.x
-= editor
->horz_si
.nPos
;
4442 pt
.y
-= editor
->vert_si
.nPos
;
4444 if (wParam
>= 0x40000) {
4445 *(POINTL
*)wParam
= pt
;
4447 return (wParam
>= 0x40000) ? 0 : MAKELONG( pt
.x
, pt
.y
);
4450 return ME_WmCreate(editor
, lParam
, unicode
);
4452 ME_DestroyEditor(editor
);
4457 if (wParam
== (WPARAM
)editor
->hWnd
&& GetCursorPos(&cursor_pos
) &&
4458 ScreenToClient(editor
->hWnd
, &cursor_pos
))
4459 ME_LinkNotify(editor
, msg
, 0, MAKELPARAM(cursor_pos
.x
, cursor_pos
.y
));
4460 return ME_SetCursor(editor
);
4462 case WM_LBUTTONDBLCLK
:
4463 case WM_LBUTTONDOWN
:
4465 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
4466 if ((editor
->nEventMask
& ENM_MOUSEEVENTS
) &&
4467 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4469 ITextHost_TxSetFocus(editor
->texthost
);
4470 ME_LButtonDown(editor
, (short)LOWORD(lParam
), (short)HIWORD(lParam
),
4471 ME_CalculateClickCount(editor
, msg
, wParam
, lParam
));
4472 ITextHost_TxSetCapture(editor
->texthost
, TRUE
);
4473 editor
->bMouseCaptured
= TRUE
;
4474 ME_LinkNotify(editor
, msg
, wParam
, lParam
);
4475 if (!ME_SetCursor(editor
)) goto do_default
;
4479 if ((editor
->nEventMask
& ENM_MOUSEEVENTS
) &&
4480 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4482 if (editor
->bMouseCaptured
)
4483 ME_MouseMove(editor
, (short)LOWORD(lParam
), (short)HIWORD(lParam
));
4485 ME_LinkNotify(editor
, msg
, wParam
, lParam
);
4486 /* Set cursor if mouse is captured, since WM_SETCURSOR won't be received. */
4487 if (editor
->bMouseCaptured
)
4488 ME_SetCursor(editor
);
4491 if (editor
->bMouseCaptured
) {
4492 ITextHost_TxSetCapture(editor
->texthost
, FALSE
);
4493 editor
->bMouseCaptured
= FALSE
;
4495 if (editor
->nSelectionType
== stDocument
)
4496 editor
->nSelectionType
= stPosition
;
4497 if ((editor
->nEventMask
& ENM_MOUSEEVENTS
) &&
4498 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4502 ME_SetCursor(editor
);
4503 ME_LinkNotify(editor
, msg
, wParam
, lParam
);
4507 case WM_RBUTTONDOWN
:
4508 case WM_RBUTTONDBLCLK
:
4509 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
4510 if ((editor
->nEventMask
& ENM_MOUSEEVENTS
) &&
4511 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4513 ME_LinkNotify(editor
, msg
, wParam
, lParam
);
4515 case WM_CONTEXTMENU
:
4516 if (!ME_ShowContextMenu(editor
, (short)LOWORD(lParam
), (short)HIWORD(lParam
)))
4520 editor
->bHaveFocus
= TRUE
;
4521 create_caret(editor
);
4522 update_caret(editor
);
4523 ME_SendOldNotify(editor
, EN_SETFOCUS
);
4524 if (!editor
->bHideSelection
&& !(editor
->styleFlags
& ES_NOHIDESEL
))
4525 ME_InvalidateSelection( editor
);
4528 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
4529 editor
->bHaveFocus
= FALSE
;
4530 editor
->wheel_remain
= 0;
4533 ME_SendOldNotify(editor
, EN_KILLFOCUS
);
4534 if (!editor
->bHideSelection
&& !(editor
->styleFlags
& ES_NOHIDESEL
))
4535 ME_InvalidateSelection( editor
);
4538 TRACE("editor wnd command = %d\n", LOWORD(wParam
));
4541 if ((editor
->nEventMask
& ENM_KEYEVENTS
) &&
4542 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4546 if ((editor
->nEventMask
& ENM_KEYEVENTS
) &&
4547 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4549 if (ME_KeyDown(editor
, LOWORD(wParam
)))
4553 if ((editor
->nEventMask
& ENM_KEYEVENTS
) &&
4554 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4556 return ME_Char(editor
, wParam
, lParam
, unicode
);
4560 if(wParam
== UNICODE_NOCHAR
) return TRUE
;
4561 if(wParam
<= 0x000fffff)
4563 if(wParam
> 0xffff) /* convert to surrogates */
4566 ME_Char(editor
, (wParam
>> 10) + 0xd800, 0, TRUE
);
4567 ME_Char(editor
, (wParam
& 0x03ff) + 0xdc00, 0, TRUE
);
4569 ME_Char(editor
, wParam
, 0, TRUE
);
4575 case EM_STOPGROUPTYPING
:
4576 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
4580 const int scrollUnit
= 7;
4582 switch(LOWORD(wParam
))
4585 ME_ScrollAbs(editor
, 0, 0);
4588 ME_ScrollAbs(editor
,
4589 editor
->horz_si
.nMax
- (int)editor
->horz_si
.nPage
,
4590 editor
->vert_si
.nMax
- (int)editor
->vert_si
.nPage
);
4593 ME_ScrollLeft(editor
, scrollUnit
);
4596 ME_ScrollRight(editor
, scrollUnit
);
4599 ME_ScrollLeft(editor
, editor
->sizeWindow
.cx
);
4602 ME_ScrollRight(editor
, editor
->sizeWindow
.cx
);
4605 case SB_THUMBPOSITION
:
4607 int pos
= HIWORD(wParam
);
4608 if (editor
->horz_si
.nMax
> 0xffff)
4609 pos
= MulDiv(pos
, editor
->horz_si
.nMax
, 0xffff);
4610 ME_HScrollAbs(editor
, pos
);
4616 case EM_SCROLL
: /* fall through */
4620 int lineHeight
= get_default_line_height( editor
);
4622 origNPos
= editor
->vert_si
.nPos
;
4624 switch(LOWORD(wParam
))
4627 ME_ScrollAbs(editor
, 0, 0);
4630 ME_ScrollAbs(editor
,
4631 editor
->horz_si
.nMax
- (int)editor
->horz_si
.nPage
,
4632 editor
->vert_si
.nMax
- (int)editor
->vert_si
.nPage
);
4635 ME_ScrollUp(editor
,lineHeight
);
4638 ME_ScrollDown(editor
,lineHeight
);
4641 ME_ScrollUp(editor
,editor
->sizeWindow
.cy
);
4644 ME_ScrollDown(editor
,editor
->sizeWindow
.cy
);
4647 case SB_THUMBPOSITION
:
4649 int pos
= HIWORD(wParam
);
4650 if (editor
->vert_si
.nMax
> 0xffff)
4651 pos
= MulDiv(pos
, editor
->vert_si
.nMax
, 0xffff);
4652 ME_VScrollAbs(editor
, pos
);
4656 if (msg
== EM_SCROLL
)
4657 return 0x00010000 | (((editor
->vert_si
.nPos
- origNPos
)/lineHeight
) & 0xffff);
4665 if ((editor
->nEventMask
& ENM_MOUSEEVENTS
) &&
4666 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4669 ctrl_is_down
= GetKeyState(VK_CONTROL
) & 0x8000;
4671 delta
= GET_WHEEL_DELTA_WPARAM(wParam
);
4673 /* if scrolling changes direction, ignore left overs */
4674 if ((delta
< 0 && editor
->wheel_remain
< 0) ||
4675 (delta
> 0 && editor
->wheel_remain
> 0))
4676 editor
->wheel_remain
+= delta
;
4678 editor
->wheel_remain
= delta
;
4680 if (editor
->wheel_remain
)
4684 if (!editor
->nZoomNumerator
|| !editor
->nZoomDenominator
)
4688 numerator
= editor
->nZoomNumerator
* 100 / editor
->nZoomDenominator
;
4690 numerator
+= calc_wheel_change( &editor
->wheel_remain
, 10 );
4691 if (numerator
>= 10 && numerator
<= 500)
4692 ME_SetZoom(editor
, numerator
, 100);
4697 SystemParametersInfoW( SPI_GETWHEELSCROLLLINES
, 0, &max_lines
, 0 );
4699 lines
= calc_wheel_change( &editor
->wheel_remain
, (int)max_lines
);
4701 ME_ScrollDown( editor
, -lines
* get_default_line_height( editor
) );
4708 *((RECT
*)lParam
) = editor
->rcFormat
;
4709 if (editor
->bDefaultFormatRect
)
4710 ((RECT
*)lParam
)->left
-= editor
->selofs
;
4720 RECT
*rc
= (RECT
*)lParam
;
4722 border
= editor
->exStyleFlags
& WS_EX_CLIENTEDGE
? 1 : 0;
4723 ITextHost_TxGetClientRect(editor
->texthost
, &clientRect
);
4726 editor
->rcFormat
.top
= max(0, rc
->top
- border
);
4727 editor
->rcFormat
.left
= max(0, rc
->left
- border
);
4728 editor
->rcFormat
.bottom
= min(clientRect
.bottom
, rc
->bottom
);
4729 editor
->rcFormat
.right
= min(clientRect
.right
, rc
->right
+ border
);
4730 } else if (wParam
== 1) {
4731 /* MSDN incorrectly says a wParam value of 1 causes the
4732 * lParam rect to be used as a relative offset,
4733 * however, the tests show it just prevents min/max bound
4735 editor
->rcFormat
.top
= rc
->top
- border
;
4736 editor
->rcFormat
.left
= rc
->left
- border
;
4737 editor
->rcFormat
.bottom
= rc
->bottom
;
4738 editor
->rcFormat
.right
= rc
->right
+ border
;
4742 editor
->bDefaultFormatRect
= FALSE
;
4746 ME_SetDefaultFormatRect(editor
);
4747 editor
->bDefaultFormatRect
= TRUE
;
4749 ME_MarkAllForWrapping(editor
);
4750 ME_WrapMarkedParagraphs(editor
);
4751 ME_UpdateScrollBar(editor
);
4752 if (msg
!= EM_SETRECTNP
)
4756 case EM_REQUESTRESIZE
:
4757 ME_SendRequestResize(editor
, TRUE
);
4761 case WM_WINDOWPOSCHANGED
:
4764 WINDOWPOS
*winpos
= (WINDOWPOS
*)lParam
;
4766 if (winpos
->flags
& SWP_NOCLIENTSIZE
) goto do_default
;
4767 ITextHost_TxGetClientRect(editor
->texthost
, &clientRect
);
4768 if (editor
->bDefaultFormatRect
) {
4769 ME_SetDefaultFormatRect(editor
);
4771 editor
->rcFormat
.right
+= clientRect
.right
- editor
->prevClientRect
.right
;
4772 editor
->rcFormat
.bottom
+= clientRect
.bottom
- editor
->prevClientRect
.bottom
;
4774 editor
->prevClientRect
= clientRect
;
4775 ME_RewrapRepaint(editor
);
4778 /* IME messages to make richedit controls IME aware */
4779 case WM_IME_SETCONTEXT
:
4780 case WM_IME_CONTROL
:
4782 case WM_IME_COMPOSITIONFULL
:
4784 case WM_IME_STARTCOMPOSITION
:
4786 editor
->imeStartIndex
=ME_GetCursorOfs(&editor
->pCursors
[0]);
4787 ME_DeleteSelection(editor
);
4788 ME_CommitUndo(editor
);
4789 ME_UpdateRepaint(editor
, FALSE
);
4792 case WM_IME_COMPOSITION
:
4796 ME_Style
*style
= ME_GetInsertStyle(editor
, 0);
4797 hIMC
= ITextHost_TxImmGetContext(editor
->texthost
);
4798 ME_DeleteSelection(editor
);
4799 ME_SaveTempStyle(editor
, style
);
4800 if (lParam
& (GCS_RESULTSTR
|GCS_COMPSTR
))
4802 LPWSTR lpCompStr
= NULL
;
4804 DWORD dwIndex
= lParam
& GCS_RESULTSTR
;
4806 dwIndex
= GCS_COMPSTR
;
4808 dwBufLen
= ImmGetCompositionStringW(hIMC
, dwIndex
, NULL
, 0);
4809 lpCompStr
= HeapAlloc(GetProcessHeap(),0,dwBufLen
+ sizeof(WCHAR
));
4810 ImmGetCompositionStringW(hIMC
, dwIndex
, lpCompStr
, dwBufLen
);
4811 lpCompStr
[dwBufLen
/sizeof(WCHAR
)] = 0;
4812 ME_InsertTextFromCursor(editor
,0,lpCompStr
,dwBufLen
/sizeof(WCHAR
),style
);
4813 HeapFree(GetProcessHeap(), 0, lpCompStr
);
4815 if (dwIndex
== GCS_COMPSTR
)
4816 set_selection_cursors(editor
,editor
->imeStartIndex
,
4817 editor
->imeStartIndex
+ dwBufLen
/sizeof(WCHAR
));
4819 ME_ReleaseStyle(style
);
4820 ME_CommitUndo(editor
);
4821 ME_UpdateRepaint(editor
, FALSE
);
4824 case WM_IME_ENDCOMPOSITION
:
4826 ME_DeleteSelection(editor
);
4827 editor
->imeStartIndex
=-1;
4830 case EM_GETOLEINTERFACE
:
4833 if (!CreateIRichEditOle(NULL
, editor
, (LPVOID
*)&editor
->reOle
))
4835 if (IUnknown_QueryInterface(editor
->reOle
, &IID_IRichEditOle
, (LPVOID
*)lParam
) == S_OK
)
4839 case EM_GETPASSWORDCHAR
:
4841 return editor
->cPasswordMask
;
4843 case EM_SETOLECALLBACK
:
4844 if(editor
->lpOleCallback
)
4845 IRichEditOleCallback_Release(editor
->lpOleCallback
);
4846 editor
->lpOleCallback
= (IRichEditOleCallback
*)lParam
;
4847 if(editor
->lpOleCallback
)
4848 IRichEditOleCallback_AddRef(editor
->lpOleCallback
);
4850 case EM_GETWORDBREAKPROC
:
4851 return (LRESULT
)editor
->pfnWordBreak
;
4852 case EM_SETWORDBREAKPROC
:
4854 EDITWORDBREAKPROCW pfnOld
= editor
->pfnWordBreak
;
4856 editor
->pfnWordBreak
= (EDITWORDBREAKPROCW
)lParam
;
4857 return (LRESULT
)pfnOld
;
4859 case EM_GETTEXTMODE
:
4860 return editor
->mode
;
4861 case EM_SETTEXTMODE
:
4866 if (ME_GetTextLength(editor
) ||
4867 !list_empty( &editor
->undo_stack
) || !list_empty( &editor
->redo_stack
))
4868 return E_UNEXPECTED
;
4870 /* Check for mutually exclusive flags in adjacent bits of wParam */
4871 if ((wParam
& (TM_RICHTEXT
| TM_MULTILEVELUNDO
| TM_MULTICODEPAGE
)) &
4872 (wParam
& (TM_PLAINTEXT
| TM_SINGLELEVELUNDO
| TM_SINGLECODEPAGE
)) << 1)
4873 return E_INVALIDARG
;
4875 if (wParam
& (TM_RICHTEXT
| TM_PLAINTEXT
))
4877 mask
|= TM_RICHTEXT
| TM_PLAINTEXT
;
4878 changes
|= wParam
& (TM_RICHTEXT
| TM_PLAINTEXT
);
4879 if (wParam
& TM_PLAINTEXT
) {
4880 /* Clear selection since it should be possible to select the
4881 * end of text run for rich text */
4882 ME_InvalidateSelection(editor
);
4883 ME_SetCursorToStart(editor
, &editor
->pCursors
[0]);
4884 editor
->pCursors
[1] = editor
->pCursors
[0];
4885 /* plain text can only have the default style. */
4886 ME_ClearTempStyle(editor
);
4887 ME_AddRefStyle(editor
->pBuffer
->pDefaultStyle
);
4888 ME_ReleaseStyle(editor
->pCursors
[0].pRun
->member
.run
.style
);
4889 editor
->pCursors
[0].pRun
->member
.run
.style
= editor
->pBuffer
->pDefaultStyle
;
4892 /* FIXME: Currently no support for undo level and code page options */
4893 editor
->mode
= (editor
->mode
& ~mask
) | changes
;
4896 case EM_SETPASSWORDCHAR
:
4898 editor
->cPasswordMask
= wParam
;
4899 ME_RewrapRepaint(editor
);
4902 case EM_SETTARGETDEVICE
:
4905 BOOL
new = (lParam
== 0 && (editor
->styleFlags
& ES_MULTILINE
));
4906 if (editor
->nAvailWidth
|| editor
->bWordWrap
!= new)
4908 editor
->bWordWrap
= new;
4909 editor
->nAvailWidth
= 0; /* wrap to client area */
4910 ME_RewrapRepaint(editor
);
4913 int width
= max(0, lParam
);
4914 if ((editor
->styleFlags
& ES_MULTILINE
) &&
4915 (!editor
->bWordWrap
|| editor
->nAvailWidth
!= width
))
4917 editor
->nAvailWidth
= width
;
4918 editor
->bWordWrap
= TRUE
;
4919 ME_RewrapRepaint(editor
);
4921 FIXME("EM_SETTARGETDEVICE doesn't use non-NULL target devices\n");
4926 *phresult
= S_FALSE
;
4932 static BOOL
create_windowed_editor(HWND hwnd
, CREATESTRUCTW
*create
, BOOL emulate_10
)
4934 ITextHost
*host
= ME_CreateTextHost( hwnd
, create
, emulate_10
);
4935 ME_TextEditor
*editor
;
4937 if (!host
) return FALSE
;
4939 editor
= ME_MakeEditor( host
, emulate_10
);
4942 ITextHost_Release( host
);
4946 editor
->exStyleFlags
= GetWindowLongW( hwnd
, GWL_EXSTYLE
);
4947 editor
->styleFlags
|= GetWindowLongW( hwnd
, GWL_STYLE
) & ES_WANTRETURN
;
4948 editor
->hWnd
= hwnd
; /* FIXME: Remove editor's dependence on hWnd */
4949 editor
->hwndParent
= create
->hwndParent
;
4951 SetWindowLongPtrW( hwnd
, 0, (LONG_PTR
)editor
);
4956 static LRESULT
RichEditWndProc_common(HWND hWnd
, UINT msg
, WPARAM wParam
,
4957 LPARAM lParam
, BOOL unicode
)
4959 ME_TextEditor
*editor
;
4961 LRESULT lresult
= 0;
4963 TRACE("enter hwnd %p msg %04x (%s) %lx %lx, unicode %d\n",
4964 hWnd
, msg
, get_msg_name(msg
), wParam
, lParam
, unicode
);
4966 editor
= (ME_TextEditor
*)GetWindowLongPtrW(hWnd
, 0);
4969 if (msg
== WM_NCCREATE
)
4971 CREATESTRUCTW
*pcs
= (CREATESTRUCTW
*)lParam
;
4973 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd
, pcs
->style
);
4974 return create_windowed_editor( hWnd
, pcs
, FALSE
);
4978 return DefWindowProcW(hWnd
, msg
, wParam
, lParam
);
4991 update_caret(editor
);
4992 hdc
= BeginPaint(editor
->hWnd
, &ps
);
4993 if (!editor
->bEmulateVersion10
|| (editor
->nEventMask
& ENM_UPDATE
))
4994 ME_SendOldNotify(editor
, EN_UPDATE
);
4995 old_brush
= SelectObject(hdc
, editor
->hbrBackground
);
4997 /* Erase area outside of the formatting rectangle */
4998 if (ps
.rcPaint
.top
< editor
->rcFormat
.top
)
5001 rc
.bottom
= editor
->rcFormat
.top
;
5002 PatBlt(hdc
, rc
.left
, rc
.top
, rc
.right
- rc
.left
, rc
.bottom
- rc
.top
, PATCOPY
);
5003 ps
.rcPaint
.top
= editor
->rcFormat
.top
;
5005 if (ps
.rcPaint
.bottom
> editor
->rcFormat
.bottom
) {
5007 rc
.top
= editor
->rcFormat
.bottom
;
5008 PatBlt(hdc
, rc
.left
, rc
.top
, rc
.right
- rc
.left
, rc
.bottom
- rc
.top
, PATCOPY
);
5009 ps
.rcPaint
.bottom
= editor
->rcFormat
.bottom
;
5011 if (ps
.rcPaint
.left
< editor
->rcFormat
.left
) {
5013 rc
.right
= editor
->rcFormat
.left
;
5014 PatBlt(hdc
, rc
.left
, rc
.top
, rc
.right
- rc
.left
, rc
.bottom
- rc
.top
, PATCOPY
);
5015 ps
.rcPaint
.left
= editor
->rcFormat
.left
;
5017 if (ps
.rcPaint
.right
> editor
->rcFormat
.right
) {
5019 rc
.left
= editor
->rcFormat
.right
;
5020 PatBlt(hdc
, rc
.left
, rc
.top
, rc
.right
- rc
.left
, rc
.bottom
- rc
.top
, PATCOPY
);
5021 ps
.rcPaint
.right
= editor
->rcFormat
.right
;
5024 ME_PaintContent(editor
, hdc
, &ps
.rcPaint
);
5025 SelectObject(hdc
, old_brush
);
5026 EndPaint(editor
->hWnd
, &ps
);
5031 HDC hDC
= (HDC
)wParam
;
5034 if (GetUpdateRect(editor
->hWnd
, &rc
, TRUE
))
5035 FillRect(hDC
, &rc
, editor
->hbrBackground
);
5041 const DWORD mask
= ECO_VERTICAL
| ECO_AUTOHSCROLL
| ECO_AUTOVSCROLL
|
5042 ECO_NOHIDESEL
| ECO_READONLY
| ECO_WANTRETURN
|
5044 lresult
= ME_HandleMessage(editor
, msg
, wParam
, lParam
, unicode
, &hresult
);
5045 dwStyle
= GetWindowLongW(hWnd
, GWL_STYLE
);
5046 dwStyle
= (dwStyle
& ~mask
) | (lresult
& mask
);
5047 SetWindowLongW(hWnd
, GWL_STYLE
, dwStyle
);
5050 case EM_SETREADONLY
:
5053 lresult
= ME_HandleMessage(editor
, msg
, wParam
, lParam
, unicode
, &hresult
);
5054 dwStyle
= GetWindowLongW(hWnd
, GWL_STYLE
);
5055 dwStyle
&= ~ES_READONLY
;
5057 dwStyle
|= ES_READONLY
;
5058 SetWindowLongW(hWnd
, GWL_STYLE
, dwStyle
);
5062 lresult
= ME_HandleMessage(editor
, msg
, wParam
, lParam
, unicode
, &hresult
);
5065 if (hresult
== S_FALSE
)
5066 lresult
= DefWindowProcW(hWnd
, msg
, wParam
, lParam
);
5068 TRACE("exit hwnd %p msg %04x (%s) %lx %lx, unicode %d -> %lu\n",
5069 hWnd
, msg
, get_msg_name(msg
), wParam
, lParam
, unicode
, lresult
);
5074 static LRESULT WINAPI
RichEditWndProcW(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
5076 BOOL unicode
= TRUE
;
5078 /* Under Win9x RichEdit20W returns ANSI strings, see the tests. */
5079 if (msg
== WM_GETTEXT
&& (GetVersion() & 0x80000000))
5082 return RichEditWndProc_common(hWnd
, msg
, wParam
, lParam
, unicode
);
5085 static LRESULT WINAPI
RichEditWndProcA(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
5087 return RichEditWndProc_common(hWnd
, msg
, wParam
, lParam
, FALSE
);
5090 /******************************************************************
5091 * RichEditANSIWndProc (RICHED20.10)
5093 LRESULT WINAPI
RichEditANSIWndProc(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
5095 return RichEditWndProcA(hWnd
, msg
, wParam
, lParam
);
5098 /******************************************************************
5099 * RichEdit10ANSIWndProc (RICHED20.9)
5101 LRESULT WINAPI
RichEdit10ANSIWndProc(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
5103 if (msg
== WM_NCCREATE
&& !GetWindowLongPtrW(hWnd
, 0))
5105 CREATESTRUCTW
*pcs
= (CREATESTRUCTW
*)lParam
;
5107 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd
, pcs
->style
);
5108 return create_windowed_editor( hWnd
, pcs
, TRUE
);
5110 return RichEditANSIWndProc(hWnd
, msg
, wParam
, lParam
);
5113 void ME_SendOldNotify(ME_TextEditor
*editor
, int nCode
)
5115 ITextHost_TxNotify(editor
->texthost
, nCode
, NULL
);
5118 /* Fill buffer with srcChars unicode characters from the start cursor.
5120 * buffer: destination buffer
5121 * buflen: length of buffer in characters excluding the NULL terminator.
5122 * start: start of editor text to copy into buffer.
5123 * srcChars: Number of characters to use from the editor text.
5124 * bCRLF: if true, replaces all end of lines with \r\n pairs.
5126 * returns the number of characters written excluding the NULL terminator.
5128 * The written text is always NULL terminated.
5130 int ME_GetTextW(ME_TextEditor
*editor
, WCHAR
*buffer
, int buflen
,
5131 const ME_Cursor
*start
, int srcChars
, BOOL bCRLF
,
5134 ME_DisplayItem
*pRun
, *pNextRun
;
5135 const WCHAR
*pStart
= buffer
;
5136 const WCHAR cr_lf
[] = {'\r', '\n', 0};
5140 /* bCRLF flag is only honored in 2.0 and up. 1.0 must always return text verbatim */
5141 if (editor
->bEmulateVersion10
) bCRLF
= FALSE
;
5145 pNextRun
= ME_FindItemFwd(pRun
, diRun
);
5147 nLen
= pRun
->member
.run
.len
- start
->nOffset
;
5148 str
= get_text( &pRun
->member
.run
, start
->nOffset
);
5150 while (srcChars
&& buflen
&& pNextRun
)
5152 int nFlags
= pRun
->member
.run
.nFlags
;
5154 if (bCRLF
&& nFlags
& MERF_ENDPARA
&& ~nFlags
& MERF_ENDCELL
)
5156 if (buflen
== 1) break;
5157 /* FIXME: native fails to reduce srcChars here for WM_GETTEXT or
5158 * EM_GETTEXTEX, however, this is done for copying text which
5159 * also uses this function. */
5160 srcChars
-= min(nLen
, srcChars
);
5164 nLen
= min(nLen
, srcChars
);
5168 nLen
= min(nLen
, buflen
);
5171 CopyMemory(buffer
, str
, sizeof(WCHAR
) * nLen
);
5176 pNextRun
= ME_FindItemFwd(pRun
, diRun
);
5178 nLen
= pRun
->member
.run
.len
;
5179 str
= get_text( &pRun
->member
.run
, 0 );
5181 /* append '\r' to the last paragraph. */
5182 if (pRun
->next
->type
== diTextEnd
&& bEOP
)
5188 return buffer
- pStart
;
5191 static BOOL
ME_RegisterEditorClass(HINSTANCE hInstance
)
5196 wcW
.style
= CS_DBLCLKS
| CS_HREDRAW
| CS_VREDRAW
| CS_GLOBALCLASS
;
5197 wcW
.lpfnWndProc
= RichEditWndProcW
;
5199 wcW
.cbWndExtra
= sizeof(ME_TextEditor
*);
5200 wcW
.hInstance
= NULL
; /* hInstance would register DLL-local class */
5202 wcW
.hCursor
= LoadCursorW(NULL
, (LPWSTR
)IDC_IBEAM
);
5203 wcW
.hbrBackground
= GetStockObject(NULL_BRUSH
);
5204 wcW
.lpszMenuName
= NULL
;
5206 if (is_version_nt())
5208 wcW
.lpszClassName
= RICHEDIT_CLASS20W
;
5209 if (!RegisterClassW(&wcW
)) return FALSE
;
5210 wcW
.lpszClassName
= MSFTEDIT_CLASS
;
5211 if (!RegisterClassW(&wcW
)) return FALSE
;
5215 /* WNDCLASSA/W have the same layout */
5216 wcW
.lpszClassName
= (LPCWSTR
)"RichEdit20W";
5217 if (!RegisterClassA((WNDCLASSA
*)&wcW
)) return FALSE
;
5218 wcW
.lpszClassName
= (LPCWSTR
)"RichEdit50W";
5219 if (!RegisterClassA((WNDCLASSA
*)&wcW
)) return FALSE
;
5222 wcA
.style
= CS_DBLCLKS
| CS_HREDRAW
| CS_VREDRAW
| CS_GLOBALCLASS
;
5223 wcA
.lpfnWndProc
= RichEditWndProcA
;
5225 wcA
.cbWndExtra
= sizeof(ME_TextEditor
*);
5226 wcA
.hInstance
= NULL
; /* hInstance would register DLL-local class */
5228 wcA
.hCursor
= LoadCursorW(NULL
, (LPWSTR
)IDC_IBEAM
);
5229 wcA
.hbrBackground
= GetStockObject(NULL_BRUSH
);
5230 wcA
.lpszMenuName
= NULL
;
5231 wcA
.lpszClassName
= RICHEDIT_CLASS20A
;
5232 if (!RegisterClassA(&wcA
)) return FALSE
;
5233 wcA
.lpszClassName
= "RichEdit50A";
5234 if (!RegisterClassA(&wcA
)) return FALSE
;
5239 static LRESULT WINAPI
REComboWndProc(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
) {
5240 /* FIXME: Not implemented */
5241 TRACE("hWnd %p msg %04x (%s) %08lx %08lx\n",
5242 hWnd
, msg
, get_msg_name(msg
), wParam
, lParam
);
5243 return DefWindowProcW(hWnd
, msg
, wParam
, lParam
);
5246 static LRESULT WINAPI
REListWndProc(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
) {
5247 /* FIXME: Not implemented */
5248 TRACE("hWnd %p msg %04x (%s) %08lx %08lx\n",
5249 hWnd
, msg
, get_msg_name(msg
), wParam
, lParam
);
5250 return DefWindowProcW(hWnd
, msg
, wParam
, lParam
);
5253 /******************************************************************
5254 * REExtendedRegisterClass (RICHED20.8)
5256 * FIXME undocumented
5257 * Need to check for errors and implement controls and callbacks
5259 LRESULT WINAPI
REExtendedRegisterClass(void)
5264 FIXME("semi stub\n");
5268 wcW
.hInstance
= NULL
;
5271 wcW
.hbrBackground
= (HBRUSH
)(COLOR_WINDOW
+1);
5272 wcW
.lpszMenuName
= NULL
;
5274 if (!ME_ListBoxRegistered
)
5276 wcW
.style
= CS_PARENTDC
| CS_DBLCLKS
| CS_GLOBALCLASS
;
5277 wcW
.lpfnWndProc
= REListWndProc
;
5278 wcW
.lpszClassName
= REListBox20W
;
5279 if (RegisterClassW(&wcW
)) ME_ListBoxRegistered
= TRUE
;
5282 if (!ME_ComboBoxRegistered
)
5284 wcW
.style
= CS_PARENTDC
| CS_DBLCLKS
| CS_GLOBALCLASS
| CS_VREDRAW
| CS_HREDRAW
;
5285 wcW
.lpfnWndProc
= REComboWndProc
;
5286 wcW
.lpszClassName
= REComboBox20W
;
5287 if (RegisterClassW(&wcW
)) ME_ComboBoxRegistered
= TRUE
;
5291 if (ME_ListBoxRegistered
)
5293 if (ME_ComboBoxRegistered
)
5299 static int __cdecl
wchar_comp( const void *key
, const void *elem
)
5301 return *(const WCHAR
*)key
- *(const WCHAR
*)elem
;
5304 /* neutral characters end the url if the next non-neutral character is a space character,
5305 otherwise they are included in the url. */
5306 static BOOL
isurlneutral( WCHAR c
)
5308 /* NB this list is sorted */
5309 static const WCHAR neutral_chars
[] = {'!','\"','\'','(',')',',','-','.',':',';','<','>','?','[',']','{','}'};
5311 /* Some shortcuts */
5312 if (isalnum( c
)) return FALSE
;
5313 if (c
> neutral_chars
[ARRAY_SIZE( neutral_chars
) - 1]) return FALSE
;
5315 return !!bsearch( &c
, neutral_chars
, ARRAY_SIZE( neutral_chars
), sizeof(c
), wchar_comp
);
5319 * This proc takes a selection, and scans it forward in order to select the span
5320 * of a possible URL candidate. A possible URL candidate must start with isalnum
5321 * or one of the following special characters: *|/\+%#@ and must consist entirely
5322 * of the characters allowed to start the URL, plus : (colon) which may occur
5323 * at most once, and not at either end.
5325 static BOOL
ME_FindNextURLCandidate(ME_TextEditor
*editor
,
5326 const ME_Cursor
*start
,
5328 ME_Cursor
*candidate_min
,
5329 ME_Cursor
*candidate_max
)
5331 ME_Cursor cursor
= *start
, neutral_end
, space_end
;
5332 BOOL candidateStarted
= FALSE
, quoted
= FALSE
;
5337 WCHAR
*str
= get_text( &cursor
.pRun
->member
.run
, 0 );
5338 int run_len
= cursor
.pRun
->member
.run
.len
;
5340 nChars
-= run_len
- cursor
.nOffset
;
5342 /* Find start of candidate */
5343 if (!candidateStarted
)
5345 while (cursor
.nOffset
< run_len
)
5347 c
= str
[cursor
.nOffset
];
5348 if (!iswspace( c
) && !isurlneutral( c
))
5350 *candidate_min
= cursor
;
5351 candidateStarted
= TRUE
;
5352 neutral_end
.pPara
= NULL
;
5353 space_end
.pPara
= NULL
;
5357 quoted
= (c
== '<');
5362 /* Find end of candidate */
5363 if (candidateStarted
)
5365 while (cursor
.nOffset
< run_len
)
5367 c
= str
[cursor
.nOffset
];
5370 if (quoted
&& c
!= '\r')
5372 if (!space_end
.pPara
)
5374 if (neutral_end
.pPara
)
5375 space_end
= neutral_end
;
5383 else if (isurlneutral( c
))
5385 if (quoted
&& c
== '>')
5387 neutral_end
.pPara
= NULL
;
5388 space_end
.pPara
= NULL
;
5391 if (!neutral_end
.pPara
)
5392 neutral_end
= cursor
;
5395 neutral_end
.pPara
= NULL
;
5402 if (!ME_NextRun(&cursor
.pPara
, &cursor
.pRun
, TRUE
))
5407 if (candidateStarted
)
5409 if (space_end
.pPara
)
5410 *candidate_max
= space_end
;
5411 else if (neutral_end
.pPara
)
5412 *candidate_max
= neutral_end
;
5414 *candidate_max
= cursor
;
5417 *candidate_max
= *candidate_min
= cursor
;
5422 * This proc evaluates the selection and returns TRUE if it can be considered an URL
5424 static BOOL
ME_IsCandidateAnURL(ME_TextEditor
*editor
, const ME_Cursor
*start
, int nChars
)
5426 #define MAX_PREFIX_LEN 9
5428 const WCHAR text
[MAX_PREFIX_LEN
];
5431 {{'p','r','o','s','p','e','r','o',':'}, 9},
5432 {{'t','e','l','n','e','t',':'}, 7},
5433 {{'g','o','p','h','e','r',':'}, 7},
5434 {{'m','a','i','l','t','o',':'}, 7},
5435 {{'h','t','t','p','s',':'}, 6},
5436 {{'f','i','l','e',':'}, 5},
5437 {{'n','e','w','s',':'}, 5},
5438 {{'w','a','i','s',':'}, 5},
5439 {{'n','n','t','p',':'}, 5},
5440 {{'h','t','t','p',':'}, 5},
5441 {{'w','w','w','.'}, 4},
5442 {{'f','t','p',':'}, 4},
5444 WCHAR bufferW
[MAX_PREFIX_LEN
+ 1];
5447 ME_GetTextW(editor
, bufferW
, MAX_PREFIX_LEN
, start
, nChars
, FALSE
, FALSE
);
5448 for (i
= 0; i
< ARRAY_SIZE(prefixes
); i
++)
5450 if (nChars
< prefixes
[i
].length
) continue;
5451 if (!memcmp(prefixes
[i
].text
, bufferW
, prefixes
[i
].length
* sizeof(WCHAR
)))
5455 #undef MAX_PREFIX_LEN
5459 * This proc walks through the indicated selection and evaluates whether each
5460 * section identified by ME_FindNextURLCandidate and in-between sections have
5461 * their proper CFE_LINK attributes set or unset. If the CFE_LINK attribute is
5462 * not what it is supposed to be, this proc sets or unsets it as appropriate.
5464 * Since this function can cause runs to be split, do not depend on the value
5465 * of the start cursor at the end of the function.
5467 * nChars may be set to INT_MAX to update to the end of the text.
5469 * Returns TRUE if at least one section was modified.
5471 static BOOL
ME_UpdateLinkAttribute(ME_TextEditor
*editor
, ME_Cursor
*start
, int nChars
)
5473 BOOL modified
= FALSE
;
5474 ME_Cursor startCur
= *start
;
5476 if (!editor
->AutoURLDetect_bEnable
) return FALSE
;
5481 ME_Cursor candidateStart
, candidateEnd
;
5483 if (ME_FindNextURLCandidate(editor
, &startCur
, nChars
,
5484 &candidateStart
, &candidateEnd
))
5486 /* Section before candidate is not an URL */
5487 int cMin
= ME_GetCursorOfs(&candidateStart
);
5488 int cMax
= ME_GetCursorOfs(&candidateEnd
);
5490 if (!ME_IsCandidateAnURL(editor
, &candidateStart
, cMax
- cMin
))
5491 candidateStart
= candidateEnd
;
5492 nChars
-= cMax
- ME_GetCursorOfs(&startCur
);
5496 /* No more candidates until end of selection */
5500 if (startCur
.pRun
!= candidateStart
.pRun
||
5501 startCur
.nOffset
!= candidateStart
.nOffset
)
5503 /* CFE_LINK effect should be consistently unset */
5504 link
.cbSize
= sizeof(link
);
5505 ME_GetCharFormat(editor
, &startCur
, &candidateStart
, &link
);
5506 if (!(link
.dwMask
& CFM_LINK
) || (link
.dwEffects
& CFE_LINK
))
5508 /* CFE_LINK must be unset from this range */
5509 memset(&link
, 0, sizeof(CHARFORMAT2W
));
5510 link
.cbSize
= sizeof(link
);
5511 link
.dwMask
= CFM_LINK
;
5513 ME_SetCharFormat(editor
, &startCur
, &candidateStart
, &link
);
5514 /* Update candidateEnd since setting character formats may split
5515 * runs, which can cause a cursor to be at an invalid offset within
5517 while (candidateEnd
.nOffset
>= candidateEnd
.pRun
->member
.run
.len
)
5519 candidateEnd
.nOffset
-= candidateEnd
.pRun
->member
.run
.len
;
5520 candidateEnd
.pRun
= ME_FindItemFwd(candidateEnd
.pRun
, diRun
);
5525 if (candidateStart
.pRun
!= candidateEnd
.pRun
||
5526 candidateStart
.nOffset
!= candidateEnd
.nOffset
)
5528 /* CFE_LINK effect should be consistently set */
5529 link
.cbSize
= sizeof(link
);
5530 ME_GetCharFormat(editor
, &candidateStart
, &candidateEnd
, &link
);
5531 if (!(link
.dwMask
& CFM_LINK
) || !(link
.dwEffects
& CFE_LINK
))
5533 /* CFE_LINK must be set on this range */
5534 memset(&link
, 0, sizeof(CHARFORMAT2W
));
5535 link
.cbSize
= sizeof(link
);
5536 link
.dwMask
= CFM_LINK
;
5537 link
.dwEffects
= CFE_LINK
;
5538 ME_SetCharFormat(editor
, &candidateStart
, &candidateEnd
, &link
);
5542 startCur
= candidateEnd
;
5543 } while (nChars
> 0);