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
);
247 static HCURSOR hLeft
;
249 BOOL me_debug
= FALSE
;
250 HANDLE me_heap
= NULL
;
252 static BOOL ME_ListBoxRegistered
= FALSE
;
253 static BOOL ME_ComboBoxRegistered
= FALSE
;
255 static inline BOOL
is_version_nt(void)
257 return !(GetVersion() & 0x80000000);
260 static ME_TextBuffer
*ME_MakeText(void) {
261 ME_TextBuffer
*buf
= heap_alloc(sizeof(*buf
));
262 ME_DisplayItem
*p1
= ME_MakeDI(diTextStart
);
263 ME_DisplayItem
*p2
= ME_MakeDI(diTextEnd
);
269 p1
->member
.para
.next_para
= p2
;
270 p2
->member
.para
.prev_para
= p1
;
271 p2
->member
.para
.nCharOfs
= 0;
275 buf
->pCharStyle
= NULL
;
280 ME_Paragraph
*editor_first_para( ME_TextEditor
*editor
)
282 return para_next( &editor
->pBuffer
->pFirst
->member
.para
);
285 /* Note, returns the diTextEnd sentinel paragraph */
286 ME_Paragraph
*editor_end_para( ME_TextEditor
*editor
)
288 return &editor
->pBuffer
->pLast
->member
.para
;
291 static LRESULT
ME_StreamInText(ME_TextEditor
*editor
, DWORD dwFormat
, ME_InStream
*stream
, ME_Style
*style
)
294 LRESULT total_bytes_read
= 0;
295 BOOL is_read
= FALSE
;
296 DWORD cp
= CP_ACP
, copy
= 0;
297 char conv_buf
[4 + STREAMIN_BUFFER_SIZE
]; /* up to 4 additional UTF-8 bytes */
299 static const char bom_utf8
[] = {0xEF, 0xBB, 0xBF};
301 TRACE("%08x %p\n", dwFormat
, stream
);
305 WCHAR wszText
[STREAMIN_BUFFER_SIZE
+1];
309 ME_StreamInFill(stream
);
310 if (stream
->editstream
->dwError
)
314 total_bytes_read
+= stream
->dwSize
;
317 if (!(dwFormat
& SF_UNICODE
))
319 char * buf
= stream
->buffer
;
320 DWORD size
= stream
->dwSize
, end
;
325 if (stream
->dwSize
>= 3 && !memcmp(stream
->buffer
, bom_utf8
, 3))
337 memcpy(conv_buf
+ copy
, buf
, size
);
342 while ((buf
[end
-1] & 0xC0) == 0x80)
345 --total_bytes_read
; /* strange, but seems to match windows */
347 if (buf
[end
-1] & 0x80)
350 if ((buf
[end
-1] & 0xE0) == 0xC0)
352 if ((buf
[end
-1] & 0xF0) == 0xE0)
354 if ((buf
[end
-1] & 0xF8) == 0xF0)
357 if (size
- end
>= need
)
359 /* we have enough bytes for this sequence */
364 /* need more bytes, so don't transcode this sequence */
372 nWideChars
= MultiByteToWideChar(cp
, 0, buf
, end
, wszText
, STREAMIN_BUFFER_SIZE
);
379 memcpy(conv_buf
, buf
+ end
, size
- end
);
386 nWideChars
= stream
->dwSize
>> 1;
387 pText
= (WCHAR
*)stream
->buffer
;
390 ME_InsertTextFromCursor(editor
, 0, pText
, nWideChars
, style
);
391 if (stream
->dwSize
== 0)
395 return total_bytes_read
;
398 static void ME_ApplyBorderProperties(RTF_Info
*info
,
399 ME_BorderRect
*borderRect
,
400 RTFBorder
*borderDef
)
403 ME_Border
*pBorders
[] = {&borderRect
->top
,
407 for (i
= 0; i
< 4; i
++)
409 RTFColor
*colorDef
= info
->colorList
;
410 pBorders
[i
]->width
= borderDef
[i
].width
;
411 colorNum
= borderDef
[i
].color
;
412 while (colorDef
&& colorDef
->rtfCNum
!= colorNum
)
413 colorDef
= colorDef
->rtfNextColor
;
415 pBorders
[i
]->colorRef
= RGB(
416 colorDef
->rtfCRed
>= 0 ? colorDef
->rtfCRed
: 0,
417 colorDef
->rtfCGreen
>= 0 ? colorDef
->rtfCGreen
: 0,
418 colorDef
->rtfCBlue
>= 0 ? colorDef
->rtfCBlue
: 0);
420 pBorders
[i
]->colorRef
= RGB(0, 0, 0);
424 void ME_RTFCharAttrHook(RTF_Info
*info
)
427 fmt
.cbSize
= sizeof(fmt
);
431 switch(info
->rtfMinor
)
434 /* FIXME add more flags once they're implemented */
435 fmt
.dwMask
= CFM_BOLD
| CFM_ITALIC
| CFM_UNDERLINE
| CFM_UNDERLINETYPE
| CFM_STRIKEOUT
|
436 CFM_COLOR
| CFM_BACKCOLOR
| CFM_SIZE
| CFM_WEIGHT
;
437 fmt
.dwEffects
= CFE_AUTOCOLOR
| CFE_AUTOBACKCOLOR
;
438 fmt
.yHeight
= 12*20; /* 12pt */
439 fmt
.wWeight
= FW_NORMAL
;
440 fmt
.bUnderlineType
= CFU_UNDERLINE
;
443 fmt
.dwMask
= CFM_BOLD
| CFM_WEIGHT
;
444 fmt
.dwEffects
= info
->rtfParam
? CFE_BOLD
: 0;
445 fmt
.wWeight
= info
->rtfParam
? FW_BOLD
: FW_NORMAL
;
448 fmt
.dwMask
= CFM_ITALIC
;
449 fmt
.dwEffects
= info
->rtfParam
? fmt
.dwMask
: 0;
452 fmt
.dwMask
= CFM_UNDERLINETYPE
| CFM_UNDERLINE
;
453 fmt
.bUnderlineType
= CFU_UNDERLINE
;
454 fmt
.dwEffects
= info
->rtfParam
? CFE_UNDERLINE
: 0;
456 case rtfDotUnderline
:
457 fmt
.dwMask
= CFM_UNDERLINETYPE
| CFM_UNDERLINE
;
458 fmt
.bUnderlineType
= CFU_UNDERLINEDOTTED
;
459 fmt
.dwEffects
= info
->rtfParam
? CFE_UNDERLINE
: 0;
462 fmt
.dwMask
= CFM_UNDERLINETYPE
| CFM_UNDERLINE
;
463 fmt
.bUnderlineType
= CFU_UNDERLINEDOUBLE
;
464 fmt
.dwEffects
= info
->rtfParam
? CFE_UNDERLINE
: 0;
466 case rtfWordUnderline
:
467 fmt
.dwMask
= CFM_UNDERLINETYPE
| CFM_UNDERLINE
;
468 fmt
.bUnderlineType
= CFU_UNDERLINEWORD
;
469 fmt
.dwEffects
= info
->rtfParam
? CFE_UNDERLINE
: 0;
472 fmt
.dwMask
= CFM_UNDERLINE
;
476 fmt
.dwMask
= CFM_STRIKEOUT
;
477 fmt
.dwEffects
= info
->rtfParam
? fmt
.dwMask
: 0;
481 case rtfSubScrShrink
:
482 case rtfSuperScrShrink
:
484 fmt
.dwMask
= CFM_SUBSCRIPT
|CFM_SUPERSCRIPT
;
485 if (info
->rtfMinor
== rtfSubScrShrink
) fmt
.dwEffects
= CFE_SUBSCRIPT
;
486 if (info
->rtfMinor
== rtfSuperScrShrink
) fmt
.dwEffects
= CFE_SUPERSCRIPT
;
487 if (info
->rtfMinor
== rtfNoSuperSub
) fmt
.dwEffects
= 0;
490 fmt
.dwMask
= CFM_HIDDEN
;
491 fmt
.dwEffects
= info
->rtfParam
? fmt
.dwMask
: 0;
494 fmt
.dwMask
= CFM_BACKCOLOR
;
496 if (info
->rtfParam
== 0)
497 fmt
.dwEffects
= CFE_AUTOBACKCOLOR
;
498 else if (info
->rtfParam
!= rtfNoParam
)
500 RTFColor
*c
= RTFGetColor(info
, info
->rtfParam
);
501 if (c
&& c
->rtfCBlue
>= 0)
502 fmt
.crBackColor
= (c
->rtfCBlue
<<16)|(c
->rtfCGreen
<<8)|(c
->rtfCRed
);
504 fmt
.dwEffects
= CFE_AUTOBACKCOLOR
;
508 fmt
.dwMask
= CFM_COLOR
;
510 if (info
->rtfParam
== 0)
511 fmt
.dwEffects
= CFE_AUTOCOLOR
;
512 else if (info
->rtfParam
!= rtfNoParam
)
514 RTFColor
*c
= RTFGetColor(info
, info
->rtfParam
);
515 if (c
&& c
->rtfCBlue
>= 0)
516 fmt
.crTextColor
= (c
->rtfCBlue
<<16)|(c
->rtfCGreen
<<8)|(c
->rtfCRed
);
518 fmt
.dwEffects
= CFE_AUTOCOLOR
;
523 if (info
->rtfParam
!= rtfNoParam
)
525 RTFFont
*f
= RTFGetFont(info
, info
->rtfParam
);
528 MultiByteToWideChar(CP_ACP
, 0, f
->rtfFName
, -1, fmt
.szFaceName
, ARRAY_SIZE(fmt
.szFaceName
));
529 fmt
.szFaceName
[ARRAY_SIZE(fmt
.szFaceName
)-1] = '\0';
530 fmt
.bCharSet
= f
->rtfFCharSet
;
531 fmt
.dwMask
= CFM_FACE
| CFM_CHARSET
;
532 fmt
.bPitchAndFamily
= f
->rtfFPitch
| (f
->rtfFFamily
<< 4);
537 fmt
.dwMask
= CFM_SIZE
;
538 if (info
->rtfParam
!= rtfNoParam
)
539 fmt
.yHeight
= info
->rtfParam
*10;
544 RTFFlushOutputBuffer(info
);
545 /* FIXME too slow ? how come ? */
546 style2
= ME_ApplyStyle(info
->editor
, info
->style
, &fmt
);
547 ME_ReleaseStyle(info
->style
);
548 info
->style
= style2
;
549 info
->styleChanged
= TRUE
;
553 /* FIXME this function doesn't get any information about context of the RTF tag, which is very bad,
554 the same tags mean different things in different contexts */
555 void ME_RTFParAttrHook(RTF_Info
*info
)
557 switch(info
->rtfMinor
)
559 case rtfParDef
: /* restores default paragraph attributes */
560 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
561 info
->borderType
= RTFBorderParaLeft
;
562 else /* v1.0 - 3.0 */
563 info
->borderType
= RTFBorderParaTop
;
564 info
->fmt
.dwMask
= PFM_ALIGNMENT
| PFM_BORDER
| PFM_LINESPACING
| PFM_TABSTOPS
|
565 PFM_OFFSET
| PFM_RIGHTINDENT
| PFM_SPACEAFTER
| PFM_SPACEBEFORE
|
566 PFM_STARTINDENT
| PFM_RTLPARA
| PFM_NUMBERING
| PFM_NUMBERINGSTART
|
567 PFM_NUMBERINGSTYLE
| PFM_NUMBERINGTAB
;
569 info
->fmt
.wAlignment
= PFA_LEFT
;
570 info
->fmt
.cTabCount
= 0;
571 info
->fmt
.dxOffset
= info
->fmt
.dxStartIndent
= info
->fmt
.dxRightIndent
= 0;
572 info
->fmt
.wBorderWidth
= info
->fmt
.wBorders
= 0;
573 info
->fmt
.wBorderSpace
= 0;
574 info
->fmt
.bLineSpacingRule
= 0;
575 info
->fmt
.dySpaceBefore
= info
->fmt
.dySpaceAfter
= 0;
576 info
->fmt
.dyLineSpacing
= 0;
577 info
->fmt
.wEffects
&= ~PFE_RTLPARA
;
578 info
->fmt
.wNumbering
= 0;
579 info
->fmt
.wNumberingStart
= 0;
580 info
->fmt
.wNumberingStyle
= 0;
581 info
->fmt
.wNumberingTab
= 0;
583 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
585 if (info
->tableDef
&& info
->tableDef
->row_start
&&
586 info
->tableDef
->row_start
->nFlags
& MEPF_ROWEND
)
590 /* We are just after a table row. */
591 RTFFlushOutputBuffer(info
);
592 cursor
= info
->editor
->pCursors
[0];
594 if (para
== para_next( info
->tableDef
->row_start
)
595 && !cursor
.nOffset
&& !cursor
.run
->nCharOfs
)
597 /* Since the table row end, no text has been inserted, and the \intbl
598 * control word has not be used. We can confirm that we are not in a
601 info
->tableDef
->row_start
= NULL
;
602 info
->canInheritInTbl
= FALSE
;
606 else /* v1.0 - v3.0 */
608 info
->fmt
.dwMask
|= PFM_TABLE
;
609 info
->fmt
.wEffects
&= ~PFE_TABLE
;
613 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
615 while (info
->rtfParam
> info
->nestingLevel
)
617 RTFTable
*tableDef
= heap_alloc_zero(sizeof(*tableDef
));
618 tableDef
->parent
= info
->tableDef
;
619 info
->tableDef
= tableDef
;
621 RTFFlushOutputBuffer(info
);
622 if (tableDef
->row_start
&& tableDef
->row_start
->nFlags
& MEPF_ROWEND
)
624 ME_Paragraph
*para
= para_next( tableDef
->row_start
);
625 tableDef
->row_start
= table_insert_row_start_at_para( info
->editor
, para
);
631 cursor
= info
->editor
->pCursors
[0];
632 if (cursor
.nOffset
|| cursor
.run
->nCharOfs
)
633 ME_InsertTextFromCursor(info
->editor
, 0, &endl
, 1, info
->style
);
634 tableDef
->row_start
= table_insert_row_start( info
->editor
, info
->editor
->pCursors
);
637 info
->nestingLevel
++;
639 info
->canInheritInTbl
= FALSE
;
644 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
646 if (info
->nestingLevel
< 1)
652 info
->tableDef
= heap_alloc_zero(sizeof(*info
->tableDef
));
653 tableDef
= info
->tableDef
;
654 RTFFlushOutputBuffer(info
);
655 if (tableDef
->row_start
&& tableDef
->row_start
->nFlags
& MEPF_ROWEND
)
656 para
= para_next( tableDef
->row_start
);
658 para
= info
->editor
->pCursors
[0].para
;
660 tableDef
->row_start
= table_insert_row_start_at_para( info
->editor
, para
);
662 info
->nestingLevel
= 1;
663 info
->canInheritInTbl
= TRUE
;
666 } else { /* v1.0 - v3.0 */
667 info
->fmt
.dwMask
|= PFM_TABLE
;
668 info
->fmt
.wEffects
|= PFE_TABLE
;
674 if ((info
->fmt
.dwMask
& (PFM_STARTINDENT
| PFM_OFFSET
)) != (PFM_STARTINDENT
| PFM_OFFSET
))
677 fmt
.cbSize
= sizeof(fmt
);
678 editor_get_selection_para_fmt( info
->editor
, &fmt
);
679 info
->fmt
.dwMask
|= PFM_STARTINDENT
| PFM_OFFSET
;
680 info
->fmt
.dxStartIndent
= fmt
.dxStartIndent
;
681 info
->fmt
.dxOffset
= fmt
.dxOffset
;
683 if (info
->rtfMinor
== rtfFirstIndent
)
685 info
->fmt
.dxStartIndent
+= info
->fmt
.dxOffset
+ info
->rtfParam
;
686 info
->fmt
.dxOffset
= -info
->rtfParam
;
689 info
->fmt
.dxStartIndent
= info
->rtfParam
- info
->fmt
.dxOffset
;
692 info
->fmt
.dwMask
|= PFM_RIGHTINDENT
;
693 info
->fmt
.dxRightIndent
= info
->rtfParam
;
697 info
->fmt
.dwMask
|= PFM_ALIGNMENT
;
698 info
->fmt
.wAlignment
= PFA_LEFT
;
701 info
->fmt
.dwMask
|= PFM_ALIGNMENT
;
702 info
->fmt
.wAlignment
= PFA_RIGHT
;
705 info
->fmt
.dwMask
|= PFM_ALIGNMENT
;
706 info
->fmt
.wAlignment
= PFA_CENTER
;
709 if (!(info
->fmt
.dwMask
& PFM_TABSTOPS
))
712 fmt
.cbSize
= sizeof(fmt
);
713 editor_get_selection_para_fmt( info
->editor
, &fmt
);
714 memcpy(info
->fmt
.rgxTabs
, fmt
.rgxTabs
,
715 fmt
.cTabCount
* sizeof(fmt
.rgxTabs
[0]));
716 info
->fmt
.cTabCount
= fmt
.cTabCount
;
717 info
->fmt
.dwMask
|= PFM_TABSTOPS
;
719 if (info
->fmt
.cTabCount
< MAX_TAB_STOPS
&& info
->rtfParam
< 0x1000000)
720 info
->fmt
.rgxTabs
[info
->fmt
.cTabCount
++] = info
->rtfParam
;
723 info
->fmt
.dwMask
|= PFM_KEEP
;
724 info
->fmt
.wEffects
|= PFE_KEEP
;
726 case rtfNoWidowControl
:
727 info
->fmt
.dwMask
|= PFM_NOWIDOWCONTROL
;
728 info
->fmt
.wEffects
|= PFE_NOWIDOWCONTROL
;
731 info
->fmt
.dwMask
|= PFM_KEEPNEXT
;
732 info
->fmt
.wEffects
|= PFE_KEEPNEXT
;
735 info
->fmt
.dwMask
|= PFM_SPACEAFTER
;
736 info
->fmt
.dySpaceAfter
= info
->rtfParam
;
739 info
->fmt
.dwMask
|= PFM_SPACEBEFORE
;
740 info
->fmt
.dySpaceBefore
= info
->rtfParam
;
742 case rtfSpaceBetween
:
743 info
->fmt
.dwMask
|= PFM_LINESPACING
;
744 if ((int)info
->rtfParam
> 0)
746 info
->fmt
.dyLineSpacing
= info
->rtfParam
;
747 info
->fmt
.bLineSpacingRule
= 3;
751 info
->fmt
.dyLineSpacing
= info
->rtfParam
;
752 info
->fmt
.bLineSpacingRule
= 4;
755 case rtfSpaceMultiply
:
756 info
->fmt
.dwMask
|= PFM_LINESPACING
;
757 info
->fmt
.dyLineSpacing
= info
->rtfParam
* 20;
758 info
->fmt
.bLineSpacingRule
= 5;
761 info
->fmt
.dwMask
|= PFM_NUMBERING
;
762 info
->fmt
.wNumbering
= PFN_BULLET
;
765 info
->fmt
.dwMask
|= PFM_NUMBERING
;
766 info
->fmt
.wNumbering
= 2; /* FIXME: MSDN says it's not used ?? */
769 info
->borderType
= RTFBorderParaLeft
;
770 info
->fmt
.wBorders
|= 1;
771 info
->fmt
.dwMask
|= PFM_BORDER
;
774 info
->borderType
= RTFBorderParaRight
;
775 info
->fmt
.wBorders
|= 2;
776 info
->fmt
.dwMask
|= PFM_BORDER
;
779 info
->borderType
= RTFBorderParaTop
;
780 info
->fmt
.wBorders
|= 4;
781 info
->fmt
.dwMask
|= PFM_BORDER
;
783 case rtfBorderBottom
:
784 info
->borderType
= RTFBorderParaBottom
;
785 info
->fmt
.wBorders
|= 8;
786 info
->fmt
.dwMask
|= PFM_BORDER
;
788 case rtfBorderSingle
:
789 info
->fmt
.wBorders
&= ~0x700;
790 info
->fmt
.wBorders
|= 1 << 8;
791 info
->fmt
.dwMask
|= PFM_BORDER
;
794 info
->fmt
.wBorders
&= ~0x700;
795 info
->fmt
.wBorders
|= 2 << 8;
796 info
->fmt
.dwMask
|= PFM_BORDER
;
798 case rtfBorderShadow
:
799 info
->fmt
.wBorders
&= ~0x700;
800 info
->fmt
.wBorders
|= 10 << 8;
801 info
->fmt
.dwMask
|= PFM_BORDER
;
803 case rtfBorderDouble
:
804 info
->fmt
.wBorders
&= ~0x700;
805 info
->fmt
.wBorders
|= 7 << 8;
806 info
->fmt
.dwMask
|= PFM_BORDER
;
809 info
->fmt
.wBorders
&= ~0x700;
810 info
->fmt
.wBorders
|= 11 << 8;
811 info
->fmt
.dwMask
|= PFM_BORDER
;
815 int borderSide
= info
->borderType
& RTFBorderSideMask
;
816 RTFTable
*tableDef
= info
->tableDef
;
817 if ((info
->borderType
& RTFBorderTypeMask
) == RTFBorderTypeCell
)
820 if (!tableDef
|| tableDef
->numCellsDefined
>= MAX_TABLE_CELLS
)
822 border
= &tableDef
->cells
[tableDef
->numCellsDefined
].border
[borderSide
];
823 border
->width
= info
->rtfParam
;
826 info
->fmt
.wBorderWidth
= info
->rtfParam
;
827 info
->fmt
.dwMask
|= PFM_BORDER
;
831 info
->fmt
.wBorderSpace
= info
->rtfParam
;
832 info
->fmt
.dwMask
|= PFM_BORDER
;
836 RTFTable
*tableDef
= info
->tableDef
;
837 int borderSide
= info
->borderType
& RTFBorderSideMask
;
838 int borderType
= info
->borderType
& RTFBorderTypeMask
;
841 case RTFBorderTypePara
:
842 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
844 /* v1.0 - 3.0 treat paragraph and row borders the same. */
845 case RTFBorderTypeRow
:
847 tableDef
->border
[borderSide
].color
= info
->rtfParam
;
850 case RTFBorderTypeCell
:
851 if (tableDef
&& tableDef
->numCellsDefined
< MAX_TABLE_CELLS
) {
852 tableDef
->cells
[tableDef
->numCellsDefined
].border
[borderSide
].color
= info
->rtfParam
;
859 info
->fmt
.dwMask
|= PFM_RTLPARA
;
860 info
->fmt
.wEffects
|= PFE_RTLPARA
;
863 info
->fmt
.dwMask
|= PFM_RTLPARA
;
864 info
->fmt
.wEffects
&= ~PFE_RTLPARA
;
869 void ME_RTFTblAttrHook(RTF_Info
*info
)
871 switch (info
->rtfMinor
)
875 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
876 info
->borderType
= 0; /* Not sure */
877 else /* v1.0 - 3.0 */
878 info
->borderType
= RTFBorderRowTop
;
879 if (!info
->tableDef
) {
880 info
->tableDef
= ME_MakeTableDef(info
->editor
);
882 ME_InitTableDef(info
->editor
, info
->tableDef
);
891 info
->tableDef
= ME_MakeTableDef(info
->editor
);
893 cellNum
= info
->tableDef
->numCellsDefined
;
894 if (cellNum
>= MAX_TABLE_CELLS
)
896 info
->tableDef
->cells
[cellNum
].rightBoundary
= info
->rtfParam
;
897 if (cellNum
< MAX_TAB_STOPS
)
899 /* Tab stops were used to store cell positions before v4.1 but v4.1
900 * still seems to set the tabstops without using them. */
901 PARAFORMAT2
*fmt
= &info
->editor
->pCursors
[0].para
->fmt
;
902 fmt
->rgxTabs
[cellNum
] &= ~0x00FFFFFF;
903 fmt
->rgxTabs
[cellNum
] |= 0x00FFFFFF & info
->rtfParam
;
905 info
->tableDef
->numCellsDefined
++;
909 info
->borderType
= RTFBorderRowTop
;
912 info
->borderType
= RTFBorderRowLeft
;
914 case rtfRowBordBottom
:
915 info
->borderType
= RTFBorderRowBottom
;
917 case rtfRowBordRight
:
918 info
->borderType
= RTFBorderRowRight
;
921 info
->borderType
= RTFBorderCellTop
;
923 case rtfCellBordLeft
:
924 info
->borderType
= RTFBorderCellLeft
;
926 case rtfCellBordBottom
:
927 info
->borderType
= RTFBorderCellBottom
;
929 case rtfCellBordRight
:
930 info
->borderType
= RTFBorderCellRight
;
934 info
->tableDef
->gapH
= info
->rtfParam
;
938 info
->tableDef
->leftEdge
= info
->rtfParam
;
943 void ME_RTFSpecialCharHook(RTF_Info
*info
)
945 RTFTable
*tableDef
= info
->tableDef
;
946 switch (info
->rtfMinor
)
949 if (info
->editor
->bEmulateVersion10
) /* v1.0 - v3.0 */
951 /* else fall through since v4.1 treats rtfNestCell and rtfCell the same */
955 RTFFlushOutputBuffer(info
);
956 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
958 if (tableDef
->row_start
)
960 if (!info
->nestingLevel
&& tableDef
->row_start
->nFlags
& MEPF_ROWEND
)
962 ME_Paragraph
*para
= para_next( tableDef
->row_start
);
963 tableDef
->row_start
= table_insert_row_start_at_para( info
->editor
, para
);
964 info
->nestingLevel
= 1;
966 table_insert_cell( info
->editor
, info
->editor
->pCursors
);
969 else /* v1.0 - v3.0 */
971 ME_Paragraph
*para
= info
->editor
->pCursors
[0].para
;
973 if (para_in_table( para
) && tableDef
->numCellsInserted
< tableDef
->numCellsDefined
)
976 ME_InsertTextFromCursor(info
->editor
, 0, &tab
, 1, info
->style
);
977 tableDef
->numCellsInserted
++;
982 if (info
->editor
->bEmulateVersion10
) /* v1.0 - v3.0 */
984 /* else fall through since v4.1 treats rtfNestRow and rtfRow the same */
994 RTFFlushOutputBuffer(info
);
995 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
997 if (!tableDef
->row_start
) break;
998 if (!info
->nestingLevel
&& tableDef
->row_start
->nFlags
& MEPF_ROWEND
)
1000 para
= para_next( tableDef
->row_start
);
1001 tableDef
->row_start
= table_insert_row_start_at_para( info
->editor
, para
);
1002 info
->nestingLevel
++;
1004 para
= tableDef
->row_start
;
1005 cell
= table_row_first_cell( para
);
1006 assert( cell
&& !cell_prev( cell
) );
1007 if (tableDef
->numCellsDefined
< 1)
1009 /* 2000 twips appears to be the cell size that native richedit uses
1010 * when no cell sizes are specified. */
1011 const int default_size
= 2000;
1012 int right_boundary
= default_size
;
1013 cell
->nRightBoundary
= right_boundary
;
1014 while (cell_next( cell
))
1016 cell
= cell_next( cell
);
1017 right_boundary
+= default_size
;
1018 cell
->nRightBoundary
= right_boundary
;
1020 para
= table_insert_cell( info
->editor
, info
->editor
->pCursors
);
1021 cell
= para_cell( para
);
1022 cell
->nRightBoundary
= right_boundary
;
1026 for (i
= 0; i
< tableDef
->numCellsDefined
; i
++)
1028 RTFCell
*cellDef
= &tableDef
->cells
[i
];
1029 cell
->nRightBoundary
= cellDef
->rightBoundary
;
1030 ME_ApplyBorderProperties( info
, &cell
->border
, cellDef
->border
);
1031 cell
= cell_next( cell
);
1034 para
= table_insert_cell( info
->editor
, info
->editor
->pCursors
);
1035 cell
= para_cell( para
);
1038 /* Cell for table row delimiter is empty */
1039 cell
->nRightBoundary
= tableDef
->cells
[i
- 1].rightBoundary
;
1042 run
= para_first_run( cell_first_para( cell
) );
1043 if (info
->editor
->pCursors
[0].run
!= run
|| info
->editor
->pCursors
[0].nOffset
)
1046 /* Delete inserted cells that aren't defined. */
1047 info
->editor
->pCursors
[1].run
= run
;
1048 info
->editor
->pCursors
[1].para
= run
->para
;
1049 info
->editor
->pCursors
[1].nOffset
= 0;
1050 nOfs
= ME_GetCursorOfs(&info
->editor
->pCursors
[1]);
1051 nChars
= ME_GetCursorOfs(&info
->editor
->pCursors
[0]) - nOfs
;
1052 ME_InternalDeleteText(info
->editor
, &info
->editor
->pCursors
[1],
1056 para
= table_insert_row_end( info
->editor
, info
->editor
->pCursors
);
1057 para
->fmt
.dxOffset
= abs(info
->tableDef
->gapH
);
1058 para
->fmt
.dxStartIndent
= info
->tableDef
->leftEdge
;
1059 ME_ApplyBorderProperties( info
, ¶
->border
, tableDef
->border
);
1060 info
->nestingLevel
--;
1061 if (!info
->nestingLevel
)
1063 if (info
->canInheritInTbl
) tableDef
->row_start
= para
;
1066 while (info
->tableDef
)
1068 tableDef
= info
->tableDef
;
1069 info
->tableDef
= tableDef
->parent
;
1070 heap_free(tableDef
);
1076 info
->tableDef
= tableDef
->parent
;
1077 heap_free(tableDef
);
1080 else /* v1.0 - v3.0 */
1084 para
= info
->editor
->pCursors
[0].para
;
1085 para
->fmt
.dxOffset
= info
->tableDef
->gapH
;
1086 para
->fmt
.dxStartIndent
= info
->tableDef
->leftEdge
;
1088 ME_ApplyBorderProperties( info
, ¶
->border
, tableDef
->border
);
1089 while (tableDef
->numCellsInserted
< tableDef
->numCellsDefined
)
1092 ME_InsertTextFromCursor(info
->editor
, 0, &tab
, 1, info
->style
);
1093 tableDef
->numCellsInserted
++;
1095 para
->fmt
.cTabCount
= min(tableDef
->numCellsDefined
, MAX_TAB_STOPS
);
1096 if (!tableDef
->numCellsDefined
) para
->fmt
.wEffects
&= ~PFE_TABLE
;
1097 ME_InsertTextFromCursor(info
->editor
, 0, &endl
, 1, info
->style
);
1098 tableDef
->numCellsInserted
= 0;
1104 if (info
->editor
->bEmulateVersion10
) /* v1.0 - 3.0 */
1108 RTFFlushOutputBuffer(info
);
1109 para
= info
->editor
->pCursors
[0].para
;
1110 if (para_in_table( para
))
1112 /* rtfPar is treated like a space within a table. */
1113 info
->rtfClass
= rtfText
;
1114 info
->rtfMajor
= ' ';
1116 else if (info
->rtfMinor
== rtfPar
&& tableDef
)
1117 tableDef
->numCellsInserted
= 0;
1123 static HRESULT
insert_static_object(ME_TextEditor
*editor
, HENHMETAFILE hemf
, HBITMAP hbmp
,
1126 LPOLEOBJECT lpObject
= NULL
;
1127 LPSTORAGE lpStorage
= NULL
;
1128 LPOLECLIENTSITE lpClientSite
= NULL
;
1129 LPDATAOBJECT lpDataObject
= NULL
;
1130 LPOLECACHE lpOleCache
= NULL
;
1131 LPRICHEDITOLE lpReOle
= NULL
;
1135 HRESULT hr
= E_FAIL
;
1140 stgm
.tymed
= TYMED_ENHMF
;
1141 stgm
.u
.hEnhMetaFile
= hemf
;
1142 fm
.cfFormat
= CF_ENHMETAFILE
;
1146 stgm
.tymed
= TYMED_GDI
;
1147 stgm
.u
.hBitmap
= hbmp
;
1148 fm
.cfFormat
= CF_BITMAP
;
1152 stgm
.pUnkForRelease
= NULL
;
1155 fm
.dwAspect
= DVASPECT_CONTENT
;
1157 fm
.tymed
= stgm
.tymed
;
1161 if (!CreateIRichEditOle(NULL
, editor
, (LPVOID
*)&editor
->reOle
))
1165 if (OleCreateDefaultHandler(&CLSID_NULL
, NULL
, &IID_IOleObject
, (void**)&lpObject
) == S_OK
&&
1166 IUnknown_QueryInterface(editor
->reOle
, &IID_IRichEditOle
, (void**)&lpReOle
) == S_OK
&&
1167 IRichEditOle_GetClientSite(lpReOle
, &lpClientSite
) == S_OK
&&
1168 IOleObject_SetClientSite(lpObject
, lpClientSite
) == S_OK
&&
1169 IOleObject_GetUserClassID(lpObject
, &clsid
) == S_OK
&&
1170 IOleObject_QueryInterface(lpObject
, &IID_IOleCache
, (void**)&lpOleCache
) == S_OK
&&
1171 IOleCache_Cache(lpOleCache
, &fm
, 0, &conn
) == S_OK
&&
1172 IOleObject_QueryInterface(lpObject
, &IID_IDataObject
, (void**)&lpDataObject
) == S_OK
&&
1173 IDataObject_SetData(lpDataObject
, &fm
, &stgm
, TRUE
) == S_OK
)
1177 reobject
.cbStruct
= sizeof(reobject
);
1178 reobject
.cp
= REO_CP_SELECTION
;
1179 reobject
.clsid
= clsid
;
1180 reobject
.poleobj
= lpObject
;
1181 reobject
.pstg
= lpStorage
;
1182 reobject
.polesite
= lpClientSite
;
1183 /* convert from twips to .01 mm */
1184 reobject
.sizel
.cx
= MulDiv(sz
->cx
, 254, 144);
1185 reobject
.sizel
.cy
= MulDiv(sz
->cy
, 254, 144);
1186 reobject
.dvaspect
= DVASPECT_CONTENT
;
1187 reobject
.dwFlags
= 0; /* FIXME */
1188 reobject
.dwUser
= 0;
1190 ME_InsertOLEFromCursor(editor
, &reobject
, 0);
1194 if (lpObject
) IOleObject_Release(lpObject
);
1195 if (lpClientSite
) IOleClientSite_Release(lpClientSite
);
1196 if (lpStorage
) IStorage_Release(lpStorage
);
1197 if (lpDataObject
) IDataObject_Release(lpDataObject
);
1198 if (lpOleCache
) IOleCache_Release(lpOleCache
);
1199 if (lpReOle
) IRichEditOle_Release(lpReOle
);
1204 static void ME_RTFReadShpPictGroup( RTF_Info
*info
)
1212 if (info
->rtfClass
== rtfEOF
) return;
1213 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1215 if (--level
== 0) break;
1217 else if (RTFCheckCM( info
, rtfGroup
, rtfBeginGroup
))
1223 RTFRouteToken( info
);
1224 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1229 RTFRouteToken( info
); /* feed "}" back to router */
1233 static DWORD
read_hex_data( RTF_Info
*info
, BYTE
**out
)
1235 DWORD read
= 0, size
= 1024;
1241 if (info
->rtfClass
!= rtfText
)
1243 ERR("Called with incorrect token\n");
1247 buf
= HeapAlloc( GetProcessHeap(), 0, size
);
1250 val
= info
->rtfMajor
;
1251 for (flip
= TRUE
;; flip
= !flip
)
1253 RTFGetToken( info
);
1254 if (info
->rtfClass
== rtfEOF
)
1256 HeapFree( GetProcessHeap(), 0, buf
);
1259 if (info
->rtfClass
!= rtfText
) break;
1265 buf
= HeapReAlloc( GetProcessHeap(), 0, buf
, size
);
1268 buf
[read
++] = RTFCharToHex(val
) * 16 + RTFCharToHex(info
->rtfMajor
);
1271 val
= info
->rtfMajor
;
1273 if (flip
) FIXME("wrong hex string\n");
1279 static void ME_RTFReadPictGroup(RTF_Info
*info
)
1282 BYTE
*buffer
= NULL
;
1287 enum gfxkind
{gfx_unknown
= 0, gfx_enhmetafile
, gfx_metafile
, gfx_dib
} gfx
= gfx_unknown
;
1295 RTFGetToken( info
);
1297 if (info
->rtfClass
== rtfText
)
1302 size
= read_hex_data( info
, &buffer
);
1306 RTFSkipGroup( info
);
1308 } /* We potentially have a new token so fall through. */
1310 if (info
->rtfClass
== rtfEOF
) return;
1312 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1314 if (--level
== 0) break;
1317 if (RTFCheckCM( info
, rtfGroup
, rtfBeginGroup
))
1322 if (!RTFCheckCM( info
, rtfControl
, rtfPictAttr
))
1324 RTFRouteToken( info
);
1325 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1330 if (RTFCheckMM( info
, rtfPictAttr
, rtfWinMetafile
))
1332 mfp
.mm
= info
->rtfParam
;
1335 else if (RTFCheckMM( info
, rtfPictAttr
, rtfDevIndBitmap
))
1337 if (info
->rtfParam
!= 0) FIXME("dibitmap should be 0 (%d)\n", info
->rtfParam
);
1340 else if (RTFCheckMM( info
, rtfPictAttr
, rtfEmfBlip
))
1341 gfx
= gfx_enhmetafile
;
1342 else if (RTFCheckMM( info
, rtfPictAttr
, rtfPicWid
))
1343 mfp
.xExt
= info
->rtfParam
;
1344 else if (RTFCheckMM( info
, rtfPictAttr
, rtfPicHt
))
1345 mfp
.yExt
= info
->rtfParam
;
1346 else if (RTFCheckMM( info
, rtfPictAttr
, rtfPicGoalWid
))
1347 sz
.cx
= info
->rtfParam
;
1348 else if (RTFCheckMM( info
, rtfPictAttr
, rtfPicGoalHt
))
1349 sz
.cy
= info
->rtfParam
;
1351 FIXME("Non supported attribute: %d %d %d\n", info
->rtfClass
, info
->rtfMajor
, info
->rtfMinor
);
1358 case gfx_enhmetafile
:
1359 if ((hemf
= SetEnhMetaFileBits( size
, buffer
)))
1360 insert_static_object( info
->editor
, hemf
, NULL
, &sz
);
1363 if ((hemf
= SetWinMetaFileBits( size
, buffer
, NULL
, &mfp
)))
1364 insert_static_object( info
->editor
, hemf
, NULL
, &sz
);
1368 BITMAPINFO
*bi
= (BITMAPINFO
*)buffer
;
1370 unsigned nc
= bi
->bmiHeader
.biClrUsed
;
1372 /* not quite right, especially for bitfields type of compression */
1373 if (!nc
&& bi
->bmiHeader
.biBitCount
<= 8)
1374 nc
= 1 << bi
->bmiHeader
.biBitCount
;
1375 if ((hbmp
= CreateDIBitmap( hdc
, &bi
->bmiHeader
,
1376 CBM_INIT
, (char*)(bi
+ 1) + nc
* sizeof(RGBQUAD
),
1377 bi
, DIB_RGB_COLORS
)) )
1378 insert_static_object( info
->editor
, NULL
, hbmp
, &sz
);
1379 ReleaseDC( 0, hdc
);
1386 HeapFree( GetProcessHeap(), 0, buffer
);
1387 RTFRouteToken( info
); /* feed "}" back to router */
1391 /* for now, lookup the \result part and use it, whatever the object */
1392 static void ME_RTFReadObjectGroup(RTF_Info
*info
)
1397 if (info
->rtfClass
== rtfEOF
)
1399 if (RTFCheckCM(info
, rtfGroup
, rtfEndGroup
))
1401 if (RTFCheckCM(info
, rtfGroup
, rtfBeginGroup
))
1404 if (info
->rtfClass
== rtfEOF
)
1406 if (RTFCheckCMM(info
, rtfControl
, rtfDestination
, rtfObjResult
))
1410 while (RTFGetToken (info
) != rtfEOF
)
1412 if (info
->rtfClass
== rtfGroup
)
1414 if (info
->rtfMajor
== rtfBeginGroup
) level
++;
1415 else if (info
->rtfMajor
== rtfEndGroup
&& --level
< 0) break;
1417 RTFRouteToken(info
);
1420 else RTFSkipGroup(info
);
1423 if (!RTFCheckCM (info
, rtfControl
, rtfObjAttr
))
1425 FIXME("Non supported attribute: %d %d %d\n", info
->rtfClass
, info
->rtfMajor
, info
->rtfMinor
);
1429 RTFRouteToken(info
); /* feed "}" back to router */
1432 static void ME_RTFReadParnumGroup( RTF_Info
*info
)
1434 int level
= 1, type
= -1;
1435 WORD indent
= 0, start
= 1;
1436 WCHAR txt_before
= 0, txt_after
= 0;
1440 RTFGetToken( info
);
1442 if (RTFCheckCMM( info
, rtfControl
, rtfDestination
, rtfParNumTextBefore
) ||
1443 RTFCheckCMM( info
, rtfControl
, rtfDestination
, rtfParNumTextAfter
))
1445 int loc
= info
->rtfMinor
;
1447 RTFGetToken( info
);
1448 if (info
->rtfClass
== rtfText
)
1450 if (loc
== rtfParNumTextBefore
)
1451 txt_before
= info
->rtfMajor
;
1453 txt_after
= info
->rtfMajor
;
1456 /* falling through to catch EOFs and group level changes */
1459 if (info
->rtfClass
== rtfEOF
)
1462 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1464 if (--level
== 0) break;
1468 if (RTFCheckCM( info
, rtfGroup
, rtfBeginGroup
))
1474 /* Ignore non para-attr */
1475 if (!RTFCheckCM( info
, rtfControl
, rtfParAttr
))
1478 switch (info
->rtfMinor
)
1480 case rtfParLevel
: /* Para level is ignored */
1487 case rtfParNumDecimal
:
1490 case rtfParNumULetter
:
1491 type
= PFN_UCLETTER
;
1493 case rtfParNumURoman
:
1496 case rtfParNumLLetter
:
1497 type
= PFN_LCLETTER
;
1499 case rtfParNumLRoman
:
1503 case rtfParNumIndent
:
1504 indent
= info
->rtfParam
;
1506 case rtfParNumStartAt
:
1507 start
= info
->rtfParam
;
1514 info
->fmt
.dwMask
|= (PFM_NUMBERING
| PFM_NUMBERINGSTART
| PFM_NUMBERINGSTYLE
| PFM_NUMBERINGTAB
);
1515 info
->fmt
.wNumbering
= type
;
1516 info
->fmt
.wNumberingStart
= start
;
1517 info
->fmt
.wNumberingStyle
= PFNS_PAREN
;
1518 if (type
!= PFN_BULLET
)
1520 if (txt_before
== 0 && txt_after
== 0)
1521 info
->fmt
.wNumberingStyle
= PFNS_PLAIN
;
1522 else if (txt_after
== '.')
1523 info
->fmt
.wNumberingStyle
= PFNS_PERIOD
;
1524 else if (txt_before
== '(' && txt_after
== ')')
1525 info
->fmt
.wNumberingStyle
= PFNS_PARENS
;
1527 info
->fmt
.wNumberingTab
= indent
;
1530 TRACE("type %d indent %d start %d txt before %04x txt after %04x\n",
1531 type
, indent
, start
, txt_before
, txt_after
);
1533 RTFRouteToken( info
); /* feed "}" back to router */
1536 static void ME_RTFReadHook(RTF_Info
*info
)
1538 switch(info
->rtfClass
)
1541 switch(info
->rtfMajor
)
1544 if (info
->stackTop
< maxStack
) {
1545 info
->stack
[info
->stackTop
].style
= info
->style
;
1546 ME_AddRefStyle(info
->style
);
1547 info
->stack
[info
->stackTop
].codePage
= info
->codePage
;
1548 info
->stack
[info
->stackTop
].unicodeLength
= info
->unicodeLength
;
1551 info
->styleChanged
= FALSE
;
1555 RTFFlushOutputBuffer(info
);
1557 if (info
->stackTop
<= 0)
1558 info
->rtfClass
= rtfEOF
;
1559 if (info
->stackTop
< 0)
1562 ME_ReleaseStyle(info
->style
);
1563 info
->style
= info
->stack
[info
->stackTop
].style
;
1564 info
->codePage
= info
->stack
[info
->stackTop
].codePage
;
1565 info
->unicodeLength
= info
->stack
[info
->stackTop
].unicodeLength
;
1574 ME_StreamInFill(ME_InStream
*stream
)
1576 stream
->editstream
->dwError
= stream
->editstream
->pfnCallback(stream
->editstream
->dwCookie
,
1577 (BYTE
*)stream
->buffer
,
1578 sizeof(stream
->buffer
),
1579 (LONG
*)&stream
->dwSize
);
1583 static LRESULT
ME_StreamIn(ME_TextEditor
*editor
, DWORD format
, EDITSTREAM
*stream
, BOOL stripLastCR
)
1587 int from
, to
, nUndoMode
;
1588 int nEventMask
= editor
->nEventMask
;
1589 ME_InStream inStream
;
1590 BOOL invalidRTF
= FALSE
;
1591 ME_Cursor
*selStart
, *selEnd
;
1592 LRESULT num_read
= 0; /* bytes read for SF_TEXT, non-control chars inserted for SF_RTF */
1594 TRACE("stream==%p editor==%p format==0x%X\n", stream
, editor
, format
);
1595 editor
->nEventMask
= 0;
1597 ME_GetSelectionOfs(editor
, &from
, &to
);
1598 if (format
& SFF_SELECTION
&& editor
->mode
& TM_RICHTEXT
)
1600 ME_GetSelection(editor
, &selStart
, &selEnd
);
1601 style
= ME_GetSelectionInsertStyle(editor
);
1603 ME_InternalDeleteText(editor
, selStart
, to
- from
, FALSE
);
1605 /* Don't insert text at the end of the table row */
1606 if (!editor
->bEmulateVersion10
) /* v4.1 */
1608 ME_Paragraph
*para
= editor
->pCursors
->para
;
1609 if (para
->nFlags
& (MEPF_ROWSTART
| MEPF_ROWEND
))
1611 para
= para_next( para
);
1612 editor
->pCursors
[0].para
= para
;
1613 editor
->pCursors
[0].run
= para_first_run( para
);
1614 editor
->pCursors
[0].nOffset
= 0;
1616 editor
->pCursors
[1] = editor
->pCursors
[0];
1618 else /* v1.0 - 3.0 */
1620 if (editor
->pCursors
[0].run
->nFlags
& MERF_ENDPARA
&&
1621 para_in_table( editor
->pCursors
[0].para
))
1627 style
= editor
->pBuffer
->pDefaultStyle
;
1628 ME_AddRefStyle(style
);
1629 set_selection_cursors(editor
, 0, 0);
1630 ME_InternalDeleteText(editor
, &editor
->pCursors
[1],
1631 ME_GetTextLength(editor
), FALSE
);
1633 ME_ClearTempStyle(editor
);
1634 editor_set_default_para_fmt( editor
, &editor
->pCursors
[0].para
->fmt
);
1638 /* Back up undo mode to a local variable */
1639 nUndoMode
= editor
->nUndoMode
;
1641 /* Only create an undo if SFF_SELECTION is set */
1642 if (!(format
& SFF_SELECTION
))
1643 editor
->nUndoMode
= umIgnore
;
1645 inStream
.editstream
= stream
;
1646 inStream
.editstream
->dwError
= 0;
1647 inStream
.dwSize
= 0;
1648 inStream
.dwUsed
= 0;
1650 if (format
& SF_RTF
)
1652 /* Check if it's really RTF, and if it is not, use plain text */
1653 ME_StreamInFill(&inStream
);
1654 if (!inStream
.editstream
->dwError
)
1656 if ((!editor
->bEmulateVersion10
&& strncmp(inStream
.buffer
, "{\\rtf", 5) && strncmp(inStream
.buffer
, "{\\urtf", 6))
1657 || (editor
->bEmulateVersion10
&& *inStream
.buffer
!= '{'))
1660 inStream
.editstream
->dwError
= -16;
1665 if (!invalidRTF
&& !inStream
.editstream
->dwError
)
1668 from
= ME_GetCursorOfs(&editor
->pCursors
[0]);
1669 if (format
& SF_RTF
) {
1671 /* setup the RTF parser */
1672 memset(&parser
, 0, sizeof parser
);
1673 RTFSetEditStream(&parser
, &inStream
);
1674 parser
.rtfFormat
= format
&(SF_TEXT
|SF_RTF
);
1675 parser
.editor
= editor
;
1676 parser
.style
= style
;
1677 WriterInit(&parser
);
1679 RTFSetReadHook(&parser
, ME_RTFReadHook
);
1680 RTFSetDestinationCallback(&parser
, rtfShpPict
, ME_RTFReadShpPictGroup
);
1681 RTFSetDestinationCallback(&parser
, rtfPict
, ME_RTFReadPictGroup
);
1682 RTFSetDestinationCallback(&parser
, rtfObject
, ME_RTFReadObjectGroup
);
1683 RTFSetDestinationCallback(&parser
, rtfParNumbering
, ME_RTFReadParnumGroup
);
1684 if (!parser
.editor
->bEmulateVersion10
) /* v4.1 */
1686 RTFSetDestinationCallback(&parser
, rtfNoNestTables
, RTFSkipGroup
);
1687 RTFSetDestinationCallback(&parser
, rtfNestTableProps
, RTFReadGroup
);
1691 /* do the parsing */
1693 RTFFlushOutputBuffer(&parser
);
1694 if (!editor
->bEmulateVersion10
) /* v4.1 */
1696 if (parser
.tableDef
&& parser
.tableDef
->row_start
&&
1697 (parser
.nestingLevel
> 0 || parser
.canInheritInTbl
))
1699 /* Delete any incomplete table row at the end of the rich text. */
1703 parser
.rtfMinor
= rtfRow
;
1704 /* Complete the table row before deleting it.
1705 * By doing it this way we will have the current paragraph format set
1706 * properly to reflect that is not in the complete table, and undo items
1707 * will be added for this change to the current paragraph format. */
1708 if (parser
.nestingLevel
> 0)
1710 while (parser
.nestingLevel
> 1)
1711 ME_RTFSpecialCharHook(&parser
); /* Decrements nestingLevel */
1712 para
= parser
.tableDef
->row_start
;
1713 ME_RTFSpecialCharHook(&parser
);
1717 para
= parser
.tableDef
->row_start
;
1718 ME_RTFSpecialCharHook(&parser
);
1719 assert( para
->nFlags
& MEPF_ROWEND
);
1720 para
= para_next( para
);
1723 editor
->pCursors
[1].para
= para
;
1724 editor
->pCursors
[1].run
= para_first_run( para
);
1725 editor
->pCursors
[1].nOffset
= 0;
1726 nOfs
= ME_GetCursorOfs(&editor
->pCursors
[1]);
1727 nChars
= ME_GetCursorOfs(&editor
->pCursors
[0]) - nOfs
;
1728 ME_InternalDeleteText(editor
, &editor
->pCursors
[1], nChars
, TRUE
);
1729 if (parser
.tableDef
) parser
.tableDef
->row_start
= NULL
;
1732 RTFDestroy(&parser
);
1734 if (parser
.stackTop
> 0)
1736 while (--parser
.stackTop
>= 0)
1738 ME_ReleaseStyle(parser
.style
);
1739 parser
.style
= parser
.stack
[parser
.stackTop
].style
;
1741 if (!inStream
.editstream
->dwError
)
1742 inStream
.editstream
->dwError
= HRESULT_FROM_WIN32(ERROR_HANDLE_EOF
);
1745 /* Remove last line break, as mandated by tests. This is not affected by
1746 CR/LF counters, since RTF streaming presents only \para tokens, which
1747 are converted according to the standard rules: \r for 2.0, \r\n for 1.0
1749 if (stripLastCR
&& !(format
& SFF_SELECTION
)) {
1751 ME_GetSelection(editor
, &selStart
, &selEnd
);
1752 newto
= ME_GetCursorOfs(selEnd
);
1753 if (newto
> to
+ (editor
->bEmulateVersion10
? 1 : 0)) {
1754 WCHAR lastchar
[3] = {'\0', '\0'};
1755 int linebreakSize
= editor
->bEmulateVersion10
? 2 : 1;
1756 ME_Cursor linebreakCursor
= *selEnd
, lastcharCursor
= *selEnd
;
1759 /* Set the final eop to the char fmt of the last char */
1760 cf
.cbSize
= sizeof(cf
);
1761 cf
.dwMask
= CFM_ALL2
;
1762 ME_MoveCursorChars(editor
, &lastcharCursor
, -1, FALSE
);
1763 ME_GetCharFormat(editor
, &lastcharCursor
, &linebreakCursor
, &cf
);
1764 set_selection_cursors(editor
, newto
, -1);
1765 ME_SetSelectionCharFormat(editor
, &cf
);
1766 set_selection_cursors(editor
, newto
, newto
);
1768 ME_MoveCursorChars(editor
, &linebreakCursor
, -linebreakSize
, FALSE
);
1769 ME_GetTextW(editor
, lastchar
, 2, &linebreakCursor
, linebreakSize
, FALSE
, FALSE
);
1770 if (lastchar
[0] == '\r' && (lastchar
[1] == '\n' || lastchar
[1] == '\0')) {
1771 ME_InternalDeleteText(editor
, &linebreakCursor
, linebreakSize
, FALSE
);
1775 to
= ME_GetCursorOfs(&editor
->pCursors
[0]);
1776 num_read
= to
- from
;
1778 style
= parser
.style
;
1780 else if (format
& SF_TEXT
)
1782 num_read
= ME_StreamInText(editor
, format
, &inStream
, style
);
1783 to
= ME_GetCursorOfs(&editor
->pCursors
[0]);
1786 ERR("EM_STREAMIN without SF_TEXT or SF_RTF\n");
1787 /* put the cursor at the top */
1788 if (!(format
& SFF_SELECTION
))
1789 set_selection_cursors(editor
, 0, 0);
1790 cursor_from_char_ofs( editor
, from
, &start
);
1791 ME_UpdateLinkAttribute(editor
, &start
, to
- from
);
1794 /* Restore saved undo mode */
1795 editor
->nUndoMode
= nUndoMode
;
1797 /* even if we didn't add an undo, we need to commit anything on the stack */
1798 ME_CommitUndo(editor
);
1800 /* If SFF_SELECTION isn't set, delete any undos from before we started too */
1801 if (!(format
& SFF_SELECTION
))
1802 ME_EmptyUndoStack(editor
);
1804 ME_ReleaseStyle(style
);
1805 editor
->nEventMask
= nEventMask
;
1806 ME_UpdateRepaint(editor
, FALSE
);
1807 if (!(format
& SFF_SELECTION
)) {
1808 ME_ClearTempStyle(editor
);
1810 update_caret(editor
);
1811 ME_SendSelChange(editor
);
1812 ME_SendRequestResize(editor
, FALSE
);
1818 typedef struct tagME_RTFStringStreamStruct
1823 } ME_RTFStringStreamStruct
;
1825 static DWORD CALLBACK
ME_ReadFromRTFString(DWORD_PTR dwCookie
, LPBYTE lpBuff
, LONG cb
, LONG
*pcb
)
1827 ME_RTFStringStreamStruct
*pStruct
= (ME_RTFStringStreamStruct
*)dwCookie
;
1830 count
= min(cb
, pStruct
->length
- pStruct
->pos
);
1831 memmove(lpBuff
, pStruct
->string
+ pStruct
->pos
, count
);
1832 pStruct
->pos
+= count
;
1838 ME_StreamInRTFString(ME_TextEditor
*editor
, BOOL selection
, char *string
)
1841 ME_RTFStringStreamStruct data
;
1843 data
.string
= string
;
1844 data
.length
= strlen(string
);
1846 es
.dwCookie
= (DWORD_PTR
)&data
;
1847 es
.pfnCallback
= ME_ReadFromRTFString
;
1848 ME_StreamIn(editor
, SF_RTF
| (selection
? SFF_SELECTION
: 0), &es
, TRUE
);
1853 ME_FindText(ME_TextEditor
*editor
, DWORD flags
, const CHARRANGE
*chrg
, const WCHAR
*text
, CHARRANGE
*chrgText
)
1855 const int nLen
= lstrlenW(text
);
1856 const int nTextLen
= ME_GetTextLength(editor
);
1859 WCHAR wLastChar
= ' ';
1861 TRACE("flags==0x%08x, chrg->cpMin==%d, chrg->cpMax==%d text==%s\n",
1862 flags
, chrg
->cpMin
, chrg
->cpMax
, debugstr_w(text
));
1864 if (flags
& ~(FR_DOWN
| FR_MATCHCASE
| FR_WHOLEWORD
))
1865 FIXME("Flags 0x%08x not implemented\n",
1866 flags
& ~(FR_DOWN
| FR_MATCHCASE
| FR_WHOLEWORD
));
1869 if (chrg
->cpMax
== -1)
1872 nMax
= chrg
->cpMax
> nTextLen
? nTextLen
: chrg
->cpMax
;
1874 /* In 1.0 emulation, if cpMax reaches end of text, add the FR_DOWN flag */
1875 if (editor
->bEmulateVersion10
&& nMax
== nTextLen
)
1880 /* In 1.0 emulation, cpMin must always be no greater than cpMax */
1881 if (editor
->bEmulateVersion10
&& nMax
< nMin
)
1885 chrgText
->cpMin
= -1;
1886 chrgText
->cpMax
= -1;
1891 /* when searching up, if cpMin < cpMax, then instead of searching
1892 * on [cpMin,cpMax], we search on [0,cpMin], otherwise, search on
1893 * [cpMax, cpMin]. The exception is when cpMax is -1, in which
1894 * case, it is always bigger than cpMin.
1896 if (!editor
->bEmulateVersion10
&& !(flags
& FR_DOWN
))
1900 nMax
= nMin
> nTextLen
? nTextLen
: nMin
;
1901 if (nMin
< nSwap
|| chrg
->cpMax
== -1)
1907 if (!nLen
|| nMin
< 0 || nMax
< 0 || nMax
< nMin
)
1910 chrgText
->cpMin
= chrgText
->cpMax
= -1;
1914 if (flags
& FR_DOWN
) /* Forward search */
1916 /* If possible, find the character before where the search starts */
1917 if ((flags
& FR_WHOLEWORD
) && nMin
)
1919 cursor_from_char_ofs( editor
, nMin
- 1, &cursor
);
1920 wLastChar
= *get_text( cursor
.run
, cursor
.nOffset
);
1921 ME_MoveCursorChars(editor
, &cursor
, 1, FALSE
);
1923 else cursor_from_char_ofs( editor
, nMin
, &cursor
);
1925 while (cursor
.run
&& ME_GetCursorOfs(&cursor
) + nLen
<= nMax
)
1927 ME_Run
*run
= cursor
.run
;
1928 int nCurStart
= cursor
.nOffset
;
1931 while (run
&& ME_CharCompare( *get_text( run
, nCurStart
+ nMatched
), text
[nMatched
], (flags
& FR_MATCHCASE
)))
1933 if ((flags
& FR_WHOLEWORD
) && iswalnum(wLastChar
))
1937 if (nMatched
== nLen
)
1939 ME_Run
*next_run
= run
;
1940 int nNextStart
= nCurStart
;
1943 /* Check to see if next character is a whitespace */
1944 if (flags
& FR_WHOLEWORD
)
1946 if (nCurStart
+ nMatched
== run
->len
)
1948 next_run
= run_next_all_paras( run
);
1949 nNextStart
= -nMatched
;
1953 wNextChar
= *get_text( next_run
, nNextStart
+ nMatched
);
1957 if (iswalnum(wNextChar
))
1961 cursor
.nOffset
+= cursor
.para
->nCharOfs
+ cursor
.run
->nCharOfs
;
1964 chrgText
->cpMin
= cursor
.nOffset
;
1965 chrgText
->cpMax
= cursor
.nOffset
+ nLen
;
1967 TRACE("found at %d-%d\n", cursor
.nOffset
, cursor
.nOffset
+ nLen
);
1968 return cursor
.nOffset
;
1970 if (nCurStart
+ nMatched
== run
->len
)
1972 run
= run_next_all_paras( run
);
1973 nCurStart
= -nMatched
;
1977 wLastChar
= *get_text( run
, nCurStart
+ nMatched
);
1982 if (cursor
.nOffset
== cursor
.run
->len
)
1984 if (run_next_all_paras( cursor
.run
))
1986 cursor
.run
= run_next_all_paras( cursor
.run
);
1987 cursor
.para
= cursor
.run
->para
;
1995 else /* Backward search */
1997 /* If possible, find the character after where the search ends */
1998 if ((flags
& FR_WHOLEWORD
) && nMax
< nTextLen
- 1)
2000 cursor_from_char_ofs( editor
, nMax
+ 1, &cursor
);
2001 wLastChar
= *get_text( cursor
.run
, cursor
.nOffset
);
2002 ME_MoveCursorChars(editor
, &cursor
, -1, FALSE
);
2004 else cursor_from_char_ofs( editor
, nMax
, &cursor
);
2006 while (cursor
.run
&& ME_GetCursorOfs(&cursor
) - nLen
>= nMin
)
2008 ME_Run
*run
= cursor
.run
;
2009 ME_Paragraph
*para
= cursor
.para
;
2010 int nCurEnd
= cursor
.nOffset
;
2013 if (nCurEnd
== 0 && run_prev_all_paras( run
))
2015 run
= run_prev_all_paras( run
);
2020 while (run
&& ME_CharCompare( *get_text( run
, nCurEnd
- nMatched
- 1 ),
2021 text
[nLen
- nMatched
- 1], (flags
& FR_MATCHCASE
) ))
2023 if ((flags
& FR_WHOLEWORD
) && iswalnum(wLastChar
))
2027 if (nMatched
== nLen
)
2029 ME_Run
*prev_run
= run
;
2030 int nPrevEnd
= nCurEnd
;
2034 /* Check to see if previous character is a whitespace */
2035 if (flags
& FR_WHOLEWORD
)
2037 if (nPrevEnd
- nMatched
== 0)
2039 prev_run
= run_prev_all_paras( run
);
2040 if (prev_run
) nPrevEnd
= prev_run
->len
+ nMatched
;
2043 if (prev_run
) wPrevChar
= *get_text( prev_run
, nPrevEnd
- nMatched
- 1 );
2044 else wPrevChar
= ' ';
2046 if (iswalnum(wPrevChar
))
2050 nStart
= para
->nCharOfs
+ run
->nCharOfs
+ nCurEnd
- nMatched
;
2053 chrgText
->cpMin
= nStart
;
2054 chrgText
->cpMax
= nStart
+ nLen
;
2056 TRACE("found at %d-%d\n", nStart
, nStart
+ nLen
);
2059 if (nCurEnd
- nMatched
== 0)
2061 if (run_prev_all_paras( run
))
2063 run
= run_prev_all_paras( run
);
2066 /* Don't care about pCurItem becoming NULL here; it's already taken
2067 * care of in the exterior loop condition */
2068 nCurEnd
= run
->len
+ nMatched
;
2072 wLastChar
= *get_text( run
, nCurEnd
- nMatched
- 1 );
2077 if (cursor
.nOffset
< 0)
2079 if (run_prev_all_paras( cursor
.run
) )
2081 cursor
.run
= run_prev_all_paras( cursor
.run
);
2082 cursor
.para
= cursor
.run
->para
;
2083 cursor
.nOffset
= cursor
.run
->len
;
2090 TRACE("not found\n");
2092 chrgText
->cpMin
= chrgText
->cpMax
= -1;
2096 static int ME_GetTextEx(ME_TextEditor
*editor
, GETTEXTEX
*ex
, LPARAM pText
)
2101 if (!ex
->cb
|| !pText
) return 0;
2103 if (ex
->flags
& ~(GT_SELECTION
| GT_USECRLF
))
2104 FIXME("GETTEXTEX flags 0x%08x not supported\n", ex
->flags
& ~(GT_SELECTION
| GT_USECRLF
));
2106 if (ex
->flags
& GT_SELECTION
)
2109 int nStartCur
= ME_GetSelectionOfs(editor
, &from
, &to
);
2110 start
= editor
->pCursors
[nStartCur
];
2115 ME_SetCursorToStart(editor
, &start
);
2118 if (ex
->codepage
== CP_UNICODE
)
2120 return ME_GetTextW(editor
, (LPWSTR
)pText
, ex
->cb
/ sizeof(WCHAR
) - 1,
2121 &start
, nChars
, ex
->flags
& GT_USECRLF
, FALSE
);
2125 /* potentially each char may be a CR, why calculate the exact value with O(N) when
2126 we can just take a bigger buffer? :)
2127 The above assumption still holds with CR/LF counters, since CR->CRLF expansion
2128 occurs only in richedit 2.0 mode, in which line breaks have only one CR
2130 int crlfmul
= (ex
->flags
& GT_USECRLF
) ? 2 : 1;
2135 buflen
= min(crlfmul
* nChars
, ex
->cb
- 1);
2136 buffer
= heap_alloc((buflen
+ 1) * sizeof(WCHAR
));
2138 nChars
= ME_GetTextW(editor
, buffer
, buflen
, &start
, nChars
, ex
->flags
& GT_USECRLF
, FALSE
);
2139 rc
= WideCharToMultiByte(ex
->codepage
, 0, buffer
, nChars
+ 1,
2140 (LPSTR
)pText
, ex
->cb
, ex
->lpDefaultChar
, ex
->lpUsedDefChar
);
2141 if (rc
) rc
--; /* do not count 0 terminator */
2148 static int ME_GetTextRange(ME_TextEditor
*editor
, WCHAR
*strText
,
2149 const ME_Cursor
*start
, int nLen
, BOOL unicode
)
2151 if (!strText
) return 0;
2153 return ME_GetTextW(editor
, strText
, INT_MAX
, start
, nLen
, FALSE
, FALSE
);
2156 WCHAR
*p
= heap_alloc((nLen
+1) * sizeof(*p
));
2158 nChars
= ME_GetTextW(editor
, p
, nLen
, start
, nLen
, FALSE
, FALSE
);
2159 WideCharToMultiByte(CP_ACP
, 0, p
, nChars
+1, (char *)strText
,
2160 nLen
+1, NULL
, NULL
);
2166 int set_selection( ME_TextEditor
*editor
, int to
, int from
)
2170 TRACE("%d - %d\n", to
, from
);
2172 if (!editor
->bHideSelection
) ME_InvalidateSelection( editor
);
2173 end
= set_selection_cursors( editor
, to
, from
);
2174 if (!editor
->bHideSelection
) ME_InvalidateSelection( editor
);
2175 update_caret( editor
);
2176 ME_SendSelChange( editor
);
2181 typedef struct tagME_GlobalDestStruct
2185 } ME_GlobalDestStruct
;
2187 static DWORD CALLBACK
ME_ReadFromHGLOBALUnicode(DWORD_PTR dwCookie
, LPBYTE lpBuff
, LONG cb
, LONG
*pcb
)
2189 ME_GlobalDestStruct
*pData
= (ME_GlobalDestStruct
*)dwCookie
;
2194 pDest
= (WORD
*)lpBuff
;
2195 pSrc
= GlobalLock(pData
->hData
);
2196 for (i
= 0; i
<cb
&& pSrc
[pData
->nLength
+i
]; i
++) {
2197 pDest
[i
] = pSrc
[pData
->nLength
+i
];
2199 pData
->nLength
+= i
;
2201 GlobalUnlock(pData
->hData
);
2205 static DWORD CALLBACK
ME_ReadFromHGLOBALRTF(DWORD_PTR dwCookie
, LPBYTE lpBuff
, LONG cb
, LONG
*pcb
)
2207 ME_GlobalDestStruct
*pData
= (ME_GlobalDestStruct
*)dwCookie
;
2212 pSrc
= GlobalLock(pData
->hData
);
2213 for (i
= 0; i
<cb
&& pSrc
[pData
->nLength
+i
]; i
++) {
2214 pDest
[i
] = pSrc
[pData
->nLength
+i
];
2216 pData
->nLength
+= i
;
2218 GlobalUnlock(pData
->hData
);
2222 static HRESULT
paste_rtf(ME_TextEditor
*editor
, FORMATETC
*fmt
, STGMEDIUM
*med
)
2225 ME_GlobalDestStruct gds
;
2228 gds
.hData
= med
->u
.hGlobal
;
2230 es
.dwCookie
= (DWORD_PTR
)&gds
;
2231 es
.pfnCallback
= ME_ReadFromHGLOBALRTF
;
2232 hr
= ME_StreamIn( editor
, SF_RTF
| SFF_SELECTION
, &es
, FALSE
) == 0 ? E_FAIL
: S_OK
;
2233 ReleaseStgMedium( med
);
2237 static HRESULT
paste_text(ME_TextEditor
*editor
, FORMATETC
*fmt
, STGMEDIUM
*med
)
2240 ME_GlobalDestStruct gds
;
2243 gds
.hData
= med
->u
.hGlobal
;
2245 es
.dwCookie
= (DWORD_PTR
)&gds
;
2246 es
.pfnCallback
= ME_ReadFromHGLOBALUnicode
;
2247 hr
= ME_StreamIn( editor
, SF_TEXT
| SF_UNICODE
| SFF_SELECTION
, &es
, FALSE
) == 0 ? E_FAIL
: S_OK
;
2248 ReleaseStgMedium( med
);
2252 static HRESULT
paste_emf(ME_TextEditor
*editor
, FORMATETC
*fmt
, STGMEDIUM
*med
)
2257 hr
= insert_static_object( editor
, med
->u
.hEnhMetaFile
, NULL
, &sz
);
2260 ME_CommitUndo( editor
);
2261 ME_UpdateRepaint( editor
, FALSE
);
2264 ReleaseStgMedium( med
);
2269 static struct paste_format
2272 HRESULT (*paste
)(ME_TextEditor
*, FORMATETC
*, STGMEDIUM
*);
2276 {{ -1, NULL
, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
}, paste_rtf
, L
"Rich Text Format" },
2277 {{ CF_UNICODETEXT
, NULL
, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
}, paste_text
},
2278 {{ CF_ENHMETAFILE
, NULL
, DVASPECT_CONTENT
, -1, TYMED_ENHMF
}, paste_emf
},
2282 static void init_paste_formats(void)
2284 struct paste_format
*format
;
2289 for (format
= paste_formats
; format
->fmt
.cfFormat
; format
++)
2292 format
->fmt
.cfFormat
= RegisterClipboardFormatW( format
->name
);
2298 static BOOL
paste_special(ME_TextEditor
*editor
, UINT cf
, REPASTESPECIAL
*ps
, BOOL check_only
)
2302 struct paste_format
*format
;
2305 /* Protect read-only edit control from modification */
2306 if (editor
->styleFlags
& ES_READONLY
)
2309 MessageBeep(MB_ICONERROR
);
2313 init_paste_formats();
2315 if (ps
&& ps
->dwAspect
!= DVASPECT_CONTENT
)
2316 FIXME("Ignoring aspect %x\n", ps
->dwAspect
);
2318 hr
= OleGetClipboard( &data
);
2319 if (hr
!= S_OK
) return FALSE
;
2321 if (cf
== CF_TEXT
) cf
= CF_UNICODETEXT
;
2324 for (format
= paste_formats
; format
->fmt
.cfFormat
; format
++)
2326 if (cf
&& cf
!= format
->fmt
.cfFormat
) continue;
2327 hr
= IDataObject_QueryGetData( data
, &format
->fmt
);
2332 hr
= IDataObject_GetData( data
, &format
->fmt
, &med
);
2333 if (hr
!= S_OK
) goto done
;
2334 hr
= format
->paste( editor
, &format
->fmt
, &med
);
2341 IDataObject_Release( data
);
2346 static HRESULT
editor_copy( ME_TextEditor
*editor
, ME_Cursor
*start
, int chars
, IDataObject
**data_out
)
2348 IDataObject
*data
= NULL
;
2351 if (editor
->lpOleCallback
)
2354 range
.cpMin
= ME_GetCursorOfs( start
);
2355 range
.cpMax
= range
.cpMin
+ chars
;
2356 hr
= IRichEditOleCallback_GetClipboardData( editor
->lpOleCallback
, &range
, RECO_COPY
, &data
);
2359 if (FAILED( hr
) || !data
)
2360 hr
= ME_GetDataObject( editor
, start
, chars
, &data
);
2362 if (SUCCEEDED( hr
))
2368 hr
= OleSetClipboard( data
);
2369 IDataObject_Release( data
);
2376 HRESULT
editor_copy_or_cut( ME_TextEditor
*editor
, BOOL cut
, ME_Cursor
*start
, int count
,
2377 IDataObject
**data_out
)
2381 if (cut
&& (editor
->styleFlags
& ES_READONLY
))
2383 return E_ACCESSDENIED
;
2386 hr
= editor_copy( editor
, start
, count
, data_out
);
2387 if (SUCCEEDED(hr
) && cut
)
2389 ME_InternalDeleteText( editor
, start
, count
, FALSE
);
2390 ME_CommitUndo( editor
);
2391 ME_UpdateRepaint( editor
, TRUE
);
2396 static BOOL
copy_or_cut( ME_TextEditor
*editor
, BOOL cut
)
2400 int start_cursor
= ME_GetSelectionOfs( editor
, &offs
, &count
);
2401 ME_Cursor
*sel_start
= &editor
->pCursors
[start_cursor
];
2403 if (editor
->cPasswordMask
) return FALSE
;
2406 hr
= editor_copy_or_cut( editor
, cut
, sel_start
, count
, NULL
);
2407 if (FAILED( hr
)) MessageBeep( MB_ICONERROR
);
2409 return SUCCEEDED( hr
);
2412 /* helper to send a msg filter notification */
2414 ME_FilterEvent(ME_TextEditor
*editor
, UINT msg
, WPARAM
* wParam
, LPARAM
* lParam
)
2418 if (!editor
->hWnd
|| !editor
->hwndParent
) return FALSE
;
2419 msgf
.nmhdr
.hwndFrom
= editor
->hWnd
;
2420 msgf
.nmhdr
.idFrom
= GetWindowLongW(editor
->hWnd
, GWLP_ID
);
2421 msgf
.nmhdr
.code
= EN_MSGFILTER
;
2423 msgf
.wParam
= *wParam
;
2424 msgf
.lParam
= *lParam
;
2425 if (SendMessageW(editor
->hwndParent
, WM_NOTIFY
, msgf
.nmhdr
.idFrom
, (LPARAM
)&msgf
))
2427 *wParam
= msgf
.wParam
;
2428 *lParam
= msgf
.lParam
;
2429 msgf
.wParam
= *wParam
;
2434 static void ME_UpdateSelectionLinkAttribute(ME_TextEditor
*editor
)
2436 ME_Paragraph
*start_para
, *end_para
;
2437 ME_Cursor
*from
, *to
, start
;
2440 if (!editor
->AutoURLDetect_bEnable
) return;
2442 ME_GetSelection(editor
, &from
, &to
);
2444 /* Find paragraph previous to the one that contains start cursor */
2445 start_para
= from
->para
;
2446 if (para_prev( start_para
)) start_para
= para_prev( start_para
);
2448 /* Find paragraph that contains end cursor */
2449 end_para
= para_next( to
->para
);
2451 start
.para
= start_para
;
2452 start
.run
= para_first_run( start_para
);
2454 num_chars
= end_para
->nCharOfs
- start_para
->nCharOfs
;
2456 ME_UpdateLinkAttribute( editor
, &start
, num_chars
);
2459 static BOOL
handle_enter(ME_TextEditor
*editor
)
2461 BOOL ctrl_is_down
= GetKeyState(VK_CONTROL
) & 0x8000;
2462 BOOL shift_is_down
= GetKeyState(VK_SHIFT
) & 0x8000;
2464 if (editor
->bDialogMode
)
2469 if (!(editor
->styleFlags
& ES_WANTRETURN
))
2471 if (editor
->hwndParent
)
2474 dw
= SendMessageW(editor
->hwndParent
, DM_GETDEFID
, 0, 0);
2475 if (HIWORD(dw
) == DC_HASDEFID
)
2477 HWND hwDefCtrl
= GetDlgItem(editor
->hwndParent
, LOWORD(dw
));
2480 SendMessageW(editor
->hwndParent
, WM_NEXTDLGCTL
, (WPARAM
)hwDefCtrl
, TRUE
);
2481 PostMessageW(hwDefCtrl
, WM_KEYDOWN
, VK_RETURN
, 0);
2489 if (editor
->styleFlags
& ES_MULTILINE
)
2491 static const WCHAR endl
= '\r';
2492 static const WCHAR endlv10
[] = {'\r','\n'};
2493 ME_Cursor cursor
= editor
->pCursors
[0];
2494 ME_Paragraph
*para
= cursor
.para
;
2496 ME_Style
*style
, *eop_style
;
2498 if (editor
->styleFlags
& ES_READONLY
)
2500 MessageBeep(MB_ICONERROR
);
2504 ME_GetSelectionOfs(editor
, &from
, &to
);
2505 if (editor
->nTextLimit
> ME_GetTextLength(editor
) - (to
-from
))
2507 if (!editor
->bEmulateVersion10
) /* v4.1 */
2509 if (para
->nFlags
& MEPF_ROWEND
)
2511 /* Add a new table row after this row. */
2512 para
= table_append_row( editor
, para
);
2513 para
= para_next( para
);
2514 editor
->pCursors
[0].para
= para
;
2515 editor
->pCursors
[0].run
= para_first_run( para
);
2516 editor
->pCursors
[0].nOffset
= 0;
2517 editor
->pCursors
[1] = editor
->pCursors
[0];
2518 ME_CommitUndo(editor
);
2519 ME_UpdateRepaint(editor
, FALSE
);
2522 else if (para
== editor
->pCursors
[1].para
&&
2523 cursor
.nOffset
+ cursor
.run
->nCharOfs
== 0 &&
2524 para_prev( para
) && para_prev( para
)->nFlags
& MEPF_ROWSTART
&&
2525 !para_prev( para
)->nCharOfs
)
2527 /* Insert a newline before the table. */
2528 para
= para_prev( para
);
2529 para
->nFlags
&= ~MEPF_ROWSTART
;
2530 editor
->pCursors
[0].para
= para
;
2531 editor
->pCursors
[0].run
= para_first_run( para
);
2532 editor
->pCursors
[1] = editor
->pCursors
[0];
2533 ME_InsertTextFromCursor( editor
, 0, &endl
, 1, editor
->pCursors
[0].run
->style
);
2534 para
= editor_first_para( editor
);
2535 editor_set_default_para_fmt( editor
, ¶
->fmt
);
2537 para_mark_rewrap( editor
, para
);
2538 editor
->pCursors
[0].para
= para
;
2539 editor
->pCursors
[0].run
= para_first_run( para
);
2540 editor
->pCursors
[1] = editor
->pCursors
[0];
2541 para_next( para
)->nFlags
|= MEPF_ROWSTART
;
2542 ME_CommitCoalescingUndo(editor
);
2543 ME_UpdateRepaint(editor
, FALSE
);
2547 else /* v1.0 - 3.0 */
2549 ME_Paragraph
*para
= cursor
.para
;
2550 if (para_in_table( para
))
2552 if (cursor
.run
->nFlags
& MERF_ENDPARA
)
2556 ME_ContinueCoalescingTransaction(editor
);
2557 para
= table_append_row( editor
, para
);
2558 editor
->pCursors
[0].para
= para
;
2559 editor
->pCursors
[0].run
= para_first_run( para
);
2560 editor
->pCursors
[0].nOffset
= 0;
2561 editor
->pCursors
[1] = editor
->pCursors
[0];
2562 ME_CommitCoalescingUndo(editor
);
2563 ME_UpdateRepaint(editor
, FALSE
);
2569 ME_ContinueCoalescingTransaction(editor
);
2570 if (cursor
.run
->nCharOfs
+ cursor
.nOffset
== 0 &&
2571 para_prev( para
) && !para_in_table( para_prev( para
) ))
2573 /* Insert newline before table */
2574 cursor
.run
= para_end_run( para_prev( para
) );
2577 editor
->pCursors
[0].run
= cursor
.run
;
2578 editor
->pCursors
[0].para
= para_prev( para
);
2580 editor
->pCursors
[0].nOffset
= 0;
2581 editor
->pCursors
[1] = editor
->pCursors
[0];
2582 ME_InsertTextFromCursor( editor
, 0, &endl
, 1, editor
->pCursors
[0].run
->style
);
2586 editor
->pCursors
[1] = editor
->pCursors
[0];
2587 para
= table_append_row( editor
, para
);
2588 editor
->pCursors
[0].para
= para
;
2589 editor
->pCursors
[0].run
= para_first_run( para
);
2590 editor
->pCursors
[0].nOffset
= 0;
2591 editor
->pCursors
[1] = editor
->pCursors
[0];
2593 ME_CommitCoalescingUndo(editor
);
2594 ME_UpdateRepaint(editor
, FALSE
);
2600 style
= style_get_insert_style( editor
, editor
->pCursors
);
2602 /* Normally the new eop style is the insert style, however in a list it is copied from the existing
2603 eop style (this prevents the list label style changing when the new eop is inserted).
2604 No extra ref is taken here on eop_style. */
2605 if (para
->fmt
.wNumbering
)
2606 eop_style
= para
->eop_run
->style
;
2609 ME_ContinueCoalescingTransaction(editor
);
2611 ME_InsertEndRowFromCursor(editor
, 0);
2613 if (!editor
->bEmulateVersion10
)
2614 ME_InsertTextFromCursor(editor
, 0, &endl
, 1, eop_style
);
2616 ME_InsertTextFromCursor(editor
, 0, endlv10
, 2, eop_style
);
2617 ME_CommitCoalescingUndo(editor
);
2620 ME_UpdateSelectionLinkAttribute(editor
);
2621 ME_UpdateRepaint(editor
, FALSE
);
2622 ME_SaveTempStyle(editor
, style
); /* set the temp insert style for the new para */
2623 ME_ReleaseStyle(style
);
2631 ME_KeyDown(ME_TextEditor
*editor
, WORD nKey
)
2633 BOOL ctrl_is_down
= GetKeyState(VK_CONTROL
) & 0x8000;
2634 BOOL shift_is_down
= GetKeyState(VK_SHIFT
) & 0x8000;
2636 if (editor
->bMouseCaptured
)
2638 if (nKey
!= VK_SHIFT
&& nKey
!= VK_CONTROL
&& nKey
!= VK_MENU
)
2639 editor
->nSelectionType
= stPosition
;
2647 editor
->nUDArrowX
= -1;
2653 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
2654 ME_ArrowKey(editor
, nKey
, shift_is_down
, ctrl_is_down
);
2658 editor
->nUDArrowX
= -1;
2659 /* FIXME backspace and delete aren't the same, they act different wrt paragraph style of the merged paragraph */
2660 if (editor
->styleFlags
& ES_READONLY
)
2662 if (ME_IsSelection(editor
))
2664 ME_DeleteSelection(editor
);
2665 ME_CommitUndo(editor
);
2667 else if (nKey
== VK_DELETE
)
2669 /* Delete stops group typing.
2670 * (See MSDN remarks on EM_STOPGROUPTYPING message) */
2671 ME_DeleteTextAtCursor(editor
, 1, 1);
2672 ME_CommitUndo(editor
);
2674 else if (ME_ArrowKey(editor
, VK_LEFT
, FALSE
, FALSE
))
2676 BOOL bDeletionSucceeded
;
2677 /* Backspace can be grouped for a single undo */
2678 ME_ContinueCoalescingTransaction(editor
);
2679 bDeletionSucceeded
= ME_DeleteTextAtCursor(editor
, 1, 1);
2680 if (!bDeletionSucceeded
&& !editor
->bEmulateVersion10
) { /* v4.1 */
2681 /* Deletion was prevented so the cursor is moved back to where it was.
2682 * (e.g. this happens when trying to delete cell boundaries)
2684 ME_ArrowKey(editor
, VK_RIGHT
, FALSE
, FALSE
);
2686 ME_CommitCoalescingUndo(editor
);
2690 table_move_from_row_start( editor
);
2691 ME_UpdateSelectionLinkAttribute(editor
);
2692 ME_UpdateRepaint(editor
, FALSE
);
2693 ME_SendRequestResize(editor
, FALSE
);
2696 if (!editor
->bEmulateVersion10
)
2697 return handle_enter(editor
);
2700 if (editor
->bDialogMode
&& editor
->hwndParent
)
2701 PostMessageW(editor
->hwndParent
, WM_CLOSE
, 0, 0);
2704 if (editor
->bDialogMode
&& editor
->hwndParent
)
2705 SendMessageW(editor
->hwndParent
, WM_NEXTDLGCTL
, shift_is_down
, 0);
2710 set_selection( editor
, 0, -1 );
2716 return paste_special( editor
, 0, NULL
, FALSE
);
2721 return copy_or_cut(editor
, nKey
== 'X');
2739 if (nKey
!= VK_SHIFT
&& nKey
!= VK_CONTROL
&& nKey
&& nKey
!= VK_MENU
)
2740 editor
->nUDArrowX
= -1;
2747 chf
.cbSize
= sizeof(chf
);
2749 ME_GetSelectionCharFormat(editor
, &chf
);
2750 ME_DumpStyleToBuf(&chf
, buf
);
2751 MessageBoxA(NULL
, buf
, "Style dump", MB_OK
);
2755 ME_CheckCharOffsets(editor
);
2762 static LRESULT
ME_Char(ME_TextEditor
*editor
, WPARAM charCode
,
2763 LPARAM flags
, BOOL unicode
)
2767 if (editor
->bMouseCaptured
)
2770 if (editor
->styleFlags
& ES_READONLY
)
2772 MessageBeep(MB_ICONERROR
);
2773 return 0; /* FIXME really 0 ? */
2777 wstr
= (WCHAR
)charCode
;
2780 CHAR charA
= charCode
;
2781 MultiByteToWideChar(CP_ACP
, 0, &charA
, 1, &wstr
, 1);
2784 if (editor
->bEmulateVersion10
&& wstr
== '\r')
2785 handle_enter(editor
);
2787 if ((unsigned)wstr
>= ' ' || wstr
== '\t')
2789 ME_Cursor cursor
= editor
->pCursors
[0];
2790 ME_Paragraph
*para
= cursor
.para
;
2792 BOOL ctrl_is_down
= GetKeyState(VK_CONTROL
) & 0x8000;
2793 ME_GetSelectionOfs(editor
, &from
, &to
);
2795 /* v4.1 allows tabs to be inserted with ctrl key down */
2796 !(ctrl_is_down
&& !editor
->bEmulateVersion10
))
2798 BOOL selected_row
= FALSE
;
2800 if (ME_IsSelection(editor
) &&
2801 cursor
.run
->nCharOfs
+ cursor
.nOffset
== 0 &&
2802 to
== ME_GetCursorOfs(&editor
->pCursors
[0]) && para_prev( para
))
2804 para
= para_prev( para
);
2805 selected_row
= TRUE
;
2807 if (para_in_table( para
))
2809 table_handle_tab( editor
, selected_row
);
2810 ME_CommitUndo(editor
);
2814 else if (!editor
->bEmulateVersion10
) /* v4.1 */
2816 if (para
->nFlags
& MEPF_ROWEND
)
2820 para
= para_next( para
);
2821 if (para
->nFlags
& MEPF_ROWSTART
) para
= para_next( para
);
2822 editor
->pCursors
[0].para
= para
;
2823 editor
->pCursors
[0].run
= para_first_run( para
);
2824 editor
->pCursors
[0].nOffset
= 0;
2825 editor
->pCursors
[1] = editor
->pCursors
[0];
2829 else /* v1.0 - 3.0 */
2831 if (para_in_table( para
) && cursor
.run
->nFlags
& MERF_ENDPARA
&& from
== to
)
2833 /* Text should not be inserted at the end of the table. */
2838 /* FIXME maybe it would make sense to call EM_REPLACESEL instead ? */
2839 /* WM_CHAR is restricted to nTextLimit */
2840 if(editor
->nTextLimit
> ME_GetTextLength(editor
) - (to
-from
))
2842 ME_Style
*style
= style_get_insert_style( editor
, editor
->pCursors
);
2843 ME_ContinueCoalescingTransaction(editor
);
2844 ME_InsertTextFromCursor(editor
, 0, &wstr
, 1, style
);
2845 ME_ReleaseStyle(style
);
2846 ME_CommitCoalescingUndo(editor
);
2847 ITextHost_TxSetCursor(editor
->texthost
, NULL
, FALSE
);
2850 ME_UpdateSelectionLinkAttribute(editor
);
2851 ME_UpdateRepaint(editor
, FALSE
);
2856 /* Process the message and calculate the new click count.
2858 * returns: The click count if it is mouse down event, else returns 0. */
2859 static int ME_CalculateClickCount(ME_TextEditor
*editor
, UINT msg
, WPARAM wParam
,
2862 static int clickNum
= 0;
2863 if (msg
< WM_MOUSEFIRST
|| msg
> WM_MOUSELAST
)
2866 if ((msg
== WM_LBUTTONDBLCLK
) ||
2867 (msg
== WM_RBUTTONDBLCLK
) ||
2868 (msg
== WM_MBUTTONDBLCLK
) ||
2869 (msg
== WM_XBUTTONDBLCLK
))
2871 msg
-= (WM_LBUTTONDBLCLK
- WM_LBUTTONDOWN
);
2874 if ((msg
== WM_LBUTTONDOWN
) ||
2875 (msg
== WM_RBUTTONDOWN
) ||
2876 (msg
== WM_MBUTTONDOWN
) ||
2877 (msg
== WM_XBUTTONDOWN
))
2879 static MSG prevClickMsg
;
2881 /* Compare the editor instead of the hwnd so that the this
2882 * can still be done for windowless richedit controls. */
2883 clickMsg
.hwnd
= (HWND
)editor
;
2884 clickMsg
.message
= msg
;
2885 clickMsg
.wParam
= wParam
;
2886 clickMsg
.lParam
= lParam
;
2887 clickMsg
.time
= GetMessageTime();
2888 clickMsg
.pt
.x
= (short)LOWORD(lParam
);
2889 clickMsg
.pt
.y
= (short)HIWORD(lParam
);
2890 if ((clickNum
!= 0) &&
2891 (clickMsg
.message
== prevClickMsg
.message
) &&
2892 (clickMsg
.hwnd
== prevClickMsg
.hwnd
) &&
2893 (clickMsg
.wParam
== prevClickMsg
.wParam
) &&
2894 (clickMsg
.time
- prevClickMsg
.time
< GetDoubleClickTime()) &&
2895 (abs(clickMsg
.pt
.x
- prevClickMsg
.pt
.x
) < GetSystemMetrics(SM_CXDOUBLECLK
)/2) &&
2896 (abs(clickMsg
.pt
.y
- prevClickMsg
.pt
.y
) < GetSystemMetrics(SM_CYDOUBLECLK
)/2))
2902 prevClickMsg
= clickMsg
;
2909 static BOOL
is_link( ME_Run
*run
)
2911 return (run
->style
->fmt
.dwMask
& CFM_LINK
) && (run
->style
->fmt
.dwEffects
& CFE_LINK
);
2914 static BOOL
ME_SetCursor(ME_TextEditor
*editor
)
2920 DWORD messagePos
= GetMessagePos();
2921 pt
.x
= (short)LOWORD(messagePos
);
2922 pt
.y
= (short)HIWORD(messagePos
);
2926 sbi
.cbSize
= sizeof(sbi
);
2927 GetScrollBarInfo(editor
->hWnd
, OBJID_HSCROLL
, &sbi
);
2928 if (!(sbi
.rgstate
[0] & (STATE_SYSTEM_INVISIBLE
|STATE_SYSTEM_OFFSCREEN
)) &&
2929 PtInRect(&sbi
.rcScrollBar
, pt
))
2931 ITextHost_TxSetCursor(editor
->texthost
,
2932 LoadCursorW(NULL
, (WCHAR
*)IDC_ARROW
), FALSE
);
2935 sbi
.cbSize
= sizeof(sbi
);
2936 GetScrollBarInfo(editor
->hWnd
, OBJID_VSCROLL
, &sbi
);
2937 if (!(sbi
.rgstate
[0] & (STATE_SYSTEM_INVISIBLE
|STATE_SYSTEM_OFFSCREEN
)) &&
2938 PtInRect(&sbi
.rcScrollBar
, pt
))
2940 ITextHost_TxSetCursor(editor
->texthost
,
2941 LoadCursorW(NULL
, (WCHAR
*)IDC_ARROW
), FALSE
);
2945 ITextHost_TxScreenToClient(editor
->texthost
, &pt
);
2947 if (editor
->nSelectionType
== stLine
&& editor
->bMouseCaptured
) {
2948 ITextHost_TxSetCursor(editor
->texthost
, hLeft
, FALSE
);
2951 if (!editor
->bEmulateVersion10
/* v4.1 */ &&
2952 pt
.y
< editor
->rcFormat
.top
&&
2953 pt
.x
< editor
->rcFormat
.left
)
2955 ITextHost_TxSetCursor(editor
->texthost
, hLeft
, FALSE
);
2958 if (pt
.y
< editor
->rcFormat
.top
|| pt
.y
> editor
->rcFormat
.bottom
)
2960 if (editor
->bEmulateVersion10
) /* v1.0 - 3.0 */
2961 ITextHost_TxSetCursor(editor
->texthost
,
2962 LoadCursorW(NULL
, (WCHAR
*)IDC_ARROW
), FALSE
);
2964 ITextHost_TxSetCursor(editor
->texthost
,
2965 LoadCursorW(NULL
, (WCHAR
*)IDC_IBEAM
), TRUE
);
2968 if (pt
.x
< editor
->rcFormat
.left
)
2970 ITextHost_TxSetCursor(editor
->texthost
, hLeft
, FALSE
);
2973 ME_CharFromPos(editor
, pt
.x
, pt
.y
, &cursor
, &isExact
);
2976 ME_Run
*run
= cursor
.run
;
2980 ITextHost_TxSetCursor(editor
->texthost
,
2981 LoadCursorW(NULL
, (WCHAR
*)IDC_HAND
),
2986 if (ME_IsSelection(editor
))
2988 int selStart
, selEnd
;
2989 int offset
= ME_GetCursorOfs(&cursor
);
2991 ME_GetSelectionOfs(editor
, &selStart
, &selEnd
);
2992 if (selStart
<= offset
&& selEnd
>= offset
) {
2993 ITextHost_TxSetCursor(editor
->texthost
,
2994 LoadCursorW(NULL
, (WCHAR
*)IDC_ARROW
),
3000 ITextHost_TxSetCursor(editor
->texthost
,
3001 LoadCursorW(NULL
, (WCHAR
*)IDC_IBEAM
), TRUE
);
3005 static void ME_SetDefaultFormatRect(ME_TextEditor
*editor
)
3007 ITextHost_TxGetClientRect(editor
->texthost
, &editor
->rcFormat
);
3008 editor
->rcFormat
.top
+= editor
->exStyleFlags
& WS_EX_CLIENTEDGE
? 1 : 0;
3009 editor
->rcFormat
.left
+= 1 + editor
->selofs
;
3010 editor
->rcFormat
.right
-= 1;
3013 static LONG
ME_GetSelectionType(ME_TextEditor
*editor
)
3015 LONG sel_type
= SEL_EMPTY
;
3018 ME_GetSelectionOfs(editor
, &start
, &end
);
3020 sel_type
= SEL_EMPTY
;
3023 LONG object_count
= 0, character_count
= 0;
3026 for (i
= 0; i
< end
- start
; i
++)
3030 cursor_from_char_ofs( editor
, start
+ i
, &cursor
);
3031 if (cursor
.run
->reobj
) object_count
++;
3032 else character_count
++;
3033 if (character_count
>= 2 && object_count
>= 2)
3034 return (SEL_TEXT
| SEL_MULTICHAR
| SEL_OBJECT
| SEL_MULTIOBJECT
);
3036 if (character_count
)
3038 sel_type
|= SEL_TEXT
;
3039 if (character_count
>= 2)
3040 sel_type
|= SEL_MULTICHAR
;
3044 sel_type
|= SEL_OBJECT
;
3045 if (object_count
>= 2)
3046 sel_type
|= SEL_MULTIOBJECT
;
3052 static BOOL
ME_ShowContextMenu(ME_TextEditor
*editor
, int x
, int y
)
3058 if(!editor
->lpOleCallback
|| !editor
->hWnd
)
3060 ME_GetSelectionOfs(editor
, &selrange
.cpMin
, &selrange
.cpMax
);
3061 seltype
= ME_GetSelectionType(editor
);
3062 if(SUCCEEDED(IRichEditOleCallback_GetContextMenu(editor
->lpOleCallback
, seltype
, NULL
, &selrange
, &menu
)))
3064 TrackPopupMenu(menu
, TPM_LEFTALIGN
| TPM_RIGHTBUTTON
, x
, y
, 0, editor
->hwndParent
, NULL
);
3070 ME_TextEditor
*ME_MakeEditor(ITextHost
*texthost
, BOOL bEmulateVersion10
)
3072 ME_TextEditor
*ed
= heap_alloc(sizeof(*ed
));
3078 ed
->hwndParent
= NULL
;
3079 ed
->sizeWindow
.cx
= ed
->sizeWindow
.cy
= 0;
3080 ed
->texthost
= texthost
;
3082 ed
->bEmulateVersion10
= bEmulateVersion10
;
3084 ed
->exStyleFlags
= 0;
3086 ITextHost_TxGetPropertyBits(texthost
,
3087 (TXTBIT_RICHTEXT
|TXTBIT_MULTILINE
|
3088 TXTBIT_READONLY
|TXTBIT_USEPASSWORD
|
3089 TXTBIT_HIDESELECTION
|TXTBIT_SAVESELECTION
|
3090 TXTBIT_AUTOWORDSEL
|TXTBIT_VERTICAL
|
3091 TXTBIT_WORDWRAP
|TXTBIT_DISABLEDRAG
),
3093 ITextHost_TxGetScrollBars(texthost
, &ed
->styleFlags
);
3094 ed
->styleFlags
&= (WS_VSCROLL
|WS_HSCROLL
|ES_AUTOVSCROLL
|
3095 ES_AUTOHSCROLL
|ES_DISABLENOSCROLL
);
3096 ed
->pBuffer
= ME_MakeText();
3097 ed
->nZoomNumerator
= ed
->nZoomDenominator
= 0;
3098 ed
->nAvailWidth
= 0; /* wrap to client area */
3099 list_init( &ed
->style_list
);
3100 ME_MakeFirstParagraph(ed
);
3101 /* The four cursors are for:
3102 * 0 - The position where the caret is shown
3103 * 1 - The anchored end of the selection (for normal selection)
3104 * 2 & 3 - The anchored start and end respectively for word, line,
3105 * or paragraph selection.
3108 ed
->pCursors
= heap_alloc(ed
->nCursors
* sizeof(*ed
->pCursors
));
3109 ME_SetCursorToStart(ed
, &ed
->pCursors
[0]);
3110 ed
->pCursors
[1] = ed
->pCursors
[0];
3111 ed
->pCursors
[2] = ed
->pCursors
[0];
3112 ed
->pCursors
[3] = ed
->pCursors
[1];
3113 ed
->nLastTotalLength
= ed
->nTotalLength
= 0;
3114 ed
->nLastTotalWidth
= ed
->nTotalWidth
= 0;
3116 ed
->rgbBackColor
= -1;
3117 ed
->hbrBackground
= GetSysColorBrush(COLOR_WINDOW
);
3119 ed
->nModifyStep
= 0;
3120 ed
->nTextLimit
= TEXT_LIMIT_DEFAULT
;
3121 list_init( &ed
->undo_stack
);
3122 list_init( &ed
->redo_stack
);
3123 ed
->nUndoStackSize
= 0;
3124 ed
->nUndoLimit
= STACK_SIZE_DEFAULT
;
3125 ed
->nUndoMode
= umAddToUndo
;
3126 ed
->nParagraphs
= 1;
3127 ed
->nLastSelStart
= ed
->nLastSelEnd
= 0;
3128 ed
->last_sel_start_para
= ed
->last_sel_end_para
= ed
->pCursors
[0].para
;
3129 ed
->bHideSelection
= FALSE
;
3130 ed
->pfnWordBreak
= NULL
;
3131 ed
->lpOleCallback
= NULL
;
3132 ed
->mode
= TM_MULTILEVELUNDO
| TM_MULTICODEPAGE
;
3133 ed
->mode
|= (props
& TXTBIT_RICHTEXT
) ? TM_RICHTEXT
: TM_PLAINTEXT
;
3134 ed
->AutoURLDetect_bEnable
= FALSE
;
3135 ed
->bHaveFocus
= FALSE
;
3136 ed
->bDialogMode
= FALSE
;
3137 ed
->bMouseCaptured
= FALSE
;
3138 ed
->caret_hidden
= FALSE
;
3139 ed
->caret_height
= 0;
3140 for (i
=0; i
<HFONT_CACHE_SIZE
; i
++)
3142 ed
->pFontCache
[i
].nRefs
= 0;
3143 ed
->pFontCache
[i
].nAge
= 0;
3144 ed
->pFontCache
[i
].hFont
= NULL
;
3147 ME_CheckCharOffsets(ed
);
3148 SetRectEmpty(&ed
->rcFormat
);
3149 ed
->bDefaultFormatRect
= TRUE
;
3150 ITextHost_TxGetSelectionBarWidth(ed
->texthost
, &selbarwidth
);
3152 /* FIXME: Convert selbarwidth from HIMETRIC to pixels */
3153 ed
->selofs
= SELECTIONBAR_WIDTH
;
3154 ed
->styleFlags
|= ES_SELECTIONBAR
;
3158 ed
->nSelectionType
= stPosition
;
3160 ed
->cPasswordMask
= 0;
3161 if (props
& TXTBIT_USEPASSWORD
)
3162 ITextHost_TxGetPasswordChar(texthost
, &ed
->cPasswordMask
);
3164 if (props
& TXTBIT_AUTOWORDSEL
)
3165 ed
->styleFlags
|= ECO_AUTOWORDSELECTION
;
3166 if (props
& TXTBIT_MULTILINE
) {
3167 ed
->styleFlags
|= ES_MULTILINE
;
3168 ed
->bWordWrap
= (props
& TXTBIT_WORDWRAP
) != 0;
3170 ed
->bWordWrap
= FALSE
;
3172 if (props
& TXTBIT_READONLY
)
3173 ed
->styleFlags
|= ES_READONLY
;
3174 if (!(props
& TXTBIT_HIDESELECTION
))
3175 ed
->styleFlags
|= ES_NOHIDESEL
;
3176 if (props
& TXTBIT_SAVESELECTION
)
3177 ed
->styleFlags
|= ES_SAVESEL
;
3178 if (props
& TXTBIT_VERTICAL
)
3179 ed
->styleFlags
|= ES_VERTICAL
;
3180 if (props
& TXTBIT_DISABLEDRAG
)
3181 ed
->styleFlags
|= ES_NOOLEDRAGDROP
;
3183 ed
->notified_cr
.cpMin
= ed
->notified_cr
.cpMax
= 0;
3185 /* Default scrollbar information */
3186 ed
->vert_si
.cbSize
= sizeof(SCROLLINFO
);
3187 ed
->vert_si
.nMin
= 0;
3188 ed
->vert_si
.nMax
= 0;
3189 ed
->vert_si
.nPage
= 0;
3190 ed
->vert_si
.nPos
= 0;
3192 ed
->horz_si
.cbSize
= sizeof(SCROLLINFO
);
3193 ed
->horz_si
.nMin
= 0;
3194 ed
->horz_si
.nMax
= 0;
3195 ed
->horz_si
.nPage
= 0;
3196 ed
->horz_si
.nPos
= 0;
3198 ed
->wheel_remain
= 0;
3200 list_init( &ed
->reobj_list
);
3201 OleInitialize(NULL
);
3206 void ME_DestroyEditor(ME_TextEditor
*editor
)
3208 ME_DisplayItem
*p
= editor
->pBuffer
->pFirst
, *pNext
= NULL
;
3209 ME_Style
*s
, *cursor2
;
3212 ME_ClearTempStyle(editor
);
3213 ME_EmptyUndoStack(editor
);
3214 editor
->pBuffer
->pFirst
= NULL
;
3218 if (p
->type
== diParagraph
)
3219 para_destroy( editor
, &p
->member
.para
);
3221 ME_DestroyDisplayItem(p
);
3225 LIST_FOR_EACH_ENTRY_SAFE( s
, cursor2
, &editor
->style_list
, ME_Style
, entry
)
3226 ME_DestroyStyle( s
);
3228 ME_ReleaseStyle(editor
->pBuffer
->pDefaultStyle
);
3229 for (i
=0; i
<HFONT_CACHE_SIZE
; i
++)
3231 if (editor
->pFontCache
[i
].hFont
)
3232 DeleteObject(editor
->pFontCache
[i
].hFont
);
3234 if (editor
->rgbBackColor
!= -1)
3235 DeleteObject(editor
->hbrBackground
);
3236 if(editor
->lpOleCallback
)
3237 IRichEditOleCallback_Release(editor
->lpOleCallback
);
3238 ITextHost_Release(editor
->texthost
);
3241 IUnknown_Release(editor
->reOle
);
3242 editor
->reOle
= NULL
;
3246 heap_free(editor
->pBuffer
);
3247 heap_free(editor
->pCursors
);
3251 BOOL WINAPI
DllMain(HINSTANCE hinstDLL
, DWORD fdwReason
, LPVOID lpvReserved
)
3256 case DLL_PROCESS_ATTACH
:
3257 DisableThreadLibraryCalls(hinstDLL
);
3258 me_heap
= HeapCreate (0, 0x10000, 0);
3259 if (!ME_RegisterEditorClass(hinstDLL
)) return FALSE
;
3260 hLeft
= LoadCursorW(hinstDLL
, MAKEINTRESOURCEW(OCR_REVERSE
));
3264 case DLL_PROCESS_DETACH
:
3265 if (lpvReserved
) break;
3266 UnregisterClassW(RICHEDIT_CLASS20W
, 0);
3267 UnregisterClassW(MSFTEDIT_CLASS
, 0);
3268 UnregisterClassA(RICHEDIT_CLASS20A
, 0);
3269 UnregisterClassA("RichEdit50A", 0);
3270 if (ME_ListBoxRegistered
)
3271 UnregisterClassW(L
"REListBox20W", 0);
3272 if (ME_ComboBoxRegistered
)
3273 UnregisterClassW(L
"REComboBox20W", 0);
3275 HeapDestroy (me_heap
);
3282 static inline int get_default_line_height( ME_TextEditor
*editor
)
3286 if (editor
->pBuffer
&& editor
->pBuffer
->pDefaultStyle
)
3287 height
= editor
->pBuffer
->pDefaultStyle
->tm
.tmHeight
;
3288 if (height
<= 0) height
= 24;
3293 static inline int calc_wheel_change( int *remain
, int amount_per_click
)
3295 int change
= amount_per_click
* (float)*remain
/ WHEEL_DELTA
;
3296 *remain
-= WHEEL_DELTA
* change
/ amount_per_click
;
3300 static const char * const edit_messages
[] = {
3329 "EM_SETPASSWORDCHAR",
3330 "EM_EMPTYUNDOBUFFER",
3331 "EM_GETFIRSTVISIBLELINE",
3333 "EM_SETWORDBREAKPROC",
3334 "EM_GETWORDBREAKPROC",
3335 "EM_GETPASSWORDCHAR",
3345 static const char * const richedit_messages
[] = {
3350 "EM_EXLINEFROMCHAR",
3356 "EM_GETOLEINTERFACE",
3366 "EM_SETOLECALLBACK",
3368 "EM_SETTARGETDEVICE",
3376 "EM_GETWORDBREAKPROCEX",
3377 "EM_SETWORDBREAKPROCEX",
3379 "EM_UNKNOWN_USER_83",
3384 "EM_STOPGROUPTYPING",
3388 "EM_GETAUTOURLDETECT",
3391 "EM_GETTEXTLENGTHEX",
3394 "EM_UNKNOWN_USER_98",
3395 "EM_UNKNOWN_USER_99",
3396 "EM_SETPUNCTUATION",
3397 "EM_GETPUNCTUATION",
3398 "EM_SETWORDWRAPMODE",
3399 "EM_GETWORDWRAPMODE",
3405 "EM_UNKNOWN_USER_109",
3406 "EM_UNKNOWN_USER_110",
3407 "EM_UNKNOWN_USER_111",
3408 "EM_UNKNOWN_USER_112",
3409 "EM_UNKNOWN_USER_113",
3410 "EM_UNKNOWN_USER_114",
3411 "EM_UNKNOWN_USER_115",
3412 "EM_UNKNOWN_USER_116",
3413 "EM_UNKNOWN_USER_117",
3414 "EM_UNKNOWN_USER_118",
3415 "EM_UNKNOWN_USER_119",
3416 "EM_SETLANGOPTIONS",
3417 "EM_GETLANGOPTIONS",
3418 "EM_GETIMECOMPMODE",
3422 "EM_SETIMEMODEBIAS",
3427 get_msg_name(UINT msg
)
3429 if (msg
>= EM_GETSEL
&& msg
<= EM_CHARFROMPOS
)
3430 return edit_messages
[msg
- EM_GETSEL
];
3431 if (msg
>= EM_CANPASTE
&& msg
<= EM_GETIMEMODEBIAS
)
3432 return richedit_messages
[msg
- EM_CANPASTE
];
3436 static void ME_LinkNotify(ME_TextEditor
*editor
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
3440 ME_Cursor cursor
; /* The start of the clicked text. */
3444 x
= (short)LOWORD(lParam
);
3445 y
= (short)HIWORD(lParam
);
3446 ME_CharFromPos(editor
, x
, y
, &cursor
, &isExact
);
3447 if (!isExact
) return;
3449 if (is_link( cursor
.run
))
3450 { /* The clicked run has CFE_LINK set */
3451 info
.nmhdr
.hwndFrom
= NULL
;
3452 info
.nmhdr
.idFrom
= 0;
3453 info
.nmhdr
.code
= EN_LINK
;
3455 info
.wParam
= wParam
;
3456 info
.lParam
= lParam
;
3459 /* find the first contiguous run with CFE_LINK set */
3460 info
.chrg
.cpMin
= ME_GetCursorOfs(&cursor
);
3462 while ((run
= run_prev( run
)) && is_link( run
))
3463 info
.chrg
.cpMin
-= run
->len
;
3465 /* find the last contiguous run with CFE_LINK set */
3466 info
.chrg
.cpMax
= ME_GetCursorOfs(&cursor
) + cursor
.run
->len
;
3468 while ((run
= run_next( run
)) && is_link( run
))
3469 info
.chrg
.cpMax
+= run
->len
;
3471 ITextHost_TxNotify(editor
->texthost
, info
.nmhdr
.code
, &info
);
3475 void ME_ReplaceSel(ME_TextEditor
*editor
, BOOL can_undo
, const WCHAR
*str
, int len
)
3477 int from
, to
, nStartCursor
;
3480 nStartCursor
= ME_GetSelectionOfs(editor
, &from
, &to
);
3481 style
= ME_GetSelectionInsertStyle(editor
);
3482 ME_InternalDeleteText(editor
, &editor
->pCursors
[nStartCursor
], to
-from
, FALSE
);
3483 ME_InsertTextFromCursor(editor
, 0, str
, len
, style
);
3484 ME_ReleaseStyle(style
);
3485 /* drop temporary style if line end */
3487 * FIXME question: does abc\n mean: put abc,
3488 * clear temp style, put \n? (would require a change)
3490 if (len
>0 && str
[len
-1] == '\n')
3491 ME_ClearTempStyle(editor
);
3492 ME_CommitUndo(editor
);
3493 ME_UpdateSelectionLinkAttribute(editor
);
3495 ME_EmptyUndoStack(editor
);
3496 ME_UpdateRepaint(editor
, FALSE
);
3499 static void ME_SetText(ME_TextEditor
*editor
, void *text
, BOOL unicode
)
3501 LONG codepage
= unicode
? CP_UNICODE
: CP_ACP
;
3504 LPWSTR wszText
= ME_ToUnicode(codepage
, text
, &textLen
);
3505 ME_InsertTextFromCursor(editor
, 0, wszText
, textLen
, editor
->pBuffer
->pDefaultStyle
);
3506 ME_EndToUnicode(codepage
, wszText
);
3509 static LRESULT
ME_WmCreate(ME_TextEditor
*editor
, LPARAM lParam
, BOOL unicode
)
3511 CREATESTRUCTW
*createW
= (CREATESTRUCTW
*)lParam
;
3512 CREATESTRUCTA
*createA
= (CREATESTRUCTA
*)lParam
;
3517 text
= unicode
? (void*)createW
->lpszName
: (void*)createA
->lpszName
;
3519 ME_SetDefaultFormatRect(editor
);
3521 max
= (editor
->styleFlags
& ES_DISABLENOSCROLL
) ? 1 : 0;
3522 if (~editor
->styleFlags
& ES_DISABLENOSCROLL
|| editor
->styleFlags
& WS_VSCROLL
)
3523 ITextHost_TxSetScrollRange(editor
->texthost
, SB_VERT
, 0, max
, TRUE
);
3525 if (~editor
->styleFlags
& ES_DISABLENOSCROLL
|| editor
->styleFlags
& WS_HSCROLL
)
3526 ITextHost_TxSetScrollRange(editor
->texthost
, SB_HORZ
, 0, max
, TRUE
);
3528 if (editor
->styleFlags
& ES_DISABLENOSCROLL
)
3530 if (editor
->styleFlags
& WS_VSCROLL
)
3532 ITextHost_TxEnableScrollBar(editor
->texthost
, SB_VERT
, ESB_DISABLE_BOTH
);
3533 ITextHost_TxShowScrollBar(editor
->texthost
, SB_VERT
, TRUE
);
3535 if (editor
->styleFlags
& WS_HSCROLL
)
3537 ITextHost_TxEnableScrollBar(editor
->texthost
, SB_HORZ
, ESB_DISABLE_BOTH
);
3538 ITextHost_TxShowScrollBar(editor
->texthost
, SB_HORZ
, TRUE
);
3544 ME_SetText(editor
, text
, unicode
);
3545 ME_SetCursorToStart(editor
, &editor
->pCursors
[0]);
3546 ME_SetCursorToStart(editor
, &editor
->pCursors
[1]);
3549 ME_CommitUndo(editor
);
3550 ME_WrapMarkedParagraphs(editor
);
3551 update_caret(editor
);
3555 static LRESULT
handle_EM_SETCHARFORMAT( ME_TextEditor
*editor
, WPARAM flags
, const CHARFORMAT2W
*fmt_in
)
3558 BOOL changed
= TRUE
;
3559 ME_Cursor start
, end
;
3561 if (!cfany_to_cf2w( &fmt
, fmt_in
)) return 0;
3563 if (flags
& SCF_ALL
)
3565 if (editor
->mode
& TM_PLAINTEXT
)
3567 ME_SetDefaultCharFormat( editor
, &fmt
);
3571 ME_SetCursorToStart( editor
, &start
);
3572 ME_SetCharFormat( editor
, &start
, NULL
, &fmt
);
3573 editor
->nModifyStep
= 1;
3576 else if (flags
& SCF_SELECTION
)
3578 if (editor
->mode
& TM_PLAINTEXT
) return 0;
3579 if (flags
& SCF_WORD
)
3581 end
= editor
->pCursors
[0];
3582 ME_MoveCursorWords( editor
, &end
, +1 );
3584 ME_MoveCursorWords( editor
, &start
, -1 );
3585 ME_SetCharFormat( editor
, &start
, &end
, &fmt
);
3587 changed
= ME_IsSelection( editor
);
3588 ME_SetSelectionCharFormat( editor
, &fmt
);
3589 if (changed
) editor
->nModifyStep
= 1;
3591 else /* SCF_DEFAULT */
3593 ME_SetDefaultCharFormat( editor
, &fmt
);
3596 ME_CommitUndo( editor
);
3599 ME_WrapMarkedParagraphs( editor
);
3600 ME_UpdateScrollBar( editor
);
3605 #define UNSUPPORTED_MSG(e) \
3607 FIXME(#e ": stub\n"); \
3608 *phresult = S_FALSE; \
3611 /* Handle messages for windowless and windowed richedit controls.
3613 * The LRESULT that is returned is a return value for window procs,
3614 * and the phresult parameter is the COM return code needed by the
3615 * text services interface. */
3616 LRESULT
ME_HandleMessage(ME_TextEditor
*editor
, UINT msg
, WPARAM wParam
,
3617 LPARAM lParam
, BOOL unicode
, HRESULT
* phresult
)
3623 UNSUPPORTED_MSG(EM_DISPLAYBAND
)
3624 UNSUPPORTED_MSG(EM_FINDWORDBREAK
)
3625 UNSUPPORTED_MSG(EM_FMTLINES
)
3626 UNSUPPORTED_MSG(EM_FORMATRANGE
)
3627 UNSUPPORTED_MSG(EM_GETBIDIOPTIONS
)
3628 UNSUPPORTED_MSG(EM_GETEDITSTYLE
)
3629 UNSUPPORTED_MSG(EM_GETIMECOMPMODE
)
3630 UNSUPPORTED_MSG(EM_GETIMESTATUS
)
3631 UNSUPPORTED_MSG(EM_SETIMESTATUS
)
3632 UNSUPPORTED_MSG(EM_GETLANGOPTIONS
)
3633 UNSUPPORTED_MSG(EM_GETREDONAME
)
3634 UNSUPPORTED_MSG(EM_GETTYPOGRAPHYOPTIONS
)
3635 UNSUPPORTED_MSG(EM_GETUNDONAME
)
3636 UNSUPPORTED_MSG(EM_GETWORDBREAKPROCEX
)
3637 UNSUPPORTED_MSG(EM_SETBIDIOPTIONS
)
3638 UNSUPPORTED_MSG(EM_SETEDITSTYLE
)
3639 UNSUPPORTED_MSG(EM_SETLANGOPTIONS
)
3640 UNSUPPORTED_MSG(EM_SETMARGINS
)
3641 UNSUPPORTED_MSG(EM_SETPALETTE
)
3642 UNSUPPORTED_MSG(EM_SETTABSTOPS
)
3643 UNSUPPORTED_MSG(EM_SETTYPOGRAPHYOPTIONS
)
3644 UNSUPPORTED_MSG(EM_SETWORDBREAKPROCEX
)
3646 /* Messages specific to Richedit controls */
3649 return ME_StreamIn(editor
, wParam
, (EDITSTREAM
*)lParam
, TRUE
);
3651 return ME_StreamOut(editor
, wParam
, (EDITSTREAM
*)lParam
);
3654 UINT code
= DLGC_WANTCHARS
|DLGC_WANTTAB
|DLGC_WANTARROWS
;
3657 editor
->bDialogMode
= TRUE
;
3658 if (editor
->styleFlags
& ES_MULTILINE
)
3659 code
|= DLGC_WANTMESSAGE
;
3660 if (!(editor
->styleFlags
& ES_SAVESEL
))
3661 code
|= DLGC_HASSETSEL
;
3664 case EM_EMPTYUNDOBUFFER
:
3665 ME_EmptyUndoStack(editor
);
3669 /* Note: wParam/lParam can be NULL */
3671 PUINT pfrom
= wParam
? (PUINT
)wParam
: &from
;
3672 PUINT pto
= lParam
? (PUINT
)lParam
: &to
;
3673 ME_GetSelectionOfs(editor
, (int *)pfrom
, (int *)pto
);
3674 if ((*pfrom
|*pto
) & 0xFFFF0000)
3676 return MAKELONG(*pfrom
,*pto
);
3680 CHARRANGE
*pRange
= (CHARRANGE
*)lParam
;
3681 ME_GetSelectionOfs(editor
, &pRange
->cpMin
, &pRange
->cpMax
);
3682 TRACE("EM_EXGETSEL = (%d,%d)\n", pRange
->cpMin
, pRange
->cpMax
);
3685 case EM_SETUNDOLIMIT
:
3687 if ((int)wParam
< 0)
3688 editor
->nUndoLimit
= STACK_SIZE_DEFAULT
;
3690 editor
->nUndoLimit
= min(wParam
, STACK_SIZE_MAX
);
3691 /* Setting a max stack size keeps wine from getting killed
3692 for hogging memory. Windows allocates all this memory at once, so
3693 no program would realistically set a value above our maximum. */
3694 return editor
->nUndoLimit
;
3697 return !list_empty( &editor
->undo_stack
);
3699 return !list_empty( &editor
->redo_stack
);
3700 case WM_UNDO
: /* FIXME: actually not the same */
3702 return ME_Undo(editor
);
3704 return ME_Redo(editor
);
3707 /* these flags are equivalent to the ES_* counterparts */
3708 DWORD mask
= ECO_VERTICAL
| ECO_AUTOHSCROLL
| ECO_AUTOVSCROLL
|
3709 ECO_NOHIDESEL
| ECO_READONLY
| ECO_WANTRETURN
| ECO_SELECTIONBAR
;
3710 DWORD settings
= editor
->styleFlags
& mask
;
3714 case EM_SETFONTSIZE
:
3717 LONG tmp_size
, size
;
3718 BOOL is_increase
= ((LONG
)wParam
> 0);
3720 if (editor
->mode
& TM_PLAINTEXT
)
3723 cf
.cbSize
= sizeof(cf
);
3724 cf
.dwMask
= CFM_SIZE
;
3725 ME_GetSelectionCharFormat(editor
, &cf
);
3726 tmp_size
= (cf
.yHeight
/ 20) + wParam
;
3730 else if (tmp_size
> 12 && tmp_size
< 28 && tmp_size
% 2)
3731 size
= tmp_size
+ (is_increase
? 1 : -1);
3732 else if (tmp_size
> 28 && tmp_size
< 36)
3733 size
= is_increase
? 36 : 28;
3734 else if (tmp_size
> 36 && tmp_size
< 48)
3735 size
= is_increase
? 48 : 36;
3736 else if (tmp_size
> 48 && tmp_size
< 72)
3737 size
= is_increase
? 72 : 48;
3738 else if (tmp_size
> 72 && tmp_size
< 80)
3739 size
= is_increase
? 80 : 72;
3740 else if (tmp_size
> 80 && tmp_size
< 1638)
3741 size
= 10 * (is_increase
? (tmp_size
/ 10 + 1) : (tmp_size
/ 10));
3742 else if (tmp_size
>= 1638)
3747 cf
.yHeight
= size
* 20; /* convert twips to points */
3748 ME_SetSelectionCharFormat(editor
, &cf
);
3749 ME_CommitUndo(editor
);
3750 ME_WrapMarkedParagraphs(editor
);
3751 ME_UpdateScrollBar(editor
);
3757 /* these flags are equivalent to ES_* counterparts, except for
3758 * ECO_AUTOWORDSELECTION that doesn't have an ES_* counterpart,
3759 * but is still stored in editor->styleFlags. */
3760 const DWORD mask
= ECO_VERTICAL
| ECO_AUTOHSCROLL
| ECO_AUTOVSCROLL
|
3761 ECO_NOHIDESEL
| ECO_READONLY
| ECO_WANTRETURN
|
3762 ECO_SELECTIONBAR
| ECO_AUTOWORDSELECTION
;
3763 DWORD settings
= mask
& editor
->styleFlags
;
3764 DWORD oldSettings
= settings
;
3765 DWORD changedSettings
;
3781 changedSettings
= oldSettings
^ settings
;
3783 if (changedSettings
) {
3784 editor
->styleFlags
= (editor
->styleFlags
& ~mask
) | (settings
& mask
);
3786 if (changedSettings
& ECO_SELECTIONBAR
)
3788 ITextHost_TxInvalidateRect(editor
->texthost
, &editor
->rcFormat
, TRUE
);
3789 if (settings
& ECO_SELECTIONBAR
) {
3790 assert(!editor
->selofs
);
3791 editor
->selofs
= SELECTIONBAR_WIDTH
;
3792 editor
->rcFormat
.left
+= editor
->selofs
;
3794 editor
->rcFormat
.left
-= editor
->selofs
;
3797 ME_RewrapRepaint(editor
);
3800 if ((changedSettings
& settings
& ES_NOHIDESEL
) && !editor
->bHaveFocus
)
3801 ME_InvalidateSelection( editor
);
3803 if (changedSettings
& settings
& ECO_VERTICAL
)
3804 FIXME("ECO_VERTICAL not implemented yet!\n");
3805 if (changedSettings
& settings
& ECO_AUTOHSCROLL
)
3806 FIXME("ECO_AUTOHSCROLL not implemented yet!\n");
3807 if (changedSettings
& settings
& ECO_AUTOVSCROLL
)
3808 FIXME("ECO_AUTOVSCROLL not implemented yet!\n");
3809 if (changedSettings
& settings
& ECO_WANTRETURN
)
3810 FIXME("ECO_WANTRETURN not implemented yet!\n");
3811 if (changedSettings
& settings
& ECO_AUTOWORDSELECTION
)
3812 FIXME("ECO_AUTOWORDSELECTION not implemented yet!\n");
3819 return set_selection( editor
, wParam
, lParam
);
3821 case EM_SETSCROLLPOS
:
3823 POINT
*point
= (POINT
*)lParam
;
3824 ME_ScrollAbs(editor
, point
->x
, point
->y
);
3827 case EM_AUTOURLDETECT
:
3829 if (wParam
==1 || wParam
==0)
3831 editor
->AutoURLDetect_bEnable
= (BOOL
)wParam
;
3834 return E_INVALIDARG
;
3836 case EM_GETAUTOURLDETECT
:
3838 return editor
->AutoURLDetect_bEnable
;
3842 CHARRANGE range
= *(CHARRANGE
*)lParam
;
3844 return set_selection( editor
, range
.cpMin
, range
.cpMax
);
3846 case EM_SHOWSCROLLBAR
:
3859 flags
= WS_HSCROLL
|WS_VSCROLL
;
3866 editor
->styleFlags
|= flags
;
3867 if (flags
& WS_HSCROLL
)
3868 ITextHost_TxShowScrollBar(editor
->texthost
, SB_HORZ
,
3869 editor
->nTotalWidth
> editor
->sizeWindow
.cx
);
3870 if (flags
& WS_VSCROLL
)
3871 ITextHost_TxShowScrollBar(editor
->texthost
, SB_VERT
,
3872 editor
->nTotalLength
> editor
->sizeWindow
.cy
);
3874 editor
->styleFlags
&= ~flags
;
3875 ITextHost_TxShowScrollBar(editor
->texthost
, wParam
, FALSE
);
3882 SETTEXTEX
*pStruct
= (SETTEXTEX
*)wParam
;
3885 BOOL bRtf
, bUnicode
, bSelection
, bUTF8
;
3886 int oldModify
= editor
->nModifyStep
;
3887 static const char utf8_bom
[] = {0xef, 0xbb, 0xbf};
3889 if (!pStruct
) return 0;
3891 /* If we detect ascii rtf at the start of the string,
3892 * we know it isn't unicode. */
3893 bRtf
= (lParam
&& (!strncmp((char *)lParam
, "{\\rtf", 5) ||
3894 !strncmp((char *)lParam
, "{\\urtf", 6)));
3895 bUnicode
= !bRtf
&& pStruct
->codepage
== CP_UNICODE
;
3896 bUTF8
= (lParam
&& (!strncmp((char *)lParam
, utf8_bom
, 3)));
3898 TRACE("EM_SETTEXTEX - %s, flags %d, cp %d\n",
3899 bUnicode
? debugstr_w((LPCWSTR
)lParam
) : debugstr_a((LPCSTR
)lParam
),
3900 pStruct
->flags
, pStruct
->codepage
);
3902 bSelection
= (pStruct
->flags
& ST_SELECTION
) != 0;
3904 int nStartCursor
= ME_GetSelectionOfs(editor
, &from
, &to
);
3905 style
= ME_GetSelectionInsertStyle(editor
);
3906 ME_InternalDeleteText(editor
, &editor
->pCursors
[nStartCursor
], to
- from
, FALSE
);
3909 ME_SetCursorToStart(editor
, &start
);
3910 ME_InternalDeleteText(editor
, &start
, ME_GetTextLength(editor
), FALSE
);
3911 style
= editor
->pBuffer
->pDefaultStyle
;
3915 ME_StreamInRTFString(editor
, bSelection
, (char *)lParam
);
3917 /* FIXME: The length returned doesn't include the rtf control
3918 * characters, only the actual text. */
3919 len
= lParam
? strlen((char *)lParam
) : 0;
3922 if (bUTF8
&& !bUnicode
) {
3923 wszText
= ME_ToUnicode(CP_UTF8
, (void *)(lParam
+3), &len
);
3924 ME_InsertTextFromCursor(editor
, 0, wszText
, len
, style
);
3925 ME_EndToUnicode(CP_UTF8
, wszText
);
3927 wszText
= ME_ToUnicode(pStruct
->codepage
, (void *)lParam
, &len
);
3928 ME_InsertTextFromCursor(editor
, 0, wszText
, len
, style
);
3929 ME_EndToUnicode(pStruct
->codepage
, wszText
);
3934 ME_ReleaseStyle(style
);
3935 ME_UpdateSelectionLinkAttribute(editor
);
3939 ME_SetCursorToStart(editor
, &cursor
);
3940 ME_UpdateLinkAttribute(editor
, &cursor
, INT_MAX
);
3942 ME_CommitUndo(editor
);
3943 if (!(pStruct
->flags
& ST_KEEPUNDO
))
3945 editor
->nModifyStep
= oldModify
;
3946 ME_EmptyUndoStack(editor
);
3948 ME_UpdateRepaint(editor
, FALSE
);
3951 case EM_SELECTIONTYPE
:
3952 return ME_GetSelectionType(editor
);
3953 case EM_SETBKGNDCOLOR
:
3956 if (editor
->rgbBackColor
!= -1) {
3957 DeleteObject(editor
->hbrBackground
);
3958 lColor
= editor
->rgbBackColor
;
3960 else lColor
= ITextHost_TxGetSysColor(editor
->texthost
, COLOR_WINDOW
);
3964 editor
->rgbBackColor
= -1;
3965 editor
->hbrBackground
= GetSysColorBrush(COLOR_WINDOW
);
3969 editor
->rgbBackColor
= lParam
;
3970 editor
->hbrBackground
= CreateSolidBrush(editor
->rgbBackColor
);
3972 ITextHost_TxInvalidateRect(editor
->texthost
, NULL
, TRUE
);
3976 return editor
->nModifyStep
== 0 ? 0 : -1;
3980 editor
->nModifyStep
= 1;
3982 editor
->nModifyStep
= 0;
3986 case EM_SETREADONLY
:
3989 editor
->styleFlags
|= ES_READONLY
;
3991 editor
->styleFlags
&= ~ES_READONLY
;
3994 case EM_SETEVENTMASK
:
3996 DWORD nOldMask
= editor
->nEventMask
;
3998 editor
->nEventMask
= lParam
;
4001 case EM_GETEVENTMASK
:
4002 return editor
->nEventMask
;
4003 case EM_SETCHARFORMAT
:
4004 return handle_EM_SETCHARFORMAT( editor
, wParam
, (CHARFORMAT2W
*)lParam
);
4005 case EM_GETCHARFORMAT
:
4007 CHARFORMAT2W tmp
, *dst
= (CHARFORMAT2W
*)lParam
;
4008 if (dst
->cbSize
!= sizeof(CHARFORMATA
) &&
4009 dst
->cbSize
!= sizeof(CHARFORMATW
) &&
4010 dst
->cbSize
!= sizeof(CHARFORMAT2A
) &&
4011 dst
->cbSize
!= sizeof(CHARFORMAT2W
))
4013 tmp
.cbSize
= sizeof(tmp
);
4015 ME_GetDefaultCharFormat(editor
, &tmp
);
4017 ME_GetSelectionCharFormat(editor
, &tmp
);
4018 cf2w_to_cfany(dst
, &tmp
);
4021 case EM_SETPARAFORMAT
:
4023 BOOL result
= editor_set_selection_para_fmt( editor
, (PARAFORMAT2
*)lParam
);
4024 ME_WrapMarkedParagraphs(editor
);
4025 ME_UpdateScrollBar(editor
);
4026 ME_CommitUndo(editor
);
4029 case EM_GETPARAFORMAT
:
4030 editor_get_selection_para_fmt( editor
, (PARAFORMAT2
*)lParam
);
4031 return ((PARAFORMAT2
*)lParam
)->dwMask
;
4032 case EM_GETFIRSTVISIBLELINE
:
4034 ME_Paragraph
*para
= editor_first_para( editor
);
4036 int y
= editor
->vert_si
.nPos
;
4039 while (para_next( para
))
4041 if (y
< para
->pt
.y
+ para
->nHeight
) break;
4042 count
+= para
->nRows
;
4043 para
= para_next( para
);
4046 row
= para_first_row( para
);
4049 if (y
< para
->pt
.y
+ row
->pt
.y
+ row
->nHeight
) break;
4051 row
= row_next( row
);
4055 case EM_HIDESELECTION
:
4057 editor
->bHideSelection
= (wParam
!= 0);
4058 ME_InvalidateSelection(editor
);
4063 if (!(editor
->styleFlags
& ES_MULTILINE
))
4065 ME_ScrollDown( editor
, lParam
* get_default_line_height( editor
) );
4071 int nStartCursor
= ME_GetSelectionOfs(editor
, &from
, &to
);
4072 ME_InternalDeleteText(editor
, &editor
->pCursors
[nStartCursor
], to
-from
, FALSE
);
4073 ME_CommitUndo(editor
);
4074 ME_UpdateRepaint(editor
, TRUE
);
4080 LONG codepage
= unicode
? CP_UNICODE
: CP_ACP
;
4081 LPWSTR wszText
= ME_ToUnicode(codepage
, (void *)lParam
, &len
);
4083 TRACE("EM_REPLACESEL - %s\n", debugstr_w(wszText
));
4085 ME_ReplaceSel(editor
, !!wParam
, wszText
, len
);
4086 ME_EndToUnicode(codepage
, wszText
);
4089 case EM_SCROLLCARET
:
4090 editor_ensure_visible( editor
, &editor
->pCursors
[0] );
4097 BOOL bRepaint
= LOWORD(lParam
);
4100 wParam
= (WPARAM
)GetStockObject(SYSTEM_FONT
);
4102 if (!GetObjectW((HGDIOBJ
)wParam
, sizeof(LOGFONTW
), &lf
))
4105 hDC
= ITextHost_TxGetDC(editor
->texthost
);
4106 ME_CharFormatFromLogFont(hDC
, &lf
, &fmt
);
4107 ITextHost_TxReleaseDC(editor
->texthost
, hDC
);
4108 if (editor
->mode
& TM_RICHTEXT
) {
4110 ME_SetCursorToStart(editor
, &start
);
4111 ME_SetCharFormat(editor
, &start
, NULL
, &fmt
);
4113 ME_SetDefaultCharFormat(editor
, &fmt
);
4115 ME_CommitUndo(editor
);
4116 editor_mark_rewrap_all( editor
);
4117 ME_WrapMarkedParagraphs(editor
);
4118 ME_UpdateScrollBar(editor
);
4126 ME_SetCursorToStart(editor
, &cursor
);
4127 ME_InternalDeleteText(editor
, &cursor
, ME_GetTextLength(editor
), FALSE
);
4130 TRACE("WM_SETTEXT lParam==%lx\n",lParam
);
4131 if (!strncmp((char *)lParam
, "{\\rtf", 5) ||
4132 !strncmp((char *)lParam
, "{\\urtf", 6))
4134 /* Undocumented: WM_SETTEXT supports RTF text */
4135 ME_StreamInRTFString(editor
, 0, (char *)lParam
);
4138 ME_SetText(editor
, (void*)lParam
, unicode
);
4141 TRACE("WM_SETTEXT - NULL\n");
4142 ME_SetCursorToStart(editor
, &cursor
);
4143 ME_UpdateLinkAttribute(editor
, &cursor
, INT_MAX
);
4144 set_selection_cursors(editor
, 0, 0);
4145 editor
->nModifyStep
= 0;
4146 ME_CommitUndo(editor
);
4147 ME_EmptyUndoStack(editor
);
4148 ME_UpdateRepaint(editor
, FALSE
);
4152 return paste_special( editor
, 0, NULL
, TRUE
);
4154 case WM_MBUTTONDOWN
:
4158 case EM_PASTESPECIAL
:
4159 paste_special( editor
, wParam
, (REPASTESPECIAL
*)lParam
, FALSE
);
4163 copy_or_cut(editor
, msg
== WM_CUT
);
4165 case WM_GETTEXTLENGTH
:
4167 GETTEXTLENGTHEX how
;
4169 /* CR/LF conversion required in 2.0 mode, verbatim in 1.0 mode */
4170 how
.flags
= GTL_CLOSE
| (editor
->bEmulateVersion10
? 0 : GTL_USECRLF
) | GTL_NUMCHARS
;
4171 how
.codepage
= unicode
? CP_UNICODE
: CP_ACP
;
4172 return ME_GetTextLengthEx(editor
, &how
);
4174 case EM_GETTEXTLENGTHEX
:
4175 return ME_GetTextLengthEx(editor
, (GETTEXTLENGTHEX
*)wParam
);
4179 ex
.cb
= wParam
* (unicode
? sizeof(WCHAR
) : sizeof(CHAR
));
4180 ex
.flags
= GT_USECRLF
;
4181 ex
.codepage
= unicode
? CP_UNICODE
: CP_ACP
;
4182 ex
.lpDefaultChar
= NULL
;
4183 ex
.lpUsedDefChar
= NULL
;
4184 return ME_GetTextEx(editor
, &ex
, lParam
);
4187 return ME_GetTextEx(editor
, (GETTEXTEX
*)wParam
, lParam
);
4190 int nFrom
, nTo
, nStartCur
= ME_GetSelectionOfs(editor
, &nFrom
, &nTo
);
4191 ME_Cursor
*from
= &editor
->pCursors
[nStartCur
];
4192 return ME_GetTextRange(editor
, (WCHAR
*)lParam
, from
,
4193 nTo
- nFrom
, unicode
);
4195 case EM_GETSCROLLPOS
:
4197 POINT
*point
= (POINT
*)lParam
;
4198 point
->x
= editor
->horz_si
.nPos
;
4199 point
->y
= editor
->vert_si
.nPos
;
4200 /* 16-bit scaled value is returned as stored in scrollinfo */
4201 if (editor
->horz_si
.nMax
> 0xffff)
4202 point
->x
= MulDiv(point
->x
, 0xffff, editor
->horz_si
.nMax
);
4203 if (editor
->vert_si
.nMax
> 0xffff)
4204 point
->y
= MulDiv(point
->y
, 0xffff, editor
->vert_si
.nMax
);
4207 case EM_GETTEXTRANGE
:
4209 TEXTRANGEW
*rng
= (TEXTRANGEW
*)lParam
;
4211 int nStart
= rng
->chrg
.cpMin
;
4212 int nEnd
= rng
->chrg
.cpMax
;
4213 int textlength
= ME_GetTextLength(editor
);
4215 TRACE("EM_GETTEXTRANGE min=%d max=%d unicode=%d textlength=%d\n",
4216 rng
->chrg
.cpMin
, rng
->chrg
.cpMax
, unicode
, textlength
);
4217 if (nStart
< 0) return 0;
4218 if ((nStart
== 0 && nEnd
== -1) || nEnd
> textlength
)
4220 if (nStart
>= nEnd
) return 0;
4222 cursor_from_char_ofs( editor
, nStart
, &start
);
4223 return ME_GetTextRange(editor
, rng
->lpstrText
, &start
, nEnd
- nStart
, unicode
);
4229 const unsigned int nMaxChars
= *(WORD
*) lParam
;
4230 unsigned int nCharsLeft
= nMaxChars
;
4231 char *dest
= (char *) lParam
;
4232 BOOL wroteNull
= FALSE
;
4233 ME_Cursor start
, end
;
4235 TRACE("EM_GETLINE: row=%d, nMaxChars=%d (%s)\n", (int) wParam
, nMaxChars
,
4236 unicode
? "Unicode" : "Ansi");
4238 row
= row_from_row_number( editor
, wParam
);
4239 if (row
== NULL
) return 0;
4241 row_first_cursor( row
, &start
);
4242 row_end_cursor( row
, &end
, TRUE
);
4248 int ofs
= (run
== start
.run
) ? start
.nOffset
: 0;
4249 int len
= (run
== end
.run
) ? end
.nOffset
: run
->len
;
4251 str
= get_text( run
, ofs
);
4252 nCopy
= min( nCharsLeft
, len
);
4255 memcpy(dest
, str
, nCopy
* sizeof(WCHAR
));
4257 nCopy
= WideCharToMultiByte(CP_ACP
, 0, str
, nCopy
, dest
,
4258 nCharsLeft
, NULL
, NULL
);
4259 dest
+= nCopy
* (unicode
? sizeof(WCHAR
) : 1);
4260 nCharsLeft
-= nCopy
;
4261 if (run
== end
.run
) break;
4262 run
= row_next_run( row
, run
);
4265 /* append line termination, space allowing */
4269 *((WCHAR
*)dest
) = '\0';
4276 TRACE("EM_GETLINE: got %u characters\n", nMaxChars
- nCharsLeft
);
4277 return nMaxChars
- nCharsLeft
- (wroteNull
? 1 : 0);
4279 case EM_GETLINECOUNT
:
4281 int count
= editor
->total_rows
;
4282 ME_Run
*prev_run
, *last_run
;
4284 last_run
= para_end_run( para_prev( editor_end_para( editor
) ) );
4285 prev_run
= run_prev_all_paras( last_run
);
4287 if (editor
->bEmulateVersion10
&& prev_run
&& last_run
->nCharOfs
== 0 &&
4288 prev_run
->len
== 1 && *get_text( prev_run
, 0 ) == '\r')
4290 /* In 1.0 emulation, the last solitary \r at the very end of the text
4291 (if one exists) is NOT a line break.
4292 FIXME: this is an ugly hack. This should have a more regular model. */
4296 count
= max(1, count
);
4297 TRACE("EM_GETLINECOUNT: count==%d\n", count
);
4300 case EM_LINEFROMCHAR
:
4302 if (wParam
== -1) wParam
= ME_GetCursorOfs( editor
->pCursors
+ 1 );
4303 return row_number_from_char_ofs( editor
, wParam
);
4305 case EM_EXLINEFROMCHAR
:
4307 if (lParam
== -1) lParam
= ME_GetCursorOfs( editor
->pCursors
+ 1 );
4308 return row_number_from_char_ofs( editor
, lParam
);
4316 if (wParam
== -1) row
= row_from_cursor( editor
->pCursors
);
4317 else row
= row_from_row_number( editor
, wParam
);
4318 if (!row
) return -1;
4320 row_first_cursor( row
, &cursor
);
4321 ofs
= ME_GetCursorOfs( &cursor
);
4322 TRACE( "EM_LINEINDEX: nCharOfs==%d\n", ofs
);
4328 int start_ofs
, end_ofs
;
4331 if (wParam
> ME_GetTextLength(editor
))
4335 FIXME("EM_LINELENGTH: returning number of unselected characters on lines with selection unsupported.\n");
4338 cursor_from_char_ofs( editor
, wParam
, &cursor
);
4339 row
= row_from_cursor( &cursor
);
4340 row_first_cursor( row
, &cursor
);
4341 start_ofs
= ME_GetCursorOfs( &cursor
);
4342 row_end_cursor( row
, &cursor
, FALSE
);
4343 end_ofs
= ME_GetCursorOfs( &cursor
);
4344 TRACE( "EM_LINELENGTH(%ld)==%d\n", wParam
, end_ofs
- start_ofs
);
4345 return end_ofs
- start_ofs
;
4347 case EM_EXLIMITTEXT
:
4349 if ((int)lParam
< 0)
4352 editor
->nTextLimit
= 65536;
4354 editor
->nTextLimit
= (int) lParam
;
4360 editor
->nTextLimit
= 65536;
4362 editor
->nTextLimit
= (int) wParam
;
4365 case EM_GETLIMITTEXT
:
4367 return editor
->nTextLimit
;
4373 FINDTEXTA
*ft
= (FINDTEXTA
*)lParam
;
4374 int nChars
= MultiByteToWideChar(CP_ACP
, 0, ft
->lpstrText
, -1, NULL
, 0);
4377 if ((tmp
= heap_alloc(nChars
* sizeof(*tmp
))) != NULL
)
4378 MultiByteToWideChar(CP_ACP
, 0, ft
->lpstrText
, -1, tmp
, nChars
);
4379 r
= ME_FindText(editor
, wParam
, &ft
->chrg
, tmp
, NULL
);
4382 FINDTEXTW
*ft
= (FINDTEXTW
*)lParam
;
4383 r
= ME_FindText(editor
, wParam
, &ft
->chrg
, ft
->lpstrText
, NULL
);
4391 FINDTEXTEXA
*ex
= (FINDTEXTEXA
*)lParam
;
4392 int nChars
= MultiByteToWideChar(CP_ACP
, 0, ex
->lpstrText
, -1, NULL
, 0);
4395 if ((tmp
= heap_alloc(nChars
* sizeof(*tmp
))) != NULL
)
4396 MultiByteToWideChar(CP_ACP
, 0, ex
->lpstrText
, -1, tmp
, nChars
);
4397 r
= ME_FindText(editor
, wParam
, &ex
->chrg
, tmp
, &ex
->chrgText
);
4400 FINDTEXTEXW
*ex
= (FINDTEXTEXW
*)lParam
;
4401 r
= ME_FindText(editor
, wParam
, &ex
->chrg
, ex
->lpstrText
, &ex
->chrgText
);
4407 FINDTEXTW
*ft
= (FINDTEXTW
*)lParam
;
4408 return ME_FindText(editor
, wParam
, &ft
->chrg
, ft
->lpstrText
, NULL
);
4410 case EM_FINDTEXTEXW
:
4412 FINDTEXTEXW
*ex
= (FINDTEXTEXW
*)lParam
;
4413 return ME_FindText(editor
, wParam
, &ex
->chrg
, ex
->lpstrText
, &ex
->chrgText
);
4416 if (!wParam
|| !lParam
)
4418 *(int *)wParam
= editor
->nZoomNumerator
;
4419 *(int *)lParam
= editor
->nZoomDenominator
;
4422 return ME_SetZoom(editor
, wParam
, lParam
);
4423 case EM_CHARFROMPOS
:
4426 if (ME_CharFromPos(editor
, ((POINTL
*)lParam
)->x
, ((POINTL
*)lParam
)->y
,
4428 return ME_GetCursorOfs(&cursor
);
4432 case EM_POSFROMCHAR
:
4435 int nCharOfs
, nLength
;
4439 /* detect which API version we're dealing with */
4440 if (wParam
>= 0x40000)
4442 nLength
= ME_GetTextLength(editor
);
4443 nCharOfs
= min(nCharOfs
, nLength
);
4444 nCharOfs
= max(nCharOfs
, 0);
4446 cursor_from_char_ofs( editor
, nCharOfs
, &cursor
);
4447 pt
.y
= cursor
.run
->pt
.y
;
4448 pt
.x
= cursor
.run
->pt
.x
+
4449 ME_PointFromChar( editor
, cursor
.run
, cursor
.nOffset
, TRUE
);
4450 pt
.y
+= cursor
.para
->pt
.y
+ editor
->rcFormat
.top
;
4451 pt
.x
+= editor
->rcFormat
.left
;
4453 pt
.x
-= editor
->horz_si
.nPos
;
4454 pt
.y
-= editor
->vert_si
.nPos
;
4456 if (wParam
>= 0x40000) *(POINTL
*)wParam
= pt
;
4458 return (wParam
>= 0x40000) ? 0 : MAKELONG( pt
.x
, pt
.y
);
4461 return ME_WmCreate(editor
, lParam
, unicode
);
4463 ME_DestroyEditor(editor
);
4468 if (wParam
== (WPARAM
)editor
->hWnd
&& GetCursorPos(&cursor_pos
) &&
4469 ScreenToClient(editor
->hWnd
, &cursor_pos
))
4470 ME_LinkNotify(editor
, msg
, 0, MAKELPARAM(cursor_pos
.x
, cursor_pos
.y
));
4471 return ME_SetCursor(editor
);
4473 case WM_LBUTTONDBLCLK
:
4474 case WM_LBUTTONDOWN
:
4476 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
4477 if ((editor
->nEventMask
& ENM_MOUSEEVENTS
) &&
4478 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4480 ITextHost_TxSetFocus(editor
->texthost
);
4481 ME_LButtonDown(editor
, (short)LOWORD(lParam
), (short)HIWORD(lParam
),
4482 ME_CalculateClickCount(editor
, msg
, wParam
, lParam
));
4483 ITextHost_TxSetCapture(editor
->texthost
, TRUE
);
4484 editor
->bMouseCaptured
= TRUE
;
4485 ME_LinkNotify(editor
, msg
, wParam
, lParam
);
4486 if (!ME_SetCursor(editor
)) goto do_default
;
4490 if ((editor
->nEventMask
& ENM_MOUSEEVENTS
) &&
4491 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4493 if (editor
->bMouseCaptured
)
4494 ME_MouseMove(editor
, (short)LOWORD(lParam
), (short)HIWORD(lParam
));
4496 ME_LinkNotify(editor
, msg
, wParam
, lParam
);
4497 /* Set cursor if mouse is captured, since WM_SETCURSOR won't be received. */
4498 if (editor
->bMouseCaptured
)
4499 ME_SetCursor(editor
);
4502 if (editor
->bMouseCaptured
) {
4503 ITextHost_TxSetCapture(editor
->texthost
, FALSE
);
4504 editor
->bMouseCaptured
= FALSE
;
4506 if (editor
->nSelectionType
== stDocument
)
4507 editor
->nSelectionType
= stPosition
;
4508 if ((editor
->nEventMask
& ENM_MOUSEEVENTS
) &&
4509 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4513 ME_SetCursor(editor
);
4514 ME_LinkNotify(editor
, msg
, wParam
, lParam
);
4518 case WM_RBUTTONDOWN
:
4519 case WM_RBUTTONDBLCLK
:
4520 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
4521 if ((editor
->nEventMask
& ENM_MOUSEEVENTS
) &&
4522 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4524 ME_LinkNotify(editor
, msg
, wParam
, lParam
);
4526 case WM_CONTEXTMENU
:
4527 if (!ME_ShowContextMenu(editor
, (short)LOWORD(lParam
), (short)HIWORD(lParam
)))
4531 editor
->bHaveFocus
= TRUE
;
4532 create_caret(editor
);
4533 update_caret(editor
);
4534 ME_SendOldNotify(editor
, EN_SETFOCUS
);
4535 if (!editor
->bHideSelection
&& !(editor
->styleFlags
& ES_NOHIDESEL
))
4536 ME_InvalidateSelection( editor
);
4539 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
4540 editor
->bHaveFocus
= FALSE
;
4541 editor
->wheel_remain
= 0;
4544 ME_SendOldNotify(editor
, EN_KILLFOCUS
);
4545 if (!editor
->bHideSelection
&& !(editor
->styleFlags
& ES_NOHIDESEL
))
4546 ME_InvalidateSelection( editor
);
4549 TRACE("editor wnd command = %d\n", LOWORD(wParam
));
4552 if ((editor
->nEventMask
& ENM_KEYEVENTS
) &&
4553 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4557 if ((editor
->nEventMask
& ENM_KEYEVENTS
) &&
4558 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4560 if (ME_KeyDown(editor
, LOWORD(wParam
)))
4564 if ((editor
->nEventMask
& ENM_KEYEVENTS
) &&
4565 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4567 return ME_Char(editor
, wParam
, lParam
, unicode
);
4571 if(wParam
== UNICODE_NOCHAR
) return TRUE
;
4572 if(wParam
<= 0x000fffff)
4574 if(wParam
> 0xffff) /* convert to surrogates */
4577 ME_Char(editor
, (wParam
>> 10) + 0xd800, 0, TRUE
);
4578 ME_Char(editor
, (wParam
& 0x03ff) + 0xdc00, 0, TRUE
);
4580 ME_Char(editor
, wParam
, 0, TRUE
);
4586 case EM_STOPGROUPTYPING
:
4587 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
4591 const int scrollUnit
= 7;
4593 switch(LOWORD(wParam
))
4596 ME_ScrollAbs(editor
, 0, 0);
4599 ME_ScrollAbs(editor
,
4600 editor
->horz_si
.nMax
- (int)editor
->horz_si
.nPage
,
4601 editor
->vert_si
.nMax
- (int)editor
->vert_si
.nPage
);
4604 ME_ScrollLeft(editor
, scrollUnit
);
4607 ME_ScrollRight(editor
, scrollUnit
);
4610 ME_ScrollLeft(editor
, editor
->sizeWindow
.cx
);
4613 ME_ScrollRight(editor
, editor
->sizeWindow
.cx
);
4616 case SB_THUMBPOSITION
:
4618 int pos
= HIWORD(wParam
);
4619 if (editor
->horz_si
.nMax
> 0xffff)
4620 pos
= MulDiv(pos
, editor
->horz_si
.nMax
, 0xffff);
4621 ME_HScrollAbs(editor
, pos
);
4627 case EM_SCROLL
: /* fall through */
4631 int lineHeight
= get_default_line_height( editor
);
4633 origNPos
= editor
->vert_si
.nPos
;
4635 switch(LOWORD(wParam
))
4638 ME_ScrollAbs(editor
, 0, 0);
4641 ME_ScrollAbs(editor
,
4642 editor
->horz_si
.nMax
- (int)editor
->horz_si
.nPage
,
4643 editor
->vert_si
.nMax
- (int)editor
->vert_si
.nPage
);
4646 ME_ScrollUp(editor
,lineHeight
);
4649 ME_ScrollDown(editor
,lineHeight
);
4652 ME_ScrollUp(editor
,editor
->sizeWindow
.cy
);
4655 ME_ScrollDown(editor
,editor
->sizeWindow
.cy
);
4658 case SB_THUMBPOSITION
:
4660 int pos
= HIWORD(wParam
);
4661 if (editor
->vert_si
.nMax
> 0xffff)
4662 pos
= MulDiv(pos
, editor
->vert_si
.nMax
, 0xffff);
4663 ME_VScrollAbs(editor
, pos
);
4667 if (msg
== EM_SCROLL
)
4668 return 0x00010000 | (((editor
->vert_si
.nPos
- origNPos
)/lineHeight
) & 0xffff);
4676 if ((editor
->nEventMask
& ENM_MOUSEEVENTS
) &&
4677 !ME_FilterEvent(editor
, msg
, &wParam
, &lParam
))
4680 ctrl_is_down
= GetKeyState(VK_CONTROL
) & 0x8000;
4682 delta
= GET_WHEEL_DELTA_WPARAM(wParam
);
4684 /* if scrolling changes direction, ignore left overs */
4685 if ((delta
< 0 && editor
->wheel_remain
< 0) ||
4686 (delta
> 0 && editor
->wheel_remain
> 0))
4687 editor
->wheel_remain
+= delta
;
4689 editor
->wheel_remain
= delta
;
4691 if (editor
->wheel_remain
)
4695 if (!editor
->nZoomNumerator
|| !editor
->nZoomDenominator
)
4699 numerator
= editor
->nZoomNumerator
* 100 / editor
->nZoomDenominator
;
4701 numerator
+= calc_wheel_change( &editor
->wheel_remain
, 10 );
4702 if (numerator
>= 10 && numerator
<= 500)
4703 ME_SetZoom(editor
, numerator
, 100);
4708 SystemParametersInfoW( SPI_GETWHEELSCROLLLINES
, 0, &max_lines
, 0 );
4710 lines
= calc_wheel_change( &editor
->wheel_remain
, (int)max_lines
);
4712 ME_ScrollDown( editor
, -lines
* get_default_line_height( editor
) );
4719 *((RECT
*)lParam
) = editor
->rcFormat
;
4720 if (editor
->bDefaultFormatRect
)
4721 ((RECT
*)lParam
)->left
-= editor
->selofs
;
4731 RECT
*rc
= (RECT
*)lParam
;
4733 border
= editor
->exStyleFlags
& WS_EX_CLIENTEDGE
? 1 : 0;
4734 ITextHost_TxGetClientRect(editor
->texthost
, &clientRect
);
4737 editor
->rcFormat
.top
= max(0, rc
->top
- border
);
4738 editor
->rcFormat
.left
= max(0, rc
->left
- border
);
4739 editor
->rcFormat
.bottom
= min(clientRect
.bottom
, rc
->bottom
);
4740 editor
->rcFormat
.right
= min(clientRect
.right
, rc
->right
+ border
);
4741 } else if (wParam
== 1) {
4742 /* MSDN incorrectly says a wParam value of 1 causes the
4743 * lParam rect to be used as a relative offset,
4744 * however, the tests show it just prevents min/max bound
4746 editor
->rcFormat
.top
= rc
->top
- border
;
4747 editor
->rcFormat
.left
= rc
->left
- border
;
4748 editor
->rcFormat
.bottom
= rc
->bottom
;
4749 editor
->rcFormat
.right
= rc
->right
+ border
;
4753 editor
->bDefaultFormatRect
= FALSE
;
4757 ME_SetDefaultFormatRect(editor
);
4758 editor
->bDefaultFormatRect
= TRUE
;
4760 editor_mark_rewrap_all( editor
);
4761 ME_WrapMarkedParagraphs(editor
);
4762 ME_UpdateScrollBar(editor
);
4763 if (msg
!= EM_SETRECTNP
)
4767 case EM_REQUESTRESIZE
:
4768 ME_SendRequestResize(editor
, TRUE
);
4772 case WM_WINDOWPOSCHANGED
:
4775 WINDOWPOS
*winpos
= (WINDOWPOS
*)lParam
;
4777 if (winpos
->flags
& SWP_NOCLIENTSIZE
) goto do_default
;
4778 ITextHost_TxGetClientRect(editor
->texthost
, &clientRect
);
4779 if (editor
->bDefaultFormatRect
) {
4780 ME_SetDefaultFormatRect(editor
);
4782 editor
->rcFormat
.right
+= clientRect
.right
- editor
->prevClientRect
.right
;
4783 editor
->rcFormat
.bottom
+= clientRect
.bottom
- editor
->prevClientRect
.bottom
;
4785 editor
->prevClientRect
= clientRect
;
4786 ME_RewrapRepaint(editor
);
4789 /* IME messages to make richedit controls IME aware */
4790 case WM_IME_SETCONTEXT
:
4791 case WM_IME_CONTROL
:
4793 case WM_IME_COMPOSITIONFULL
:
4795 case WM_IME_STARTCOMPOSITION
:
4797 editor
->imeStartIndex
=ME_GetCursorOfs(&editor
->pCursors
[0]);
4798 ME_DeleteSelection(editor
);
4799 ME_CommitUndo(editor
);
4800 ME_UpdateRepaint(editor
, FALSE
);
4803 case WM_IME_COMPOSITION
:
4807 ME_Style
*style
= style_get_insert_style( editor
, editor
->pCursors
);
4808 hIMC
= ITextHost_TxImmGetContext(editor
->texthost
);
4809 ME_DeleteSelection(editor
);
4810 ME_SaveTempStyle(editor
, style
);
4811 if (lParam
& (GCS_RESULTSTR
|GCS_COMPSTR
))
4813 LPWSTR lpCompStr
= NULL
;
4815 DWORD dwIndex
= lParam
& GCS_RESULTSTR
;
4817 dwIndex
= GCS_COMPSTR
;
4819 dwBufLen
= ImmGetCompositionStringW(hIMC
, dwIndex
, NULL
, 0);
4820 lpCompStr
= HeapAlloc(GetProcessHeap(),0,dwBufLen
+ sizeof(WCHAR
));
4821 ImmGetCompositionStringW(hIMC
, dwIndex
, lpCompStr
, dwBufLen
);
4822 lpCompStr
[dwBufLen
/sizeof(WCHAR
)] = 0;
4823 ME_InsertTextFromCursor(editor
,0,lpCompStr
,dwBufLen
/sizeof(WCHAR
),style
);
4824 HeapFree(GetProcessHeap(), 0, lpCompStr
);
4826 if (dwIndex
== GCS_COMPSTR
)
4827 set_selection_cursors(editor
,editor
->imeStartIndex
,
4828 editor
->imeStartIndex
+ dwBufLen
/sizeof(WCHAR
));
4830 ME_ReleaseStyle(style
);
4831 ME_CommitUndo(editor
);
4832 ME_UpdateRepaint(editor
, FALSE
);
4835 case WM_IME_ENDCOMPOSITION
:
4837 ME_DeleteSelection(editor
);
4838 editor
->imeStartIndex
=-1;
4841 case EM_GETOLEINTERFACE
:
4844 if (!CreateIRichEditOle(NULL
, editor
, (LPVOID
*)&editor
->reOle
))
4846 if (IUnknown_QueryInterface(editor
->reOle
, &IID_IRichEditOle
, (LPVOID
*)lParam
) == S_OK
)
4850 case EM_GETPASSWORDCHAR
:
4852 return editor
->cPasswordMask
;
4854 case EM_SETOLECALLBACK
:
4855 if(editor
->lpOleCallback
)
4856 IRichEditOleCallback_Release(editor
->lpOleCallback
);
4857 editor
->lpOleCallback
= (IRichEditOleCallback
*)lParam
;
4858 if(editor
->lpOleCallback
)
4859 IRichEditOleCallback_AddRef(editor
->lpOleCallback
);
4861 case EM_GETWORDBREAKPROC
:
4862 return (LRESULT
)editor
->pfnWordBreak
;
4863 case EM_SETWORDBREAKPROC
:
4865 EDITWORDBREAKPROCW pfnOld
= editor
->pfnWordBreak
;
4867 editor
->pfnWordBreak
= (EDITWORDBREAKPROCW
)lParam
;
4868 return (LRESULT
)pfnOld
;
4870 case EM_GETTEXTMODE
:
4871 return editor
->mode
;
4872 case EM_SETTEXTMODE
:
4877 if (ME_GetTextLength(editor
) ||
4878 !list_empty( &editor
->undo_stack
) || !list_empty( &editor
->redo_stack
))
4879 return E_UNEXPECTED
;
4881 /* Check for mutually exclusive flags in adjacent bits of wParam */
4882 if ((wParam
& (TM_RICHTEXT
| TM_MULTILEVELUNDO
| TM_MULTICODEPAGE
)) &
4883 (wParam
& (TM_PLAINTEXT
| TM_SINGLELEVELUNDO
| TM_SINGLECODEPAGE
)) << 1)
4884 return E_INVALIDARG
;
4886 if (wParam
& (TM_RICHTEXT
| TM_PLAINTEXT
))
4888 mask
|= TM_RICHTEXT
| TM_PLAINTEXT
;
4889 changes
|= wParam
& (TM_RICHTEXT
| TM_PLAINTEXT
);
4890 if (wParam
& TM_PLAINTEXT
) {
4891 /* Clear selection since it should be possible to select the
4892 * end of text run for rich text */
4893 ME_InvalidateSelection(editor
);
4894 ME_SetCursorToStart(editor
, &editor
->pCursors
[0]);
4895 editor
->pCursors
[1] = editor
->pCursors
[0];
4896 /* plain text can only have the default style. */
4897 ME_ClearTempStyle(editor
);
4898 ME_AddRefStyle(editor
->pBuffer
->pDefaultStyle
);
4899 ME_ReleaseStyle( editor
->pCursors
[0].run
->style
);
4900 editor
->pCursors
[0].run
->style
= editor
->pBuffer
->pDefaultStyle
;
4903 /* FIXME: Currently no support for undo level and code page options */
4904 editor
->mode
= (editor
->mode
& ~mask
) | changes
;
4907 case EM_SETPASSWORDCHAR
:
4909 editor
->cPasswordMask
= wParam
;
4910 ME_RewrapRepaint(editor
);
4913 case EM_SETTARGETDEVICE
:
4916 BOOL
new = (lParam
== 0 && (editor
->styleFlags
& ES_MULTILINE
));
4917 if (editor
->nAvailWidth
|| editor
->bWordWrap
!= new)
4919 editor
->bWordWrap
= new;
4920 editor
->nAvailWidth
= 0; /* wrap to client area */
4921 ME_RewrapRepaint(editor
);
4924 int width
= max(0, lParam
);
4925 if ((editor
->styleFlags
& ES_MULTILINE
) &&
4926 (!editor
->bWordWrap
|| editor
->nAvailWidth
!= width
))
4928 editor
->nAvailWidth
= width
;
4929 editor
->bWordWrap
= TRUE
;
4930 ME_RewrapRepaint(editor
);
4932 FIXME("EM_SETTARGETDEVICE doesn't use non-NULL target devices\n");
4937 *phresult
= S_FALSE
;
4943 static BOOL
create_windowed_editor(HWND hwnd
, CREATESTRUCTW
*create
, BOOL emulate_10
)
4945 ITextHost
*host
= ME_CreateTextHost( hwnd
, create
, emulate_10
);
4946 ME_TextEditor
*editor
;
4948 if (!host
) return FALSE
;
4950 editor
= ME_MakeEditor( host
, emulate_10
);
4953 ITextHost_Release( host
);
4957 editor
->exStyleFlags
= GetWindowLongW( hwnd
, GWL_EXSTYLE
);
4958 editor
->styleFlags
|= GetWindowLongW( hwnd
, GWL_STYLE
) & ES_WANTRETURN
;
4959 editor
->hWnd
= hwnd
; /* FIXME: Remove editor's dependence on hWnd */
4960 editor
->hwndParent
= create
->hwndParent
;
4962 SetWindowLongPtrW( hwnd
, 0, (LONG_PTR
)editor
);
4967 static LRESULT
RichEditWndProc_common(HWND hWnd
, UINT msg
, WPARAM wParam
,
4968 LPARAM lParam
, BOOL unicode
)
4970 ME_TextEditor
*editor
;
4972 LRESULT lresult
= 0;
4974 TRACE("enter hwnd %p msg %04x (%s) %lx %lx, unicode %d\n",
4975 hWnd
, msg
, get_msg_name(msg
), wParam
, lParam
, unicode
);
4977 editor
= (ME_TextEditor
*)GetWindowLongPtrW(hWnd
, 0);
4980 if (msg
== WM_NCCREATE
)
4982 CREATESTRUCTW
*pcs
= (CREATESTRUCTW
*)lParam
;
4984 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd
, pcs
->style
);
4985 return create_windowed_editor( hWnd
, pcs
, FALSE
);
4989 return DefWindowProcW(hWnd
, msg
, wParam
, lParam
);
5002 update_caret(editor
);
5003 hdc
= BeginPaint(editor
->hWnd
, &ps
);
5004 if (!editor
->bEmulateVersion10
|| (editor
->nEventMask
& ENM_UPDATE
))
5005 ME_SendOldNotify(editor
, EN_UPDATE
);
5006 old_brush
= SelectObject(hdc
, editor
->hbrBackground
);
5008 /* Erase area outside of the formatting rectangle */
5009 if (ps
.rcPaint
.top
< editor
->rcFormat
.top
)
5012 rc
.bottom
= editor
->rcFormat
.top
;
5013 PatBlt(hdc
, rc
.left
, rc
.top
, rc
.right
- rc
.left
, rc
.bottom
- rc
.top
, PATCOPY
);
5014 ps
.rcPaint
.top
= editor
->rcFormat
.top
;
5016 if (ps
.rcPaint
.bottom
> editor
->rcFormat
.bottom
) {
5018 rc
.top
= editor
->rcFormat
.bottom
;
5019 PatBlt(hdc
, rc
.left
, rc
.top
, rc
.right
- rc
.left
, rc
.bottom
- rc
.top
, PATCOPY
);
5020 ps
.rcPaint
.bottom
= editor
->rcFormat
.bottom
;
5022 if (ps
.rcPaint
.left
< editor
->rcFormat
.left
) {
5024 rc
.right
= editor
->rcFormat
.left
;
5025 PatBlt(hdc
, rc
.left
, rc
.top
, rc
.right
- rc
.left
, rc
.bottom
- rc
.top
, PATCOPY
);
5026 ps
.rcPaint
.left
= editor
->rcFormat
.left
;
5028 if (ps
.rcPaint
.right
> editor
->rcFormat
.right
) {
5030 rc
.left
= editor
->rcFormat
.right
;
5031 PatBlt(hdc
, rc
.left
, rc
.top
, rc
.right
- rc
.left
, rc
.bottom
- rc
.top
, PATCOPY
);
5032 ps
.rcPaint
.right
= editor
->rcFormat
.right
;
5035 ME_PaintContent(editor
, hdc
, &ps
.rcPaint
);
5036 SelectObject(hdc
, old_brush
);
5037 EndPaint(editor
->hWnd
, &ps
);
5042 HDC hDC
= (HDC
)wParam
;
5045 if (GetUpdateRect(editor
->hWnd
, &rc
, TRUE
))
5046 FillRect(hDC
, &rc
, editor
->hbrBackground
);
5052 const DWORD mask
= ECO_VERTICAL
| ECO_AUTOHSCROLL
| ECO_AUTOVSCROLL
|
5053 ECO_NOHIDESEL
| ECO_READONLY
| ECO_WANTRETURN
|
5055 lresult
= ME_HandleMessage(editor
, msg
, wParam
, lParam
, unicode
, &hresult
);
5056 dwStyle
= GetWindowLongW(hWnd
, GWL_STYLE
);
5057 dwStyle
= (dwStyle
& ~mask
) | (lresult
& mask
);
5058 SetWindowLongW(hWnd
, GWL_STYLE
, dwStyle
);
5061 case EM_SETREADONLY
:
5064 lresult
= ME_HandleMessage(editor
, msg
, wParam
, lParam
, unicode
, &hresult
);
5065 dwStyle
= GetWindowLongW(hWnd
, GWL_STYLE
);
5066 dwStyle
&= ~ES_READONLY
;
5068 dwStyle
|= ES_READONLY
;
5069 SetWindowLongW(hWnd
, GWL_STYLE
, dwStyle
);
5073 lresult
= ME_HandleMessage(editor
, msg
, wParam
, lParam
, unicode
, &hresult
);
5076 if (hresult
== S_FALSE
)
5077 lresult
= DefWindowProcW(hWnd
, msg
, wParam
, lParam
);
5079 TRACE("exit hwnd %p msg %04x (%s) %lx %lx, unicode %d -> %lu\n",
5080 hWnd
, msg
, get_msg_name(msg
), wParam
, lParam
, unicode
, lresult
);
5085 static LRESULT WINAPI
RichEditWndProcW(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
5087 BOOL unicode
= TRUE
;
5089 /* Under Win9x RichEdit20W returns ANSI strings, see the tests. */
5090 if (msg
== WM_GETTEXT
&& (GetVersion() & 0x80000000))
5093 return RichEditWndProc_common(hWnd
, msg
, wParam
, lParam
, unicode
);
5096 static LRESULT WINAPI
RichEditWndProcA(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
5098 return RichEditWndProc_common(hWnd
, msg
, wParam
, lParam
, FALSE
);
5101 /******************************************************************
5102 * RichEditANSIWndProc (RICHED20.10)
5104 LRESULT WINAPI
RichEditANSIWndProc(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
5106 return RichEditWndProcA(hWnd
, msg
, wParam
, lParam
);
5109 /******************************************************************
5110 * RichEdit10ANSIWndProc (RICHED20.9)
5112 LRESULT WINAPI
RichEdit10ANSIWndProc(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
5114 if (msg
== WM_NCCREATE
&& !GetWindowLongPtrW(hWnd
, 0))
5116 CREATESTRUCTW
*pcs
= (CREATESTRUCTW
*)lParam
;
5118 TRACE("WM_NCCREATE: hWnd %p style 0x%08x\n", hWnd
, pcs
->style
);
5119 return create_windowed_editor( hWnd
, pcs
, TRUE
);
5121 return RichEditANSIWndProc(hWnd
, msg
, wParam
, lParam
);
5124 void ME_SendOldNotify(ME_TextEditor
*editor
, int nCode
)
5126 ITextHost_TxNotify(editor
->texthost
, nCode
, NULL
);
5129 /* Fill buffer with srcChars unicode characters from the start cursor.
5131 * buffer: destination buffer
5132 * buflen: length of buffer in characters excluding the NULL terminator.
5133 * start: start of editor text to copy into buffer.
5134 * srcChars: Number of characters to use from the editor text.
5135 * bCRLF: if true, replaces all end of lines with \r\n pairs.
5137 * returns the number of characters written excluding the NULL terminator.
5139 * The written text is always NULL terminated.
5141 int ME_GetTextW(ME_TextEditor
*editor
, WCHAR
*buffer
, int buflen
,
5142 const ME_Cursor
*start
, int srcChars
, BOOL bCRLF
,
5145 ME_Run
*run
, *next_run
;
5146 const WCHAR
*pStart
= buffer
;
5150 /* bCRLF flag is only honored in 2.0 and up. 1.0 must always return text verbatim */
5151 if (editor
->bEmulateVersion10
) bCRLF
= FALSE
;
5154 next_run
= run_next_all_paras( run
);
5156 nLen
= run
->len
- start
->nOffset
;
5157 str
= get_text( run
, start
->nOffset
);
5159 while (srcChars
&& buflen
&& next_run
)
5161 if (bCRLF
&& run
->nFlags
& MERF_ENDPARA
&& ~run
->nFlags
& MERF_ENDCELL
)
5163 if (buflen
== 1) break;
5164 /* FIXME: native fails to reduce srcChars here for WM_GETTEXT or
5165 * EM_GETTEXTEX, however, this is done for copying text which
5166 * also uses this function. */
5167 srcChars
-= min(nLen
, srcChars
);
5173 nLen
= min(nLen
, srcChars
);
5177 nLen
= min(nLen
, buflen
);
5180 CopyMemory(buffer
, str
, sizeof(WCHAR
) * nLen
);
5185 next_run
= run_next_all_paras( run
);
5188 str
= get_text( run
, 0 );
5190 /* append '\r' to the last paragraph. */
5191 if (run
== para_end_run( para_prev( editor_end_para( editor
) ) ) && bEOP
)
5197 return buffer
- pStart
;
5200 static BOOL
ME_RegisterEditorClass(HINSTANCE hInstance
)
5205 wcW
.style
= CS_DBLCLKS
| CS_HREDRAW
| CS_VREDRAW
| CS_GLOBALCLASS
;
5206 wcW
.lpfnWndProc
= RichEditWndProcW
;
5208 wcW
.cbWndExtra
= sizeof(ME_TextEditor
*);
5209 wcW
.hInstance
= NULL
; /* hInstance would register DLL-local class */
5211 wcW
.hCursor
= LoadCursorW(NULL
, (LPWSTR
)IDC_IBEAM
);
5212 wcW
.hbrBackground
= GetStockObject(NULL_BRUSH
);
5213 wcW
.lpszMenuName
= NULL
;
5215 if (is_version_nt())
5217 wcW
.lpszClassName
= RICHEDIT_CLASS20W
;
5218 if (!RegisterClassW(&wcW
)) return FALSE
;
5219 wcW
.lpszClassName
= MSFTEDIT_CLASS
;
5220 if (!RegisterClassW(&wcW
)) return FALSE
;
5224 /* WNDCLASSA/W have the same layout */
5225 wcW
.lpszClassName
= (LPCWSTR
)"RichEdit20W";
5226 if (!RegisterClassA((WNDCLASSA
*)&wcW
)) return FALSE
;
5227 wcW
.lpszClassName
= (LPCWSTR
)"RichEdit50W";
5228 if (!RegisterClassA((WNDCLASSA
*)&wcW
)) return FALSE
;
5231 wcA
.style
= CS_DBLCLKS
| CS_HREDRAW
| CS_VREDRAW
| CS_GLOBALCLASS
;
5232 wcA
.lpfnWndProc
= RichEditWndProcA
;
5234 wcA
.cbWndExtra
= sizeof(ME_TextEditor
*);
5235 wcA
.hInstance
= NULL
; /* hInstance would register DLL-local class */
5237 wcA
.hCursor
= LoadCursorW(NULL
, (LPWSTR
)IDC_IBEAM
);
5238 wcA
.hbrBackground
= GetStockObject(NULL_BRUSH
);
5239 wcA
.lpszMenuName
= NULL
;
5240 wcA
.lpszClassName
= RICHEDIT_CLASS20A
;
5241 if (!RegisterClassA(&wcA
)) return FALSE
;
5242 wcA
.lpszClassName
= "RichEdit50A";
5243 if (!RegisterClassA(&wcA
)) return FALSE
;
5248 static LRESULT WINAPI
REComboWndProc(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
) {
5249 /* FIXME: Not implemented */
5250 TRACE("hWnd %p msg %04x (%s) %08lx %08lx\n",
5251 hWnd
, msg
, get_msg_name(msg
), wParam
, lParam
);
5252 return DefWindowProcW(hWnd
, msg
, wParam
, lParam
);
5255 static LRESULT WINAPI
REListWndProc(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
) {
5256 /* FIXME: Not implemented */
5257 TRACE("hWnd %p msg %04x (%s) %08lx %08lx\n",
5258 hWnd
, msg
, get_msg_name(msg
), wParam
, lParam
);
5259 return DefWindowProcW(hWnd
, msg
, wParam
, lParam
);
5262 /******************************************************************
5263 * REExtendedRegisterClass (RICHED20.8)
5265 * FIXME undocumented
5266 * Need to check for errors and implement controls and callbacks
5268 LRESULT WINAPI
REExtendedRegisterClass(void)
5273 FIXME("semi stub\n");
5277 wcW
.hInstance
= NULL
;
5280 wcW
.hbrBackground
= (HBRUSH
)(COLOR_WINDOW
+1);
5281 wcW
.lpszMenuName
= NULL
;
5283 if (!ME_ListBoxRegistered
)
5285 wcW
.style
= CS_PARENTDC
| CS_DBLCLKS
| CS_GLOBALCLASS
;
5286 wcW
.lpfnWndProc
= REListWndProc
;
5287 wcW
.lpszClassName
= L
"REListBox20W";
5288 if (RegisterClassW(&wcW
)) ME_ListBoxRegistered
= TRUE
;
5291 if (!ME_ComboBoxRegistered
)
5293 wcW
.style
= CS_PARENTDC
| CS_DBLCLKS
| CS_GLOBALCLASS
| CS_VREDRAW
| CS_HREDRAW
;
5294 wcW
.lpfnWndProc
= REComboWndProc
;
5295 wcW
.lpszClassName
= L
"REComboBox20W";
5296 if (RegisterClassW(&wcW
)) ME_ComboBoxRegistered
= TRUE
;
5300 if (ME_ListBoxRegistered
)
5302 if (ME_ComboBoxRegistered
)
5308 static int __cdecl
wchar_comp( const void *key
, const void *elem
)
5310 return *(const WCHAR
*)key
- *(const WCHAR
*)elem
;
5313 /* neutral characters end the url if the next non-neutral character is a space character,
5314 otherwise they are included in the url. */
5315 static BOOL
isurlneutral( WCHAR c
)
5317 /* NB this list is sorted */
5318 static const WCHAR neutral_chars
[] = {'!','\"','\'','(',')',',','-','.',':',';','<','>','?','[',']','{','}'};
5320 /* Some shortcuts */
5321 if (isalnum( c
)) return FALSE
;
5322 if (c
> neutral_chars
[ARRAY_SIZE( neutral_chars
) - 1]) return FALSE
;
5324 return !!bsearch( &c
, neutral_chars
, ARRAY_SIZE( neutral_chars
), sizeof(c
), wchar_comp
);
5328 * This proc takes a selection, and scans it forward in order to select the span
5329 * of a possible URL candidate. A possible URL candidate must start with isalnum
5330 * or one of the following special characters: *|/\+%#@ and must consist entirely
5331 * of the characters allowed to start the URL, plus : (colon) which may occur
5332 * at most once, and not at either end.
5334 static BOOL
ME_FindNextURLCandidate(ME_TextEditor
*editor
,
5335 const ME_Cursor
*start
,
5337 ME_Cursor
*candidate_min
,
5338 ME_Cursor
*candidate_max
)
5340 ME_Cursor cursor
= *start
, neutral_end
, space_end
;
5341 BOOL candidateStarted
= FALSE
, quoted
= FALSE
;
5346 WCHAR
*str
= get_text( cursor
.run
, 0 );
5347 int run_len
= cursor
.run
->len
;
5349 nChars
-= run_len
- cursor
.nOffset
;
5351 /* Find start of candidate */
5352 if (!candidateStarted
)
5354 while (cursor
.nOffset
< run_len
)
5356 c
= str
[cursor
.nOffset
];
5357 if (!iswspace( c
) && !isurlneutral( c
))
5359 *candidate_min
= cursor
;
5360 candidateStarted
= TRUE
;
5361 neutral_end
.para
= NULL
;
5362 space_end
.para
= NULL
;
5366 quoted
= (c
== '<');
5371 /* Find end of candidate */
5372 if (candidateStarted
)
5374 while (cursor
.nOffset
< run_len
)
5376 c
= str
[cursor
.nOffset
];
5379 if (quoted
&& c
!= '\r')
5381 if (!space_end
.para
)
5383 if (neutral_end
.para
)
5384 space_end
= neutral_end
;
5392 else if (isurlneutral( c
))
5394 if (quoted
&& c
== '>')
5396 neutral_end
.para
= NULL
;
5397 space_end
.para
= NULL
;
5400 if (!neutral_end
.para
)
5401 neutral_end
= cursor
;
5404 neutral_end
.para
= NULL
;
5411 if (!cursor_next_run( &cursor
, TRUE
))
5416 if (candidateStarted
)
5419 *candidate_max
= space_end
;
5420 else if (neutral_end
.para
)
5421 *candidate_max
= neutral_end
;
5423 *candidate_max
= cursor
;
5426 *candidate_max
= *candidate_min
= cursor
;
5431 * This proc evaluates the selection and returns TRUE if it can be considered an URL
5433 static BOOL
ME_IsCandidateAnURL(ME_TextEditor
*editor
, const ME_Cursor
*start
, int nChars
)
5435 #define MAX_PREFIX_LEN 9
5436 #define X(str) str, ARRAY_SIZE(str) - 1
5438 const WCHAR text
[MAX_PREFIX_LEN
];
5455 WCHAR bufferW
[MAX_PREFIX_LEN
+ 1];
5458 ME_GetTextW(editor
, bufferW
, MAX_PREFIX_LEN
, start
, nChars
, FALSE
, FALSE
);
5459 for (i
= 0; i
< ARRAY_SIZE(prefixes
); i
++)
5461 if (nChars
< prefixes
[i
].length
) continue;
5462 if (!memcmp(prefixes
[i
].text
, bufferW
, prefixes
[i
].length
* sizeof(WCHAR
)))
5466 #undef MAX_PREFIX_LEN
5470 * This proc walks through the indicated selection and evaluates whether each
5471 * section identified by ME_FindNextURLCandidate and in-between sections have
5472 * their proper CFE_LINK attributes set or unset. If the CFE_LINK attribute is
5473 * not what it is supposed to be, this proc sets or unsets it as appropriate.
5475 * Since this function can cause runs to be split, do not depend on the value
5476 * of the start cursor at the end of the function.
5478 * nChars may be set to INT_MAX to update to the end of the text.
5480 * Returns TRUE if at least one section was modified.
5482 static BOOL
ME_UpdateLinkAttribute(ME_TextEditor
*editor
, ME_Cursor
*start
, int nChars
)
5484 BOOL modified
= FALSE
;
5485 ME_Cursor startCur
= *start
;
5487 if (!editor
->AutoURLDetect_bEnable
) return FALSE
;
5492 ME_Cursor candidateStart
, candidateEnd
;
5494 if (ME_FindNextURLCandidate(editor
, &startCur
, nChars
,
5495 &candidateStart
, &candidateEnd
))
5497 /* Section before candidate is not an URL */
5498 int cMin
= ME_GetCursorOfs(&candidateStart
);
5499 int cMax
= ME_GetCursorOfs(&candidateEnd
);
5501 if (!ME_IsCandidateAnURL(editor
, &candidateStart
, cMax
- cMin
))
5502 candidateStart
= candidateEnd
;
5503 nChars
-= cMax
- ME_GetCursorOfs(&startCur
);
5507 /* No more candidates until end of selection */
5511 if (startCur
.run
!= candidateStart
.run
||
5512 startCur
.nOffset
!= candidateStart
.nOffset
)
5514 /* CFE_LINK effect should be consistently unset */
5515 link
.cbSize
= sizeof(link
);
5516 ME_GetCharFormat(editor
, &startCur
, &candidateStart
, &link
);
5517 if (!(link
.dwMask
& CFM_LINK
) || (link
.dwEffects
& CFE_LINK
))
5519 /* CFE_LINK must be unset from this range */
5520 memset(&link
, 0, sizeof(CHARFORMAT2W
));
5521 link
.cbSize
= sizeof(link
);
5522 link
.dwMask
= CFM_LINK
;
5524 ME_SetCharFormat(editor
, &startCur
, &candidateStart
, &link
);
5525 /* Update candidateEnd since setting character formats may split
5526 * runs, which can cause a cursor to be at an invalid offset within
5528 while (candidateEnd
.nOffset
>= candidateEnd
.run
->len
)
5530 candidateEnd
.nOffset
-= candidateEnd
.run
->len
;
5531 candidateEnd
.run
= run_next_all_paras( candidateEnd
.run
);
5536 if (candidateStart
.run
!= candidateEnd
.run
||
5537 candidateStart
.nOffset
!= candidateEnd
.nOffset
)
5539 /* CFE_LINK effect should be consistently set */
5540 link
.cbSize
= sizeof(link
);
5541 ME_GetCharFormat(editor
, &candidateStart
, &candidateEnd
, &link
);
5542 if (!(link
.dwMask
& CFM_LINK
) || !(link
.dwEffects
& CFE_LINK
))
5544 /* CFE_LINK must be set on this range */
5545 memset(&link
, 0, sizeof(CHARFORMAT2W
));
5546 link
.cbSize
= sizeof(link
);
5547 link
.dwMask
= CFM_LINK
;
5548 link
.dwEffects
= CFE_LINK
;
5549 ME_SetCharFormat(editor
, &candidateStart
, &candidateEnd
, &link
);
5553 startCur
= candidateEnd
;
5554 } while (nChars
> 0);