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_UpdateLinkAttribute(ME_TextEditor
*editor
, ME_Cursor
*start
, int nChars
);
247 HINSTANCE dll_instance
= NULL
;
248 BOOL me_debug
= FALSE
;
250 static ME_TextBuffer
*ME_MakeText(void) {
251 ME_TextBuffer
*buf
= heap_alloc(sizeof(*buf
));
252 ME_DisplayItem
*p1
= ME_MakeDI(diTextStart
);
253 ME_DisplayItem
*p2
= ME_MakeDI(diTextEnd
);
259 p1
->member
.para
.next_para
= p2
;
260 p2
->member
.para
.prev_para
= p1
;
261 p2
->member
.para
.nCharOfs
= 0;
265 buf
->pCharStyle
= NULL
;
270 ME_Paragraph
*editor_first_para( ME_TextEditor
*editor
)
272 return para_next( &editor
->pBuffer
->pFirst
->member
.para
);
275 /* Note, returns the diTextEnd sentinel paragraph */
276 ME_Paragraph
*editor_end_para( ME_TextEditor
*editor
)
278 return &editor
->pBuffer
->pLast
->member
.para
;
281 static BOOL
editor_beep( ME_TextEditor
*editor
, UINT type
)
283 return editor
->props
& TXTBIT_ALLOWBEEP
&& MessageBeep( type
);
286 static LRESULT
ME_StreamInText(ME_TextEditor
*editor
, DWORD dwFormat
, ME_InStream
*stream
, ME_Style
*style
)
289 LRESULT total_bytes_read
= 0;
290 BOOL is_read
= FALSE
;
291 DWORD cp
= CP_ACP
, copy
= 0;
292 char conv_buf
[4 + STREAMIN_BUFFER_SIZE
]; /* up to 4 additional UTF-8 bytes */
294 static const char bom_utf8
[] = {0xEF, 0xBB, 0xBF};
296 TRACE("%08lx %p\n", dwFormat
, stream
);
300 WCHAR wszText
[STREAMIN_BUFFER_SIZE
+1];
304 ME_StreamInFill(stream
);
305 if (stream
->editstream
->dwError
)
309 total_bytes_read
+= stream
->dwSize
;
312 if (!(dwFormat
& SF_UNICODE
))
314 char * buf
= stream
->buffer
;
315 DWORD size
= stream
->dwSize
, end
;
320 if (stream
->dwSize
>= 3 && !memcmp(stream
->buffer
, bom_utf8
, 3))
332 memcpy(conv_buf
+ copy
, buf
, size
);
337 while ((buf
[end
-1] & 0xC0) == 0x80)
340 --total_bytes_read
; /* strange, but seems to match windows */
342 if (buf
[end
-1] & 0x80)
345 if ((buf
[end
-1] & 0xE0) == 0xC0)
347 if ((buf
[end
-1] & 0xF0) == 0xE0)
349 if ((buf
[end
-1] & 0xF8) == 0xF0)
352 if (size
- end
>= need
)
354 /* we have enough bytes for this sequence */
359 /* need more bytes, so don't transcode this sequence */
367 nWideChars
= MultiByteToWideChar(cp
, 0, buf
, end
, wszText
, STREAMIN_BUFFER_SIZE
);
374 memcpy(conv_buf
, buf
+ end
, size
- end
);
381 nWideChars
= stream
->dwSize
>> 1;
382 pText
= (WCHAR
*)stream
->buffer
;
385 ME_InsertTextFromCursor(editor
, 0, pText
, nWideChars
, style
);
386 if (stream
->dwSize
== 0)
390 return total_bytes_read
;
393 static void ME_ApplyBorderProperties(RTF_Info
*info
,
394 ME_BorderRect
*borderRect
,
395 RTFBorder
*borderDef
)
398 ME_Border
*pBorders
[] = {&borderRect
->top
,
402 for (i
= 0; i
< 4; i
++)
404 RTFColor
*colorDef
= info
->colorList
;
405 pBorders
[i
]->width
= borderDef
[i
].width
;
406 colorNum
= borderDef
[i
].color
;
407 while (colorDef
&& colorDef
->rtfCNum
!= colorNum
)
408 colorDef
= colorDef
->rtfNextColor
;
410 pBorders
[i
]->colorRef
= RGB(
411 colorDef
->rtfCRed
>= 0 ? colorDef
->rtfCRed
: 0,
412 colorDef
->rtfCGreen
>= 0 ? colorDef
->rtfCGreen
: 0,
413 colorDef
->rtfCBlue
>= 0 ? colorDef
->rtfCBlue
: 0);
415 pBorders
[i
]->colorRef
= RGB(0, 0, 0);
419 void ME_RTFCharAttrHook(RTF_Info
*info
)
422 fmt
.cbSize
= sizeof(fmt
);
426 switch(info
->rtfMinor
)
429 /* FIXME add more flags once they're implemented */
430 fmt
.dwMask
= CFM_BOLD
| CFM_ITALIC
| CFM_UNDERLINE
| CFM_UNDERLINETYPE
| CFM_STRIKEOUT
|
431 CFM_COLOR
| CFM_BACKCOLOR
| CFM_SIZE
| CFM_WEIGHT
;
432 fmt
.dwEffects
= CFE_AUTOCOLOR
| CFE_AUTOBACKCOLOR
;
433 fmt
.yHeight
= 12*20; /* 12pt */
434 fmt
.wWeight
= FW_NORMAL
;
435 fmt
.bUnderlineType
= CFU_UNDERLINE
;
438 fmt
.dwMask
= CFM_BOLD
| CFM_WEIGHT
;
439 fmt
.dwEffects
= info
->rtfParam
? CFE_BOLD
: 0;
440 fmt
.wWeight
= info
->rtfParam
? FW_BOLD
: FW_NORMAL
;
443 fmt
.dwMask
= CFM_ITALIC
;
444 fmt
.dwEffects
= info
->rtfParam
? fmt
.dwMask
: 0;
447 fmt
.dwMask
= CFM_UNDERLINETYPE
| CFM_UNDERLINE
;
448 fmt
.bUnderlineType
= CFU_UNDERLINE
;
449 fmt
.dwEffects
= info
->rtfParam
? CFE_UNDERLINE
: 0;
451 case rtfDotUnderline
:
452 fmt
.dwMask
= CFM_UNDERLINETYPE
| CFM_UNDERLINE
;
453 fmt
.bUnderlineType
= CFU_UNDERLINEDOTTED
;
454 fmt
.dwEffects
= info
->rtfParam
? CFE_UNDERLINE
: 0;
457 fmt
.dwMask
= CFM_UNDERLINETYPE
| CFM_UNDERLINE
;
458 fmt
.bUnderlineType
= CFU_UNDERLINEDOUBLE
;
459 fmt
.dwEffects
= info
->rtfParam
? CFE_UNDERLINE
: 0;
461 case rtfWordUnderline
:
462 fmt
.dwMask
= CFM_UNDERLINETYPE
| CFM_UNDERLINE
;
463 fmt
.bUnderlineType
= CFU_UNDERLINEWORD
;
464 fmt
.dwEffects
= info
->rtfParam
? CFE_UNDERLINE
: 0;
467 fmt
.dwMask
= CFM_UNDERLINE
;
471 fmt
.dwMask
= CFM_STRIKEOUT
;
472 fmt
.dwEffects
= info
->rtfParam
? fmt
.dwMask
: 0;
476 case rtfSubScrShrink
:
477 case rtfSuperScrShrink
:
479 fmt
.dwMask
= CFM_SUBSCRIPT
|CFM_SUPERSCRIPT
;
480 if (info
->rtfMinor
== rtfSubScrShrink
) fmt
.dwEffects
= CFE_SUBSCRIPT
;
481 if (info
->rtfMinor
== rtfSuperScrShrink
) fmt
.dwEffects
= CFE_SUPERSCRIPT
;
482 if (info
->rtfMinor
== rtfNoSuperSub
) fmt
.dwEffects
= 0;
485 fmt
.dwMask
= CFM_HIDDEN
;
486 fmt
.dwEffects
= info
->rtfParam
? fmt
.dwMask
: 0;
489 fmt
.dwMask
= CFM_BACKCOLOR
;
491 if (info
->rtfParam
== 0)
492 fmt
.dwEffects
= CFE_AUTOBACKCOLOR
;
493 else if (info
->rtfParam
!= rtfNoParam
)
495 RTFColor
*c
= RTFGetColor(info
, info
->rtfParam
);
496 if (c
&& c
->rtfCBlue
>= 0)
497 fmt
.crBackColor
= (c
->rtfCBlue
<<16)|(c
->rtfCGreen
<<8)|(c
->rtfCRed
);
499 fmt
.dwEffects
= CFE_AUTOBACKCOLOR
;
503 fmt
.dwMask
= CFM_COLOR
;
505 if (info
->rtfParam
== 0)
506 fmt
.dwEffects
= CFE_AUTOCOLOR
;
507 else if (info
->rtfParam
!= rtfNoParam
)
509 RTFColor
*c
= RTFGetColor(info
, info
->rtfParam
);
510 if (c
&& c
->rtfCBlue
>= 0)
511 fmt
.crTextColor
= (c
->rtfCBlue
<<16)|(c
->rtfCGreen
<<8)|(c
->rtfCRed
);
513 fmt
.dwEffects
= CFE_AUTOCOLOR
;
518 if (info
->rtfParam
!= rtfNoParam
)
520 RTFFont
*f
= RTFGetFont(info
, info
->rtfParam
);
523 MultiByteToWideChar(CP_ACP
, 0, f
->rtfFName
, -1, fmt
.szFaceName
, ARRAY_SIZE(fmt
.szFaceName
));
524 fmt
.szFaceName
[ARRAY_SIZE(fmt
.szFaceName
)-1] = '\0';
525 fmt
.bCharSet
= f
->rtfFCharSet
;
526 fmt
.dwMask
= CFM_FACE
| CFM_CHARSET
;
527 fmt
.bPitchAndFamily
= f
->rtfFPitch
| (f
->rtfFFamily
<< 4);
532 fmt
.dwMask
= CFM_SIZE
;
533 if (info
->rtfParam
!= rtfNoParam
)
534 fmt
.yHeight
= info
->rtfParam
*10;
539 RTFFlushOutputBuffer(info
);
540 /* FIXME too slow ? how come ? */
541 style2
= ME_ApplyStyle(info
->editor
, info
->style
, &fmt
);
542 ME_ReleaseStyle(info
->style
);
543 info
->style
= style2
;
544 info
->styleChanged
= TRUE
;
548 /* FIXME this function doesn't get any information about context of the RTF tag, which is very bad,
549 the same tags mean different things in different contexts */
550 void ME_RTFParAttrHook(RTF_Info
*info
)
552 switch(info
->rtfMinor
)
554 case rtfParDef
: /* restores default paragraph attributes */
555 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
556 info
->borderType
= RTFBorderParaLeft
;
557 else /* v1.0 - 3.0 */
558 info
->borderType
= RTFBorderParaTop
;
559 info
->fmt
.dwMask
= PFM_ALIGNMENT
| PFM_BORDER
| PFM_LINESPACING
| PFM_TABSTOPS
|
560 PFM_OFFSET
| PFM_RIGHTINDENT
| PFM_SPACEAFTER
| PFM_SPACEBEFORE
|
561 PFM_STARTINDENT
| PFM_RTLPARA
| PFM_NUMBERING
| PFM_NUMBERINGSTART
|
562 PFM_NUMBERINGSTYLE
| PFM_NUMBERINGTAB
;
564 info
->fmt
.wAlignment
= PFA_LEFT
;
565 info
->fmt
.cTabCount
= 0;
566 info
->fmt
.dxOffset
= info
->fmt
.dxStartIndent
= info
->fmt
.dxRightIndent
= 0;
567 info
->fmt
.wBorderWidth
= info
->fmt
.wBorders
= 0;
568 info
->fmt
.wBorderSpace
= 0;
569 info
->fmt
.bLineSpacingRule
= 0;
570 info
->fmt
.dySpaceBefore
= info
->fmt
.dySpaceAfter
= 0;
571 info
->fmt
.dyLineSpacing
= 0;
572 info
->fmt
.wEffects
&= ~PFE_RTLPARA
;
573 info
->fmt
.wNumbering
= 0;
574 info
->fmt
.wNumberingStart
= 0;
575 info
->fmt
.wNumberingStyle
= 0;
576 info
->fmt
.wNumberingTab
= 0;
578 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
580 if (info
->tableDef
&& info
->tableDef
->row_start
&&
581 info
->tableDef
->row_start
->nFlags
& MEPF_ROWEND
)
585 /* We are just after a table row. */
586 RTFFlushOutputBuffer(info
);
587 cursor
= info
->editor
->pCursors
[0];
589 if (para
== para_next( info
->tableDef
->row_start
)
590 && !cursor
.nOffset
&& !cursor
.run
->nCharOfs
)
592 /* Since the table row end, no text has been inserted, and the \intbl
593 * control word has not be used. We can confirm that we are not in a
596 info
->tableDef
->row_start
= NULL
;
597 info
->canInheritInTbl
= FALSE
;
601 else /* v1.0 - v3.0 */
603 info
->fmt
.dwMask
|= PFM_TABLE
;
604 info
->fmt
.wEffects
&= ~PFE_TABLE
;
608 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
610 while (info
->rtfParam
> info
->nestingLevel
)
612 RTFTable
*tableDef
= heap_alloc_zero(sizeof(*tableDef
));
613 tableDef
->parent
= info
->tableDef
;
614 info
->tableDef
= tableDef
;
616 RTFFlushOutputBuffer(info
);
617 if (tableDef
->row_start
&& tableDef
->row_start
->nFlags
& MEPF_ROWEND
)
619 ME_Paragraph
*para
= para_next( tableDef
->row_start
);
620 tableDef
->row_start
= table_insert_row_start_at_para( info
->editor
, para
);
625 cursor
= info
->editor
->pCursors
[0];
626 if (cursor
.nOffset
|| cursor
.run
->nCharOfs
)
627 ME_InsertTextFromCursor(info
->editor
, 0, L
"\r", 1, info
->style
);
628 tableDef
->row_start
= table_insert_row_start( info
->editor
, info
->editor
->pCursors
);
631 info
->nestingLevel
++;
633 info
->canInheritInTbl
= FALSE
;
638 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
640 if (info
->nestingLevel
< 1)
646 info
->tableDef
= heap_alloc_zero(sizeof(*info
->tableDef
));
647 tableDef
= info
->tableDef
;
648 RTFFlushOutputBuffer(info
);
649 if (tableDef
->row_start
&& tableDef
->row_start
->nFlags
& MEPF_ROWEND
)
650 para
= para_next( tableDef
->row_start
);
652 para
= info
->editor
->pCursors
[0].para
;
654 tableDef
->row_start
= table_insert_row_start_at_para( info
->editor
, para
);
656 info
->nestingLevel
= 1;
657 info
->canInheritInTbl
= TRUE
;
660 } else { /* v1.0 - v3.0 */
661 info
->fmt
.dwMask
|= PFM_TABLE
;
662 info
->fmt
.wEffects
|= PFE_TABLE
;
668 if ((info
->fmt
.dwMask
& (PFM_STARTINDENT
| PFM_OFFSET
)) != (PFM_STARTINDENT
| PFM_OFFSET
))
671 fmt
.cbSize
= sizeof(fmt
);
672 editor_get_selection_para_fmt( info
->editor
, &fmt
);
673 info
->fmt
.dwMask
|= PFM_STARTINDENT
| PFM_OFFSET
;
674 info
->fmt
.dxStartIndent
= fmt
.dxStartIndent
;
675 info
->fmt
.dxOffset
= fmt
.dxOffset
;
677 if (info
->rtfMinor
== rtfFirstIndent
)
679 info
->fmt
.dxStartIndent
+= info
->fmt
.dxOffset
+ info
->rtfParam
;
680 info
->fmt
.dxOffset
= -info
->rtfParam
;
683 info
->fmt
.dxStartIndent
= info
->rtfParam
- info
->fmt
.dxOffset
;
686 info
->fmt
.dwMask
|= PFM_RIGHTINDENT
;
687 info
->fmt
.dxRightIndent
= info
->rtfParam
;
691 info
->fmt
.dwMask
|= PFM_ALIGNMENT
;
692 info
->fmt
.wAlignment
= PFA_LEFT
;
695 info
->fmt
.dwMask
|= PFM_ALIGNMENT
;
696 info
->fmt
.wAlignment
= PFA_RIGHT
;
699 info
->fmt
.dwMask
|= PFM_ALIGNMENT
;
700 info
->fmt
.wAlignment
= PFA_CENTER
;
703 if (!(info
->fmt
.dwMask
& PFM_TABSTOPS
))
706 fmt
.cbSize
= sizeof(fmt
);
707 editor_get_selection_para_fmt( info
->editor
, &fmt
);
708 memcpy(info
->fmt
.rgxTabs
, fmt
.rgxTabs
,
709 fmt
.cTabCount
* sizeof(fmt
.rgxTabs
[0]));
710 info
->fmt
.cTabCount
= fmt
.cTabCount
;
711 info
->fmt
.dwMask
|= PFM_TABSTOPS
;
713 if (info
->fmt
.cTabCount
< MAX_TAB_STOPS
&& info
->rtfParam
< 0x1000000)
714 info
->fmt
.rgxTabs
[info
->fmt
.cTabCount
++] = info
->rtfParam
;
717 info
->fmt
.dwMask
|= PFM_KEEP
;
718 info
->fmt
.wEffects
|= PFE_KEEP
;
720 case rtfNoWidowControl
:
721 info
->fmt
.dwMask
|= PFM_NOWIDOWCONTROL
;
722 info
->fmt
.wEffects
|= PFE_NOWIDOWCONTROL
;
725 info
->fmt
.dwMask
|= PFM_KEEPNEXT
;
726 info
->fmt
.wEffects
|= PFE_KEEPNEXT
;
729 info
->fmt
.dwMask
|= PFM_SPACEAFTER
;
730 info
->fmt
.dySpaceAfter
= info
->rtfParam
;
733 info
->fmt
.dwMask
|= PFM_SPACEBEFORE
;
734 info
->fmt
.dySpaceBefore
= info
->rtfParam
;
736 case rtfSpaceBetween
:
737 info
->fmt
.dwMask
|= PFM_LINESPACING
;
738 if ((int)info
->rtfParam
> 0)
740 info
->fmt
.dyLineSpacing
= info
->rtfParam
;
741 info
->fmt
.bLineSpacingRule
= 3;
745 info
->fmt
.dyLineSpacing
= info
->rtfParam
;
746 info
->fmt
.bLineSpacingRule
= 4;
749 case rtfSpaceMultiply
:
750 info
->fmt
.dwMask
|= PFM_LINESPACING
;
751 info
->fmt
.dyLineSpacing
= info
->rtfParam
* 20;
752 info
->fmt
.bLineSpacingRule
= 5;
755 info
->fmt
.dwMask
|= PFM_NUMBERING
;
756 info
->fmt
.wNumbering
= PFN_BULLET
;
759 info
->fmt
.dwMask
|= PFM_NUMBERING
;
760 info
->fmt
.wNumbering
= 2; /* FIXME: MSDN says it's not used ?? */
763 info
->borderType
= RTFBorderParaLeft
;
764 info
->fmt
.wBorders
|= 1;
765 info
->fmt
.dwMask
|= PFM_BORDER
;
768 info
->borderType
= RTFBorderParaRight
;
769 info
->fmt
.wBorders
|= 2;
770 info
->fmt
.dwMask
|= PFM_BORDER
;
773 info
->borderType
= RTFBorderParaTop
;
774 info
->fmt
.wBorders
|= 4;
775 info
->fmt
.dwMask
|= PFM_BORDER
;
777 case rtfBorderBottom
:
778 info
->borderType
= RTFBorderParaBottom
;
779 info
->fmt
.wBorders
|= 8;
780 info
->fmt
.dwMask
|= PFM_BORDER
;
782 case rtfBorderSingle
:
783 info
->fmt
.wBorders
&= ~0x700;
784 info
->fmt
.wBorders
|= 1 << 8;
785 info
->fmt
.dwMask
|= PFM_BORDER
;
788 info
->fmt
.wBorders
&= ~0x700;
789 info
->fmt
.wBorders
|= 2 << 8;
790 info
->fmt
.dwMask
|= PFM_BORDER
;
792 case rtfBorderShadow
:
793 info
->fmt
.wBorders
&= ~0x700;
794 info
->fmt
.wBorders
|= 10 << 8;
795 info
->fmt
.dwMask
|= PFM_BORDER
;
797 case rtfBorderDouble
:
798 info
->fmt
.wBorders
&= ~0x700;
799 info
->fmt
.wBorders
|= 7 << 8;
800 info
->fmt
.dwMask
|= PFM_BORDER
;
803 info
->fmt
.wBorders
&= ~0x700;
804 info
->fmt
.wBorders
|= 11 << 8;
805 info
->fmt
.dwMask
|= PFM_BORDER
;
809 int borderSide
= info
->borderType
& RTFBorderSideMask
;
810 RTFTable
*tableDef
= info
->tableDef
;
811 if ((info
->borderType
& RTFBorderTypeMask
) == RTFBorderTypeCell
)
814 if (!tableDef
|| tableDef
->numCellsDefined
>= MAX_TABLE_CELLS
)
816 border
= &tableDef
->cells
[tableDef
->numCellsDefined
].border
[borderSide
];
817 border
->width
= info
->rtfParam
;
820 info
->fmt
.wBorderWidth
= info
->rtfParam
;
821 info
->fmt
.dwMask
|= PFM_BORDER
;
825 info
->fmt
.wBorderSpace
= info
->rtfParam
;
826 info
->fmt
.dwMask
|= PFM_BORDER
;
830 RTFTable
*tableDef
= info
->tableDef
;
831 int borderSide
= info
->borderType
& RTFBorderSideMask
;
832 int borderType
= info
->borderType
& RTFBorderTypeMask
;
835 case RTFBorderTypePara
:
836 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
838 /* v1.0 - 3.0 treat paragraph and row borders the same. */
839 case RTFBorderTypeRow
:
841 tableDef
->border
[borderSide
].color
= info
->rtfParam
;
844 case RTFBorderTypeCell
:
845 if (tableDef
&& tableDef
->numCellsDefined
< MAX_TABLE_CELLS
) {
846 tableDef
->cells
[tableDef
->numCellsDefined
].border
[borderSide
].color
= info
->rtfParam
;
853 info
->fmt
.dwMask
|= PFM_RTLPARA
;
854 info
->fmt
.wEffects
|= PFE_RTLPARA
;
857 info
->fmt
.dwMask
|= PFM_RTLPARA
;
858 info
->fmt
.wEffects
&= ~PFE_RTLPARA
;
863 void ME_RTFTblAttrHook(RTF_Info
*info
)
865 switch (info
->rtfMinor
)
869 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
870 info
->borderType
= 0; /* Not sure */
871 else /* v1.0 - 3.0 */
872 info
->borderType
= RTFBorderRowTop
;
873 if (!info
->tableDef
) {
874 info
->tableDef
= ME_MakeTableDef(info
->editor
);
876 ME_InitTableDef(info
->editor
, info
->tableDef
);
885 info
->tableDef
= ME_MakeTableDef(info
->editor
);
887 cellNum
= info
->tableDef
->numCellsDefined
;
888 if (cellNum
>= MAX_TABLE_CELLS
)
890 info
->tableDef
->cells
[cellNum
].rightBoundary
= info
->rtfParam
;
891 if (cellNum
< MAX_TAB_STOPS
)
893 /* Tab stops were used to store cell positions before v4.1 but v4.1
894 * still seems to set the tabstops without using them. */
895 PARAFORMAT2
*fmt
= &info
->editor
->pCursors
[0].para
->fmt
;
896 fmt
->rgxTabs
[cellNum
] &= ~0x00FFFFFF;
897 fmt
->rgxTabs
[cellNum
] |= 0x00FFFFFF & info
->rtfParam
;
899 info
->tableDef
->numCellsDefined
++;
903 info
->borderType
= RTFBorderRowTop
;
906 info
->borderType
= RTFBorderRowLeft
;
908 case rtfRowBordBottom
:
909 info
->borderType
= RTFBorderRowBottom
;
911 case rtfRowBordRight
:
912 info
->borderType
= RTFBorderRowRight
;
915 info
->borderType
= RTFBorderCellTop
;
917 case rtfCellBordLeft
:
918 info
->borderType
= RTFBorderCellLeft
;
920 case rtfCellBordBottom
:
921 info
->borderType
= RTFBorderCellBottom
;
923 case rtfCellBordRight
:
924 info
->borderType
= RTFBorderCellRight
;
928 info
->tableDef
->gapH
= info
->rtfParam
;
932 info
->tableDef
->leftEdge
= info
->rtfParam
;
937 void ME_RTFSpecialCharHook(RTF_Info
*info
)
939 RTFTable
*tableDef
= info
->tableDef
;
940 switch (info
->rtfMinor
)
943 if (info
->editor
->bEmulateVersion10
) /* v1.0 - v3.0 */
945 /* else fall through since v4.1 treats rtfNestCell and rtfCell the same */
949 RTFFlushOutputBuffer(info
);
950 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
952 if (tableDef
->row_start
)
954 if (!info
->nestingLevel
&& tableDef
->row_start
->nFlags
& MEPF_ROWEND
)
956 ME_Paragraph
*para
= para_next( tableDef
->row_start
);
957 tableDef
->row_start
= table_insert_row_start_at_para( info
->editor
, para
);
958 info
->nestingLevel
= 1;
960 table_insert_cell( info
->editor
, info
->editor
->pCursors
);
963 else /* v1.0 - v3.0 */
965 ME_Paragraph
*para
= info
->editor
->pCursors
[0].para
;
967 if (para_in_table( para
) && tableDef
->numCellsInserted
< tableDef
->numCellsDefined
)
970 ME_InsertTextFromCursor(info
->editor
, 0, &tab
, 1, info
->style
);
971 tableDef
->numCellsInserted
++;
976 if (info
->editor
->bEmulateVersion10
) /* v1.0 - v3.0 */
978 /* else fall through since v4.1 treats rtfNestRow and rtfRow the same */
988 RTFFlushOutputBuffer(info
);
989 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
991 if (!tableDef
->row_start
) break;
992 if (!info
->nestingLevel
&& tableDef
->row_start
->nFlags
& MEPF_ROWEND
)
994 para
= para_next( tableDef
->row_start
);
995 tableDef
->row_start
= table_insert_row_start_at_para( info
->editor
, para
);
996 info
->nestingLevel
++;
998 para
= tableDef
->row_start
;
999 cell
= table_row_first_cell( para
);
1000 assert( cell
&& !cell_prev( cell
) );
1001 if (tableDef
->numCellsDefined
< 1)
1003 /* 2000 twips appears to be the cell size that native richedit uses
1004 * when no cell sizes are specified. */
1005 const int default_size
= 2000;
1006 int right_boundary
= default_size
;
1007 cell
->nRightBoundary
= right_boundary
;
1008 while (cell_next( cell
))
1010 cell
= cell_next( cell
);
1011 right_boundary
+= default_size
;
1012 cell
->nRightBoundary
= right_boundary
;
1014 para
= table_insert_cell( info
->editor
, info
->editor
->pCursors
);
1015 cell
= para_cell( para
);
1016 cell
->nRightBoundary
= right_boundary
;
1020 for (i
= 0; i
< tableDef
->numCellsDefined
; i
++)
1022 RTFCell
*cellDef
= &tableDef
->cells
[i
];
1023 cell
->nRightBoundary
= cellDef
->rightBoundary
;
1024 ME_ApplyBorderProperties( info
, &cell
->border
, cellDef
->border
);
1025 cell
= cell_next( cell
);
1028 para
= table_insert_cell( info
->editor
, info
->editor
->pCursors
);
1029 cell
= para_cell( para
);
1032 /* Cell for table row delimiter is empty */
1033 cell
->nRightBoundary
= tableDef
->cells
[i
- 1].rightBoundary
;
1036 run
= para_first_run( cell_first_para( cell
) );
1037 if (info
->editor
->pCursors
[0].run
!= run
|| info
->editor
->pCursors
[0].nOffset
)
1040 /* Delete inserted cells that aren't defined. */
1041 info
->editor
->pCursors
[1].run
= run
;
1042 info
->editor
->pCursors
[1].para
= run
->para
;
1043 info
->editor
->pCursors
[1].nOffset
= 0;
1044 nOfs
= ME_GetCursorOfs(&info
->editor
->pCursors
[1]);
1045 nChars
= ME_GetCursorOfs(&info
->editor
->pCursors
[0]) - nOfs
;
1046 ME_InternalDeleteText(info
->editor
, &info
->editor
->pCursors
[1],
1050 para
= table_insert_row_end( info
->editor
, info
->editor
->pCursors
);
1051 para
->fmt
.dxOffset
= abs(info
->tableDef
->gapH
);
1052 para
->fmt
.dxStartIndent
= info
->tableDef
->leftEdge
;
1053 ME_ApplyBorderProperties( info
, ¶
->border
, tableDef
->border
);
1054 info
->nestingLevel
--;
1055 if (!info
->nestingLevel
)
1057 if (info
->canInheritInTbl
) tableDef
->row_start
= para
;
1060 while (info
->tableDef
)
1062 tableDef
= info
->tableDef
;
1063 info
->tableDef
= tableDef
->parent
;
1064 heap_free(tableDef
);
1070 info
->tableDef
= tableDef
->parent
;
1071 heap_free(tableDef
);
1074 else /* v1.0 - v3.0 */
1076 para
= info
->editor
->pCursors
[0].para
;
1077 para
->fmt
.dxOffset
= info
->tableDef
->gapH
;
1078 para
->fmt
.dxStartIndent
= info
->tableDef
->leftEdge
;
1080 ME_ApplyBorderProperties( info
, ¶
->border
, tableDef
->border
);
1081 while (tableDef
->numCellsInserted
< tableDef
->numCellsDefined
)
1084 ME_InsertTextFromCursor(info
->editor
, 0, &tab
, 1, info
->style
);
1085 tableDef
->numCellsInserted
++;
1087 para
->fmt
.cTabCount
= min(tableDef
->numCellsDefined
, MAX_TAB_STOPS
);
1088 if (!tableDef
->numCellsDefined
) para
->fmt
.wEffects
&= ~PFE_TABLE
;
1089 ME_InsertTextFromCursor(info
->editor
, 0, L
"\r", 1, info
->style
);
1090 tableDef
->numCellsInserted
= 0;
1096 if (info
->editor
->bEmulateVersion10
) /* v1.0 - 3.0 */
1100 RTFFlushOutputBuffer(info
);
1101 para
= info
->editor
->pCursors
[0].para
;
1102 if (para_in_table( para
))
1104 /* rtfPar is treated like a space within a table. */
1105 info
->rtfClass
= rtfText
;
1106 info
->rtfMajor
= ' ';
1108 else if (info
->rtfMinor
== rtfPar
&& tableDef
)
1109 tableDef
->numCellsInserted
= 0;
1115 static HRESULT
insert_static_object(ME_TextEditor
*editor
, HENHMETAFILE hemf
, HBITMAP hbmp
,
1118 LPOLEOBJECT lpObject
= NULL
;
1119 LPSTORAGE lpStorage
= NULL
;
1120 LPOLECLIENTSITE lpClientSite
= NULL
;
1121 LPDATAOBJECT lpDataObject
= NULL
;
1122 LPOLECACHE lpOleCache
= NULL
;
1126 HRESULT hr
= E_FAIL
;
1131 stgm
.tymed
= TYMED_ENHMF
;
1132 stgm
.u
.hEnhMetaFile
= hemf
;
1133 fm
.cfFormat
= CF_ENHMETAFILE
;
1137 stgm
.tymed
= TYMED_GDI
;
1138 stgm
.u
.hBitmap
= hbmp
;
1139 fm
.cfFormat
= CF_BITMAP
;
1143 stgm
.pUnkForRelease
= NULL
;
1146 fm
.dwAspect
= DVASPECT_CONTENT
;
1148 fm
.tymed
= stgm
.tymed
;
1150 if (OleCreateDefaultHandler(&CLSID_NULL
, NULL
, &IID_IOleObject
, (void**)&lpObject
) == S_OK
&&
1151 IRichEditOle_GetClientSite(editor
->richole
, &lpClientSite
) == S_OK
&&
1152 IOleObject_SetClientSite(lpObject
, lpClientSite
) == S_OK
&&
1153 IOleObject_GetUserClassID(lpObject
, &clsid
) == S_OK
&&
1154 IOleObject_QueryInterface(lpObject
, &IID_IOleCache
, (void**)&lpOleCache
) == S_OK
&&
1155 IOleCache_Cache(lpOleCache
, &fm
, 0, &conn
) == S_OK
&&
1156 IOleObject_QueryInterface(lpObject
, &IID_IDataObject
, (void**)&lpDataObject
) == S_OK
&&
1157 IDataObject_SetData(lpDataObject
, &fm
, &stgm
, TRUE
) == S_OK
)
1161 reobject
.cbStruct
= sizeof(reobject
);
1162 reobject
.cp
= REO_CP_SELECTION
;
1163 reobject
.clsid
= clsid
;
1164 reobject
.poleobj
= lpObject
;
1165 reobject
.pstg
= lpStorage
;
1166 reobject
.polesite
= lpClientSite
;
1167 /* convert from twips to .01 mm */
1168 reobject
.sizel
.cx
= MulDiv(sz
->cx
, 254, 144);
1169 reobject
.sizel
.cy
= MulDiv(sz
->cy
, 254, 144);
1170 reobject
.dvaspect
= DVASPECT_CONTENT
;
1171 reobject
.dwFlags
= 0; /* FIXME */
1172 reobject
.dwUser
= 0;
1174 hr
= editor_insert_oleobj(editor
, &reobject
);
1177 if (lpObject
) IOleObject_Release(lpObject
);
1178 if (lpClientSite
) IOleClientSite_Release(lpClientSite
);
1179 if (lpStorage
) IStorage_Release(lpStorage
);
1180 if (lpDataObject
) IDataObject_Release(lpDataObject
);
1181 if (lpOleCache
) IOleCache_Release(lpOleCache
);
1186 static void ME_RTFReadShpPictGroup( RTF_Info
*info
)
1194 if (info
->rtfClass
== rtfEOF
) return;
1195 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1197 if (--level
== 0) break;
1199 else if (RTFCheckCM( info
, rtfGroup
, rtfBeginGroup
))
1205 RTFRouteToken( info
);
1206 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1211 RTFRouteToken( info
); /* feed "}" back to router */
1215 static DWORD
read_hex_data( RTF_Info
*info
, BYTE
**out
)
1217 DWORD read
= 0, size
= 1024;
1223 if (info
->rtfClass
!= rtfText
)
1225 ERR("Called with incorrect token\n");
1229 buf
= HeapAlloc( GetProcessHeap(), 0, size
);
1232 val
= info
->rtfMajor
;
1233 for (flip
= TRUE
;; flip
= !flip
)
1235 RTFGetToken( info
);
1236 if (info
->rtfClass
== rtfEOF
)
1238 HeapFree( GetProcessHeap(), 0, buf
);
1241 if (info
->rtfClass
!= rtfText
) break;
1247 buf
= HeapReAlloc( GetProcessHeap(), 0, buf
, size
);
1250 buf
[read
++] = RTFCharToHex(val
) * 16 + RTFCharToHex(info
->rtfMajor
);
1253 val
= info
->rtfMajor
;
1255 if (flip
) FIXME("wrong hex string\n");
1261 static void ME_RTFReadPictGroup(RTF_Info
*info
)
1264 BYTE
*buffer
= NULL
;
1269 enum gfxkind
{gfx_unknown
= 0, gfx_enhmetafile
, gfx_metafile
, gfx_dib
} gfx
= gfx_unknown
;
1277 RTFGetToken( info
);
1279 if (info
->rtfClass
== rtfText
)
1284 size
= read_hex_data( info
, &buffer
);
1288 RTFSkipGroup( info
);
1290 } /* We potentially have a new token so fall through. */
1292 if (info
->rtfClass
== rtfEOF
) return;
1294 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1296 if (--level
== 0) break;
1299 if (RTFCheckCM( info
, rtfGroup
, rtfBeginGroup
))
1304 if (!RTFCheckCM( info
, rtfControl
, rtfPictAttr
))
1306 RTFRouteToken( info
);
1307 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1312 if (RTFCheckMM( info
, rtfPictAttr
, rtfWinMetafile
))
1314 mfp
.mm
= info
->rtfParam
;
1317 else if (RTFCheckMM( info
, rtfPictAttr
, rtfDevIndBitmap
))
1319 if (info
->rtfParam
!= 0) FIXME("dibitmap should be 0 (%d)\n", info
->rtfParam
);
1322 else if (RTFCheckMM( info
, rtfPictAttr
, rtfEmfBlip
))
1323 gfx
= gfx_enhmetafile
;
1324 else if (RTFCheckMM( info
, rtfPictAttr
, rtfPicWid
))
1325 mfp
.xExt
= info
->rtfParam
;
1326 else if (RTFCheckMM( info
, rtfPictAttr
, rtfPicHt
))
1327 mfp
.yExt
= info
->rtfParam
;
1328 else if (RTFCheckMM( info
, rtfPictAttr
, rtfPicGoalWid
))
1329 sz
.cx
= info
->rtfParam
;
1330 else if (RTFCheckMM( info
, rtfPictAttr
, rtfPicGoalHt
))
1331 sz
.cy
= info
->rtfParam
;
1333 FIXME("Non supported attribute: %d %d %d\n", info
->rtfClass
, info
->rtfMajor
, info
->rtfMinor
);
1340 case gfx_enhmetafile
:
1341 if ((hemf
= SetEnhMetaFileBits( size
, buffer
)))
1342 insert_static_object( info
->editor
, hemf
, NULL
, &sz
);
1345 if ((hemf
= SetWinMetaFileBits( size
, buffer
, NULL
, &mfp
)))
1346 insert_static_object( info
->editor
, hemf
, NULL
, &sz
);
1350 BITMAPINFO
*bi
= (BITMAPINFO
*)buffer
;
1352 unsigned nc
= bi
->bmiHeader
.biClrUsed
;
1354 /* not quite right, especially for bitfields type of compression */
1355 if (!nc
&& bi
->bmiHeader
.biBitCount
<= 8)
1356 nc
= 1 << bi
->bmiHeader
.biBitCount
;
1357 if ((hbmp
= CreateDIBitmap( hdc
, &bi
->bmiHeader
,
1358 CBM_INIT
, (char*)(bi
+ 1) + nc
* sizeof(RGBQUAD
),
1359 bi
, DIB_RGB_COLORS
)) )
1360 insert_static_object( info
->editor
, NULL
, hbmp
, &sz
);
1361 ReleaseDC( 0, hdc
);
1368 HeapFree( GetProcessHeap(), 0, buffer
);
1369 RTFRouteToken( info
); /* feed "}" back to router */
1373 /* for now, lookup the \result part and use it, whatever the object */
1374 static void ME_RTFReadObjectGroup(RTF_Info
*info
)
1379 if (info
->rtfClass
== rtfEOF
)
1381 if (RTFCheckCM(info
, rtfGroup
, rtfEndGroup
))
1383 if (RTFCheckCM(info
, rtfGroup
, rtfBeginGroup
))
1386 if (info
->rtfClass
== rtfEOF
)
1388 if (RTFCheckCMM(info
, rtfControl
, rtfDestination
, rtfObjResult
))
1392 while (RTFGetToken (info
) != rtfEOF
)
1394 if (info
->rtfClass
== rtfGroup
)
1396 if (info
->rtfMajor
== rtfBeginGroup
) level
++;
1397 else if (info
->rtfMajor
== rtfEndGroup
&& --level
< 0) break;
1399 RTFRouteToken(info
);
1402 else RTFSkipGroup(info
);
1405 if (!RTFCheckCM (info
, rtfControl
, rtfObjAttr
))
1407 FIXME("Non supported attribute: %d %d %d\n", info
->rtfClass
, info
->rtfMajor
, info
->rtfMinor
);
1411 RTFRouteToken(info
); /* feed "}" back to router */
1414 static void ME_RTFReadParnumGroup( RTF_Info
*info
)
1416 int level
= 1, type
= -1;
1417 WORD indent
= 0, start
= 1;
1418 WCHAR txt_before
= 0, txt_after
= 0;
1422 RTFGetToken( info
);
1424 if (RTFCheckCMM( info
, rtfControl
, rtfDestination
, rtfParNumTextBefore
) ||
1425 RTFCheckCMM( info
, rtfControl
, rtfDestination
, rtfParNumTextAfter
))
1427 int loc
= info
->rtfMinor
;
1429 RTFGetToken( info
);
1430 if (info
->rtfClass
== rtfText
)
1432 if (loc
== rtfParNumTextBefore
)
1433 txt_before
= info
->rtfMajor
;
1435 txt_after
= info
->rtfMajor
;
1438 /* falling through to catch EOFs and group level changes */
1441 if (info
->rtfClass
== rtfEOF
)
1444 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1446 if (--level
== 0) break;
1450 if (RTFCheckCM( info
, rtfGroup
, rtfBeginGroup
))
1456 /* Ignore non para-attr */
1457 if (!RTFCheckCM( info
, rtfControl
, rtfParAttr
))
1460 switch (info
->rtfMinor
)
1462 case rtfParLevel
: /* Para level is ignored */
1469 case rtfParNumDecimal
:
1472 case rtfParNumULetter
:
1473 type
= PFN_UCLETTER
;
1475 case rtfParNumURoman
:
1478 case rtfParNumLLetter
:
1479 type
= PFN_LCLETTER
;
1481 case rtfParNumLRoman
:
1485 case rtfParNumIndent
:
1486 indent
= info
->rtfParam
;
1488 case rtfParNumStartAt
:
1489 start
= info
->rtfParam
;
1496 info
->fmt
.dwMask
|= (PFM_NUMBERING
| PFM_NUMBERINGSTART
| PFM_NUMBERINGSTYLE
| PFM_NUMBERINGTAB
);
1497 info
->fmt
.wNumbering
= type
;
1498 info
->fmt
.wNumberingStart
= start
;
1499 info
->fmt
.wNumberingStyle
= PFNS_PAREN
;
1500 if (type
!= PFN_BULLET
)
1502 if (txt_before
== 0 && txt_after
== 0)
1503 info
->fmt
.wNumberingStyle
= PFNS_PLAIN
;
1504 else if (txt_after
== '.')
1505 info
->fmt
.wNumberingStyle
= PFNS_PERIOD
;
1506 else if (txt_before
== '(' && txt_after
== ')')
1507 info
->fmt
.wNumberingStyle
= PFNS_PARENS
;
1509 info
->fmt
.wNumberingTab
= indent
;
1512 TRACE("type %d indent %d start %d txt before %04x txt after %04x\n",
1513 type
, indent
, start
, txt_before
, txt_after
);
1515 RTFRouteToken( info
); /* feed "}" back to router */
1518 static void ME_RTFReadHook(RTF_Info
*info
)
1520 switch(info
->rtfClass
)
1523 switch(info
->rtfMajor
)
1526 if (info
->stackTop
< maxStack
) {
1527 info
->stack
[info
->stackTop
].style
= info
->style
;
1528 ME_AddRefStyle(info
->style
);
1529 info
->stack
[info
->stackTop
].codePage
= info
->codePage
;
1530 info
->stack
[info
->stackTop
].unicodeLength
= info
->unicodeLength
;
1533 info
->styleChanged
= FALSE
;
1537 RTFFlushOutputBuffer(info
);
1539 if (info
->stackTop
<= 0)
1540 info
->rtfClass
= rtfEOF
;
1541 if (info
->stackTop
< 0)
1544 ME_ReleaseStyle(info
->style
);
1545 info
->style
= info
->stack
[info
->stackTop
].style
;
1546 info
->codePage
= info
->stack
[info
->stackTop
].codePage
;
1547 info
->unicodeLength
= info
->stack
[info
->stackTop
].unicodeLength
;
1556 ME_StreamInFill(ME_InStream
*stream
)
1558 stream
->editstream
->dwError
= stream
->editstream
->pfnCallback(stream
->editstream
->dwCookie
,
1559 (BYTE
*)stream
->buffer
,
1560 sizeof(stream
->buffer
),
1561 (LONG
*)&stream
->dwSize
);
1565 static LRESULT
ME_StreamIn(ME_TextEditor
*editor
, DWORD format
, EDITSTREAM
*stream
, BOOL stripLastCR
)
1571 int nEventMask
= editor
->nEventMask
;
1572 ME_InStream inStream
;
1573 BOOL invalidRTF
= FALSE
;
1574 ME_Cursor
*selStart
, *selEnd
;
1575 LRESULT num_read
= 0; /* bytes read for SF_TEXT, non-control chars inserted for SF_RTF */
1577 TRACE("stream==%p editor==%p format==0x%lX\n", stream
, editor
, format
);
1578 editor
->nEventMask
= 0;
1580 ME_GetSelectionOfs(editor
, &from
, &to
);
1581 if (format
& SFF_SELECTION
&& editor
->mode
& TM_RICHTEXT
)
1583 ME_GetSelection(editor
, &selStart
, &selEnd
);
1584 style
= ME_GetSelectionInsertStyle(editor
);
1586 ME_InternalDeleteText(editor
, selStart
, to
- from
, FALSE
);
1588 /* Don't insert text at the end of the table row */
1589 if (!editor
->bEmulateVersion10
) /* v4.1 */
1591 ME_Paragraph
*para
= editor
->pCursors
->para
;
1592 if (para
->nFlags
& (MEPF_ROWSTART
| MEPF_ROWEND
))
1594 para
= para_next( para
);
1595 editor
->pCursors
[0].para
= para
;
1596 editor
->pCursors
[0].run
= para_first_run( para
);
1597 editor
->pCursors
[0].nOffset
= 0;
1599 editor
->pCursors
[1] = editor
->pCursors
[0];
1601 else /* v1.0 - 3.0 */
1603 if (editor
->pCursors
[0].run
->nFlags
& MERF_ENDPARA
&&
1604 para_in_table( editor
->pCursors
[0].para
))
1610 style
= editor
->pBuffer
->pDefaultStyle
;
1611 ME_AddRefStyle(style
);
1612 if (format
& SFF_SELECTION
)
1614 ME_GetSelection(editor
, &selStart
, &selEnd
);
1615 ME_InternalDeleteText(editor
, selStart
, to
- from
, FALSE
);
1619 set_selection_cursors(editor
, 0, 0);
1620 ME_InternalDeleteText(editor
, &editor
->pCursors
[1],
1621 ME_GetTextLength(editor
), FALSE
);
1624 ME_ClearTempStyle(editor
);
1625 editor_set_default_para_fmt( editor
, &editor
->pCursors
[0].para
->fmt
);
1629 /* Back up undo mode to a local variable */
1630 nUndoMode
= editor
->nUndoMode
;
1632 /* Only create an undo if SFF_SELECTION is set */
1633 if (!(format
& SFF_SELECTION
))
1634 editor
->nUndoMode
= umIgnore
;
1636 inStream
.editstream
= stream
;
1637 inStream
.editstream
->dwError
= 0;
1638 inStream
.dwSize
= 0;
1639 inStream
.dwUsed
= 0;
1641 if (format
& SF_RTF
)
1643 /* Check if it's really RTF, and if it is not, use plain text */
1644 ME_StreamInFill(&inStream
);
1645 if (!inStream
.editstream
->dwError
)
1647 if ((!editor
->bEmulateVersion10
&& strncmp(inStream
.buffer
, "{\\rtf", 5) && strncmp(inStream
.buffer
, "{\\urtf", 6))
1648 || (editor
->bEmulateVersion10
&& *inStream
.buffer
!= '{'))
1651 inStream
.editstream
->dwError
= -16;
1656 if (!invalidRTF
&& !inStream
.editstream
->dwError
)
1659 from
= ME_GetCursorOfs(&editor
->pCursors
[0]);
1660 if (format
& SF_RTF
) {
1662 /* setup the RTF parser */
1663 memset(&parser
, 0, sizeof parser
);
1664 RTFSetEditStream(&parser
, &inStream
);
1665 parser
.rtfFormat
= format
&(SF_TEXT
|SF_RTF
);
1666 parser
.editor
= editor
;
1667 parser
.style
= style
;
1668 WriterInit(&parser
);
1670 RTFSetReadHook(&parser
, ME_RTFReadHook
);
1671 RTFSetDestinationCallback(&parser
, rtfShpPict
, ME_RTFReadShpPictGroup
);
1672 RTFSetDestinationCallback(&parser
, rtfPict
, ME_RTFReadPictGroup
);
1673 RTFSetDestinationCallback(&parser
, rtfObject
, ME_RTFReadObjectGroup
);
1674 RTFSetDestinationCallback(&parser
, rtfParNumbering
, ME_RTFReadParnumGroup
);
1675 if (!parser
.editor
->bEmulateVersion10
) /* v4.1 */
1677 RTFSetDestinationCallback(&parser
, rtfNoNestTables
, RTFSkipGroup
);
1678 RTFSetDestinationCallback(&parser
, rtfNestTableProps
, RTFReadGroup
);
1682 /* do the parsing */
1684 RTFFlushOutputBuffer(&parser
);
1685 if (!editor
->bEmulateVersion10
) /* v4.1 */
1687 if (parser
.tableDef
&& parser
.tableDef
->row_start
&&
1688 (parser
.nestingLevel
> 0 || parser
.canInheritInTbl
))
1690 /* Delete any incomplete table row at the end of the rich text. */
1694 parser
.rtfMinor
= rtfRow
;
1695 /* Complete the table row before deleting it.
1696 * By doing it this way we will have the current paragraph format set
1697 * properly to reflect that is not in the complete table, and undo items
1698 * will be added for this change to the current paragraph format. */
1699 if (parser
.nestingLevel
> 0)
1701 while (parser
.nestingLevel
> 1)
1702 ME_RTFSpecialCharHook(&parser
); /* Decrements nestingLevel */
1703 para
= parser
.tableDef
->row_start
;
1704 ME_RTFSpecialCharHook(&parser
);
1708 para
= parser
.tableDef
->row_start
;
1709 ME_RTFSpecialCharHook(&parser
);
1710 assert( para
->nFlags
& MEPF_ROWEND
);
1711 para
= para_next( para
);
1714 editor
->pCursors
[1].para
= para
;
1715 editor
->pCursors
[1].run
= para_first_run( para
);
1716 editor
->pCursors
[1].nOffset
= 0;
1717 nOfs
= ME_GetCursorOfs(&editor
->pCursors
[1]);
1718 nChars
= ME_GetCursorOfs(&editor
->pCursors
[0]) - nOfs
;
1719 ME_InternalDeleteText(editor
, &editor
->pCursors
[1], nChars
, TRUE
);
1720 if (parser
.tableDef
) parser
.tableDef
->row_start
= NULL
;
1723 RTFDestroy(&parser
);
1725 if (parser
.stackTop
> 0)
1727 while (--parser
.stackTop
>= 0)
1729 ME_ReleaseStyle(parser
.style
);
1730 parser
.style
= parser
.stack
[parser
.stackTop
].style
;
1732 if (!inStream
.editstream
->dwError
)
1733 inStream
.editstream
->dwError
= HRESULT_FROM_WIN32(ERROR_HANDLE_EOF
);
1736 /* Remove last line break, as mandated by tests. This is not affected by
1737 CR/LF counters, since RTF streaming presents only \para tokens, which
1738 are converted according to the standard rules: \r for 2.0, \r\n for 1.0
1740 if (stripLastCR
&& !(format
& SFF_SELECTION
)) {
1742 ME_GetSelection(editor
, &selStart
, &selEnd
);
1743 newto
= ME_GetCursorOfs(selEnd
);
1744 if (newto
> to
+ (editor
->bEmulateVersion10
? 1 : 0)) {
1745 WCHAR lastchar
[3] = {'\0', '\0'};
1746 int linebreakSize
= editor
->bEmulateVersion10
? 2 : 1;
1747 ME_Cursor linebreakCursor
= *selEnd
, lastcharCursor
= *selEnd
;
1750 /* Set the final eop to the char fmt of the last char */
1751 cf
.cbSize
= sizeof(cf
);
1752 cf
.dwMask
= CFM_ALL2
;
1753 ME_MoveCursorChars(editor
, &lastcharCursor
, -1, FALSE
);
1754 ME_GetCharFormat(editor
, &lastcharCursor
, &linebreakCursor
, &cf
);
1755 set_selection_cursors(editor
, newto
, -1);
1756 ME_SetSelectionCharFormat(editor
, &cf
);
1757 set_selection_cursors(editor
, newto
, newto
);
1759 ME_MoveCursorChars(editor
, &linebreakCursor
, -linebreakSize
, FALSE
);
1760 ME_GetTextW(editor
, lastchar
, 2, &linebreakCursor
, linebreakSize
, FALSE
, FALSE
);
1761 if (lastchar
[0] == '\r' && (lastchar
[1] == '\n' || lastchar
[1] == '\0')) {
1762 ME_InternalDeleteText(editor
, &linebreakCursor
, linebreakSize
, FALSE
);
1766 to
= ME_GetCursorOfs(&editor
->pCursors
[0]);
1767 num_read
= to
- from
;
1769 style
= parser
.style
;
1771 else if (format
& SF_TEXT
)
1773 num_read
= ME_StreamInText(editor
, format
, &inStream
, style
);
1774 to
= ME_GetCursorOfs(&editor
->pCursors
[0]);
1777 ERR("EM_STREAMIN without SF_TEXT or SF_RTF\n");
1778 /* put the cursor at the top */
1779 if (!(format
& SFF_SELECTION
))
1780 set_selection_cursors(editor
, 0, 0);
1781 cursor_from_char_ofs( editor
, from
, &start
);
1782 ME_UpdateLinkAttribute(editor
, &start
, to
- from
);
1785 /* Restore saved undo mode */
1786 editor
->nUndoMode
= nUndoMode
;
1788 /* even if we didn't add an undo, we need to commit anything on the stack */
1789 ME_CommitUndo(editor
);
1791 /* If SFF_SELECTION isn't set, delete any undos from before we started too */
1792 if (!(format
& SFF_SELECTION
))
1793 ME_EmptyUndoStack(editor
);
1795 ME_ReleaseStyle(style
);
1796 editor
->nEventMask
= nEventMask
;
1797 ME_UpdateRepaint(editor
, FALSE
);
1798 if (!(format
& SFF_SELECTION
)) {
1799 ME_ClearTempStyle(editor
);
1801 ME_SendSelChange(editor
);
1802 ME_SendRequestResize(editor
, FALSE
);
1808 typedef struct tagME_RTFStringStreamStruct
1813 } ME_RTFStringStreamStruct
;
1815 static DWORD CALLBACK
ME_ReadFromRTFString(DWORD_PTR dwCookie
, LPBYTE lpBuff
, LONG cb
, LONG
*pcb
)
1817 ME_RTFStringStreamStruct
*pStruct
= (ME_RTFStringStreamStruct
*)dwCookie
;
1820 count
= min(cb
, pStruct
->length
- pStruct
->pos
);
1821 memmove(lpBuff
, pStruct
->string
+ pStruct
->pos
, count
);
1822 pStruct
->pos
+= count
;
1828 ME_StreamInRTFString(ME_TextEditor
*editor
, BOOL selection
, char *string
)
1831 ME_RTFStringStreamStruct data
;
1833 data
.string
= string
;
1834 data
.length
= strlen(string
);
1836 es
.dwCookie
= (DWORD_PTR
)&data
;
1837 es
.pfnCallback
= ME_ReadFromRTFString
;
1838 ME_StreamIn(editor
, SF_RTF
| (selection
? SFF_SELECTION
: 0), &es
, TRUE
);
1843 ME_FindText(ME_TextEditor
*editor
, DWORD flags
, const CHARRANGE
*chrg
, const WCHAR
*text
, CHARRANGE
*chrgText
)
1845 const int nLen
= lstrlenW(text
);
1846 const int nTextLen
= ME_GetTextLength(editor
);
1849 WCHAR wLastChar
= ' ';
1851 TRACE("flags==0x%08lx, chrg->cpMin==%ld, chrg->cpMax==%ld text==%s\n",
1852 flags
, chrg
->cpMin
, chrg
->cpMax
, debugstr_w(text
));
1854 if (flags
& ~(FR_DOWN
| FR_MATCHCASE
| FR_WHOLEWORD
))
1855 FIXME("Flags 0x%08lx not implemented\n",
1856 flags
& ~(FR_DOWN
| FR_MATCHCASE
| FR_WHOLEWORD
));
1859 if (chrg
->cpMax
== -1)
1862 nMax
= chrg
->cpMax
> nTextLen
? nTextLen
: chrg
->cpMax
;
1864 /* In 1.0 emulation, if cpMax reaches end of text, add the FR_DOWN flag */
1865 if (editor
->bEmulateVersion10
&& nMax
== nTextLen
)
1870 /* In 1.0 emulation, cpMin must always be no greater than cpMax */
1871 if (editor
->bEmulateVersion10
&& nMax
< nMin
)
1875 chrgText
->cpMin
= -1;
1876 chrgText
->cpMax
= -1;
1881 /* when searching up, if cpMin < cpMax, then instead of searching
1882 * on [cpMin,cpMax], we search on [0,cpMin], otherwise, search on
1883 * [cpMax, cpMin]. The exception is when cpMax is -1, in which
1884 * case, it is always bigger than cpMin.
1886 if (!editor
->bEmulateVersion10
&& !(flags
& FR_DOWN
))
1890 nMax
= nMin
> nTextLen
? nTextLen
: nMin
;
1891 if (nMin
< nSwap
|| chrg
->cpMax
== -1)
1897 if (!nLen
|| nMin
< 0 || nMax
< 0 || nMax
< nMin
)
1900 chrgText
->cpMin
= chrgText
->cpMax
= -1;
1904 if (flags
& FR_DOWN
) /* Forward search */
1906 /* If possible, find the character before where the search starts */
1907 if ((flags
& FR_WHOLEWORD
) && nMin
)
1909 cursor_from_char_ofs( editor
, nMin
- 1, &cursor
);
1910 wLastChar
= *get_text( cursor
.run
, cursor
.nOffset
);
1911 ME_MoveCursorChars(editor
, &cursor
, 1, FALSE
);
1913 else cursor_from_char_ofs( editor
, nMin
, &cursor
);
1915 while (cursor
.run
&& ME_GetCursorOfs(&cursor
) + nLen
<= nMax
)
1917 ME_Run
*run
= cursor
.run
;
1918 int nCurStart
= cursor
.nOffset
;
1921 while (run
&& ME_CharCompare( *get_text( run
, nCurStart
+ nMatched
), text
[nMatched
], (flags
& FR_MATCHCASE
)))
1923 if ((flags
& FR_WHOLEWORD
) && iswalnum(wLastChar
))
1927 if (nMatched
== nLen
)
1929 ME_Run
*next_run
= run
;
1930 int nNextStart
= nCurStart
;
1933 /* Check to see if next character is a whitespace */
1934 if (flags
& FR_WHOLEWORD
)
1936 if (nCurStart
+ nMatched
== run
->len
)
1938 next_run
= run_next_all_paras( run
);
1939 nNextStart
= -nMatched
;
1943 wNextChar
= *get_text( next_run
, nNextStart
+ nMatched
);
1947 if (iswalnum(wNextChar
))
1951 cursor
.nOffset
+= cursor
.para
->nCharOfs
+ cursor
.run
->nCharOfs
;
1954 chrgText
->cpMin
= cursor
.nOffset
;
1955 chrgText
->cpMax
= cursor
.nOffset
+ nLen
;
1957 TRACE("found at %d-%d\n", cursor
.nOffset
, cursor
.nOffset
+ nLen
);
1958 return cursor
.nOffset
;
1960 if (nCurStart
+ nMatched
== run
->len
)
1962 run
= run_next_all_paras( run
);
1963 nCurStart
= -nMatched
;
1967 wLastChar
= *get_text( run
, nCurStart
+ nMatched
);
1972 if (cursor
.nOffset
== cursor
.run
->len
)
1974 if (run_next_all_paras( cursor
.run
))
1976 cursor
.run
= run_next_all_paras( cursor
.run
);
1977 cursor
.para
= cursor
.run
->para
;
1985 else /* Backward search */
1987 /* If possible, find the character after where the search ends */
1988 if ((flags
& FR_WHOLEWORD
) && nMax
< nTextLen
- 1)
1990 cursor_from_char_ofs( editor
, nMax
+ 1, &cursor
);
1991 wLastChar
= *get_text( cursor
.run
, cursor
.nOffset
);
1992 ME_MoveCursorChars(editor
, &cursor
, -1, FALSE
);
1994 else cursor_from_char_ofs( editor
, nMax
, &cursor
);
1996 while (cursor
.run
&& ME_GetCursorOfs(&cursor
) - nLen
>= nMin
)
1998 ME_Run
*run
= cursor
.run
;
1999 ME_Paragraph
*para
= cursor
.para
;
2000 int nCurEnd
= cursor
.nOffset
;
2003 if (nCurEnd
== 0 && run_prev_all_paras( run
))
2005 run
= run_prev_all_paras( run
);
2010 while (run
&& ME_CharCompare( *get_text( run
, nCurEnd
- nMatched
- 1 ),
2011 text
[nLen
- nMatched
- 1], (flags
& FR_MATCHCASE
) ))
2013 if ((flags
& FR_WHOLEWORD
) && iswalnum(wLastChar
))
2017 if (nMatched
== nLen
)
2019 ME_Run
*prev_run
= run
;
2020 int nPrevEnd
= nCurEnd
;
2024 /* Check to see if previous character is a whitespace */
2025 if (flags
& FR_WHOLEWORD
)
2027 if (nPrevEnd
- nMatched
== 0)
2029 prev_run
= run_prev_all_paras( run
);
2030 if (prev_run
) nPrevEnd
= prev_run
->len
+ nMatched
;
2033 if (prev_run
) wPrevChar
= *get_text( prev_run
, nPrevEnd
- nMatched
- 1 );
2034 else wPrevChar
= ' ';
2036 if (iswalnum(wPrevChar
))
2040 nStart
= para
->nCharOfs
+ run
->nCharOfs
+ nCurEnd
- nMatched
;
2043 chrgText
->cpMin
= nStart
;
2044 chrgText
->cpMax
= nStart
+ nLen
;
2046 TRACE("found at %d-%d\n", nStart
, nStart
+ nLen
);
2049 if (nCurEnd
- nMatched
== 0)
2051 if (run_prev_all_paras( run
))
2053 run
= run_prev_all_paras( run
);
2056 /* Don't care about pCurItem becoming NULL here; it's already taken
2057 * care of in the exterior loop condition */
2058 nCurEnd
= run
->len
+ nMatched
;
2062 wLastChar
= *get_text( run
, nCurEnd
- nMatched
- 1 );
2067 if (cursor
.nOffset
< 0)
2069 if (run_prev_all_paras( cursor
.run
) )
2071 cursor
.run
= run_prev_all_paras( cursor
.run
);
2072 cursor
.para
= cursor
.run
->para
;
2073 cursor
.nOffset
= cursor
.run
->len
;
2080 TRACE("not found\n");
2082 chrgText
->cpMin
= chrgText
->cpMax
= -1;
2086 static int ME_GetTextEx(ME_TextEditor
*editor
, GETTEXTEX
*ex
, LPARAM pText
)
2091 if (!ex
->cb
|| !pText
) return 0;
2093 if (ex
->flags
& ~(GT_SELECTION
| GT_USECRLF
))
2094 FIXME("GETTEXTEX flags 0x%08lx not supported\n", ex
->flags
& ~(GT_SELECTION
| GT_USECRLF
));
2096 if (ex
->flags
& GT_SELECTION
)
2099 int nStartCur
= ME_GetSelectionOfs(editor
, &from
, &to
);
2100 start
= editor
->pCursors
[nStartCur
];
2105 ME_SetCursorToStart(editor
, &start
);
2108 if (ex
->codepage
== CP_UNICODE
)
2110 return ME_GetTextW(editor
, (LPWSTR
)pText
, ex
->cb
/ sizeof(WCHAR
) - 1,
2111 &start
, nChars
, ex
->flags
& GT_USECRLF
, FALSE
);
2115 /* potentially each char may be a CR, why calculate the exact value with O(N) when
2116 we can just take a bigger buffer? :)
2117 The above assumption still holds with CR/LF counters, since CR->CRLF expansion
2118 occurs only in richedit 2.0 mode, in which line breaks have only one CR
2120 int crlfmul
= (ex
->flags
& GT_USECRLF
) ? 2 : 1;
2125 buflen
= min(crlfmul
* nChars
, ex
->cb
- 1);
2126 buffer
= heap_alloc((buflen
+ 1) * sizeof(WCHAR
));
2128 nChars
= ME_GetTextW(editor
, buffer
, buflen
, &start
, nChars
, ex
->flags
& GT_USECRLF
, FALSE
);
2129 rc
= WideCharToMultiByte(ex
->codepage
, 0, buffer
, nChars
+ 1,
2130 (LPSTR
)pText
, ex
->cb
, ex
->lpDefaultChar
, ex
->lpUsedDefChar
);
2131 if (rc
) rc
--; /* do not count 0 terminator */
2138 static int get_text_range( ME_TextEditor
*editor
, WCHAR
*buffer
,
2139 const ME_Cursor
*start
, int len
)
2141 if (!buffer
) return 0;
2142 return ME_GetTextW( editor
, buffer
, INT_MAX
, start
, len
, FALSE
, FALSE
);
2145 int set_selection( ME_TextEditor
*editor
, int to
, int from
)
2149 TRACE("%d - %d\n", to
, from
);
2151 if (!editor
->bHideSelection
) ME_InvalidateSelection( editor
);
2152 end
= set_selection_cursors( editor
, to
, from
);
2153 editor_ensure_visible( editor
, &editor
->pCursors
[0] );
2154 if (!editor
->bHideSelection
) ME_InvalidateSelection( editor
);
2155 update_caret( editor
);
2156 ME_SendSelChange( editor
);
2161 typedef struct tagME_GlobalDestStruct
2165 } ME_GlobalDestStruct
;
2167 static DWORD CALLBACK
ME_ReadFromHGLOBALUnicode(DWORD_PTR dwCookie
, LPBYTE lpBuff
, LONG cb
, LONG
*pcb
)
2169 ME_GlobalDestStruct
*pData
= (ME_GlobalDestStruct
*)dwCookie
;
2174 pDest
= (WORD
*)lpBuff
;
2175 pSrc
= GlobalLock(pData
->hData
);
2176 for (i
= 0; i
<cb
&& pSrc
[pData
->nLength
+i
]; i
++) {
2177 pDest
[i
] = pSrc
[pData
->nLength
+i
];
2179 pData
->nLength
+= i
;
2181 GlobalUnlock(pData
->hData
);
2185 static DWORD CALLBACK
ME_ReadFromHGLOBALRTF(DWORD_PTR dwCookie
, LPBYTE lpBuff
, LONG cb
, LONG
*pcb
)
2187 ME_GlobalDestStruct
*pData
= (ME_GlobalDestStruct
*)dwCookie
;
2192 pSrc
= GlobalLock(pData
->hData
);
2193 for (i
= 0; i
<cb
&& pSrc
[pData
->nLength
+i
]; i
++) {
2194 pDest
[i
] = pSrc
[pData
->nLength
+i
];
2196 pData
->nLength
+= i
;
2198 GlobalUnlock(pData
->hData
);
2202 static HRESULT
paste_rtf(ME_TextEditor
*editor
, FORMATETC
*fmt
, STGMEDIUM
*med
)
2205 ME_GlobalDestStruct gds
;
2208 gds
.hData
= med
->u
.hGlobal
;
2210 es
.dwCookie
= (DWORD_PTR
)&gds
;
2211 es
.pfnCallback
= ME_ReadFromHGLOBALRTF
;
2212 hr
= ME_StreamIn( editor
, SF_RTF
| SFF_SELECTION
, &es
, FALSE
) == 0 ? E_FAIL
: S_OK
;
2213 ReleaseStgMedium( med
);
2217 static HRESULT
paste_text(ME_TextEditor
*editor
, FORMATETC
*fmt
, STGMEDIUM
*med
)
2220 ME_GlobalDestStruct gds
;
2223 gds
.hData
= med
->u
.hGlobal
;
2225 es
.dwCookie
= (DWORD_PTR
)&gds
;
2226 es
.pfnCallback
= ME_ReadFromHGLOBALUnicode
;
2227 hr
= ME_StreamIn( editor
, SF_TEXT
| SF_UNICODE
| SFF_SELECTION
, &es
, FALSE
) == 0 ? E_FAIL
: S_OK
;
2228 ReleaseStgMedium( med
);
2232 static HRESULT
paste_emf(ME_TextEditor
*editor
, FORMATETC
*fmt
, STGMEDIUM
*med
)
2237 hr
= insert_static_object( editor
, med
->u
.hEnhMetaFile
, NULL
, &sz
);
2240 ME_CommitUndo( editor
);
2241 ME_UpdateRepaint( editor
, FALSE
);
2244 ReleaseStgMedium( med
);
2249 static struct paste_format
2252 HRESULT (*paste
)(ME_TextEditor
*, FORMATETC
*, STGMEDIUM
*);
2256 {{ -1, NULL
, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
}, paste_rtf
, L
"Rich Text Format" },
2257 {{ CF_UNICODETEXT
, NULL
, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
}, paste_text
},
2258 {{ CF_ENHMETAFILE
, NULL
, DVASPECT_CONTENT
, -1, TYMED_ENHMF
}, paste_emf
},
2262 static void init_paste_formats(void)
2264 struct paste_format
*format
;
2269 for (format
= paste_formats
; format
->fmt
.cfFormat
; format
++)
2272 format
->fmt
.cfFormat
= RegisterClipboardFormatW( format
->name
);
2278 static BOOL
paste_special(ME_TextEditor
*editor
, UINT cf
, REPASTESPECIAL
*ps
, BOOL check_only
)
2282 struct paste_format
*format
;
2285 /* Protect read-only edit control from modification */
2286 if (editor
->props
& TXTBIT_READONLY
)
2288 if (!check_only
) editor_beep( editor
, MB_ICONERROR
);
2292 init_paste_formats();
2294 if (ps
&& ps
->dwAspect
!= DVASPECT_CONTENT
)
2295 FIXME("Ignoring aspect %lx\n", ps
->dwAspect
);
2297 hr
= OleGetClipboard( &data
);
2298 if (hr
!= S_OK
) return FALSE
;
2300 if (cf
== CF_TEXT
) cf
= CF_UNICODETEXT
;
2303 for (format
= paste_formats
; format
->fmt
.cfFormat
; format
++)
2305 if (cf
&& cf
!= format
->fmt
.cfFormat
) continue;
2306 hr
= IDataObject_QueryGetData( data
, &format
->fmt
);
2311 hr
= IDataObject_GetData( data
, &format
->fmt
, &med
);
2312 if (hr
!= S_OK
) goto done
;
2313 hr
= format
->paste( editor
, &format
->fmt
, &med
);
2320 IDataObject_Release( data
);
2325 static HRESULT
editor_copy( ME_TextEditor
*editor
, ME_Cursor
*start
, int chars
, IDataObject
**data_out
)
2327 IDataObject
*data
= NULL
;
2330 if (editor
->lpOleCallback
)
2333 range
.cpMin
= ME_GetCursorOfs( start
);
2334 range
.cpMax
= range
.cpMin
+ chars
;
2335 hr
= IRichEditOleCallback_GetClipboardData( editor
->lpOleCallback
, &range
, RECO_COPY
, &data
);
2338 if (FAILED( hr
) || !data
)
2339 hr
= ME_GetDataObject( editor
, start
, chars
, &data
);
2341 if (SUCCEEDED( hr
))
2347 hr
= OleSetClipboard( data
);
2348 IDataObject_Release( data
);
2355 HRESULT
editor_copy_or_cut( ME_TextEditor
*editor
, BOOL cut
, ME_Cursor
*start
, int count
,
2356 IDataObject
**data_out
)
2360 if (cut
&& (editor
->props
& TXTBIT_READONLY
))
2362 return E_ACCESSDENIED
;
2365 hr
= editor_copy( editor
, start
, count
, data_out
);
2366 if (SUCCEEDED(hr
) && cut
)
2368 ME_InternalDeleteText( editor
, start
, count
, FALSE
);
2369 ME_CommitUndo( editor
);
2370 ME_UpdateRepaint( editor
, TRUE
);
2375 static BOOL
copy_or_cut( ME_TextEditor
*editor
, BOOL cut
)
2379 int start_cursor
= ME_GetSelectionOfs( editor
, &offs
, &count
);
2380 ME_Cursor
*sel_start
= &editor
->pCursors
[start_cursor
];
2382 if (editor
->password_char
) return FALSE
;
2385 hr
= editor_copy_or_cut( editor
, cut
, sel_start
, count
, NULL
);
2386 if (FAILED( hr
)) editor_beep( editor
, MB_ICONERROR
);
2388 return SUCCEEDED( hr
);
2391 static void ME_UpdateSelectionLinkAttribute(ME_TextEditor
*editor
)
2393 ME_Paragraph
*start_para
, *end_para
;
2394 ME_Cursor
*from
, *to
, start
;
2397 if (!editor
->AutoURLDetect_bEnable
) return;
2399 ME_GetSelection(editor
, &from
, &to
);
2401 /* Find paragraph previous to the one that contains start cursor */
2402 start_para
= from
->para
;
2403 if (para_prev( start_para
)) start_para
= para_prev( start_para
);
2405 /* Find paragraph that contains end cursor */
2406 end_para
= para_next( to
->para
);
2408 start
.para
= start_para
;
2409 start
.run
= para_first_run( start_para
);
2411 num_chars
= end_para
->nCharOfs
- start_para
->nCharOfs
;
2413 ME_UpdateLinkAttribute( editor
, &start
, num_chars
);
2416 static BOOL
handle_enter(ME_TextEditor
*editor
)
2418 BOOL shift_is_down
= GetKeyState(VK_SHIFT
) & 0x8000;
2420 if (editor
->props
& TXTBIT_MULTILINE
)
2422 ME_Cursor cursor
= editor
->pCursors
[0];
2423 ME_Paragraph
*para
= cursor
.para
;
2425 ME_Style
*style
, *eop_style
;
2427 if (editor
->props
& TXTBIT_READONLY
)
2429 editor_beep( editor
, MB_ICONERROR
);
2433 ME_GetSelectionOfs(editor
, &from
, &to
);
2434 if (editor
->nTextLimit
> ME_GetTextLength(editor
) - (to
-from
))
2436 if (!editor
->bEmulateVersion10
) /* v4.1 */
2438 if (para
->nFlags
& MEPF_ROWEND
)
2440 /* Add a new table row after this row. */
2441 para
= table_append_row( editor
, para
);
2442 para
= para_next( para
);
2443 editor
->pCursors
[0].para
= para
;
2444 editor
->pCursors
[0].run
= para_first_run( para
);
2445 editor
->pCursors
[0].nOffset
= 0;
2446 editor
->pCursors
[1] = editor
->pCursors
[0];
2447 ME_CommitUndo(editor
);
2448 ME_UpdateRepaint(editor
, FALSE
);
2451 else if (para
== editor
->pCursors
[1].para
&&
2452 cursor
.nOffset
+ cursor
.run
->nCharOfs
== 0 &&
2453 para_prev( para
) && para_prev( para
)->nFlags
& MEPF_ROWSTART
&&
2454 !para_prev( para
)->nCharOfs
)
2456 /* Insert a newline before the table. */
2457 para
= para_prev( para
);
2458 para
->nFlags
&= ~MEPF_ROWSTART
;
2459 editor
->pCursors
[0].para
= para
;
2460 editor
->pCursors
[0].run
= para_first_run( para
);
2461 editor
->pCursors
[1] = editor
->pCursors
[0];
2462 ME_InsertTextFromCursor( editor
, 0, L
"\r", 1, editor
->pCursors
[0].run
->style
);
2463 para
= editor_first_para( editor
);
2464 editor_set_default_para_fmt( editor
, ¶
->fmt
);
2466 para_mark_rewrap( editor
, para
);
2467 editor
->pCursors
[0].para
= para
;
2468 editor
->pCursors
[0].run
= para_first_run( para
);
2469 editor
->pCursors
[1] = editor
->pCursors
[0];
2470 para_next( para
)->nFlags
|= MEPF_ROWSTART
;
2471 ME_CommitCoalescingUndo(editor
);
2472 ME_UpdateRepaint(editor
, FALSE
);
2476 else /* v1.0 - 3.0 */
2478 ME_Paragraph
*para
= cursor
.para
;
2479 if (para_in_table( para
))
2481 if (cursor
.run
->nFlags
& MERF_ENDPARA
)
2485 ME_ContinueCoalescingTransaction(editor
);
2486 para
= table_append_row( editor
, para
);
2487 editor
->pCursors
[0].para
= para
;
2488 editor
->pCursors
[0].run
= para_first_run( para
);
2489 editor
->pCursors
[0].nOffset
= 0;
2490 editor
->pCursors
[1] = editor
->pCursors
[0];
2491 ME_CommitCoalescingUndo(editor
);
2492 ME_UpdateRepaint(editor
, FALSE
);
2498 ME_ContinueCoalescingTransaction(editor
);
2499 if (cursor
.run
->nCharOfs
+ cursor
.nOffset
== 0 &&
2500 para_prev( para
) && !para_in_table( para_prev( para
) ))
2502 /* Insert newline before table */
2503 cursor
.run
= para_end_run( para_prev( para
) );
2506 editor
->pCursors
[0].run
= cursor
.run
;
2507 editor
->pCursors
[0].para
= para_prev( para
);
2509 editor
->pCursors
[0].nOffset
= 0;
2510 editor
->pCursors
[1] = editor
->pCursors
[0];
2511 ME_InsertTextFromCursor( editor
, 0, L
"\r", 1, editor
->pCursors
[0].run
->style
);
2515 editor
->pCursors
[1] = editor
->pCursors
[0];
2516 para
= table_append_row( editor
, para
);
2517 editor
->pCursors
[0].para
= para
;
2518 editor
->pCursors
[0].run
= para_first_run( para
);
2519 editor
->pCursors
[0].nOffset
= 0;
2520 editor
->pCursors
[1] = editor
->pCursors
[0];
2522 ME_CommitCoalescingUndo(editor
);
2523 ME_UpdateRepaint(editor
, FALSE
);
2529 style
= style_get_insert_style( editor
, editor
->pCursors
);
2531 /* Normally the new eop style is the insert style, however in a list it is copied from the existing
2532 eop style (this prevents the list label style changing when the new eop is inserted).
2533 No extra ref is taken here on eop_style. */
2534 if (para
->fmt
.wNumbering
)
2535 eop_style
= para
->eop_run
->style
;
2538 ME_ContinueCoalescingTransaction(editor
);
2540 ME_InsertEndRowFromCursor(editor
, 0);
2542 if (!editor
->bEmulateVersion10
)
2543 ME_InsertTextFromCursor(editor
, 0, L
"\r", 1, eop_style
);
2545 ME_InsertTextFromCursor(editor
, 0, L
"\r\n", 2, eop_style
);
2546 ME_CommitCoalescingUndo(editor
);
2549 ME_UpdateSelectionLinkAttribute(editor
);
2550 ME_UpdateRepaint(editor
, FALSE
);
2551 ME_SaveTempStyle(editor
, style
); /* set the temp insert style for the new para */
2552 ME_ReleaseStyle(style
);
2560 ME_KeyDown(ME_TextEditor
*editor
, WORD nKey
)
2562 BOOL ctrl_is_down
= GetKeyState(VK_CONTROL
) & 0x8000;
2563 BOOL shift_is_down
= GetKeyState(VK_SHIFT
) & 0x8000;
2565 if (editor
->bMouseCaptured
)
2567 if (nKey
!= VK_SHIFT
&& nKey
!= VK_CONTROL
&& nKey
!= VK_MENU
)
2568 editor
->nSelectionType
= stPosition
;
2576 editor
->nUDArrowX
= -1;
2582 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
2583 ME_ArrowKey(editor
, nKey
, shift_is_down
, ctrl_is_down
);
2587 editor
->nUDArrowX
= -1;
2588 /* FIXME backspace and delete aren't the same, they act different wrt paragraph style of the merged paragraph */
2589 if (editor
->props
& TXTBIT_READONLY
)
2591 if (ME_IsSelection(editor
))
2593 ME_DeleteSelection(editor
);
2594 ME_CommitUndo(editor
);
2596 else if (nKey
== VK_DELETE
)
2598 /* Delete stops group typing.
2599 * (See MSDN remarks on EM_STOPGROUPTYPING message) */
2600 ME_DeleteTextAtCursor(editor
, 1, 1);
2601 ME_CommitUndo(editor
);
2603 else if (ME_ArrowKey(editor
, VK_LEFT
, FALSE
, FALSE
))
2605 BOOL bDeletionSucceeded
;
2606 /* Backspace can be grouped for a single undo */
2607 ME_ContinueCoalescingTransaction(editor
);
2608 bDeletionSucceeded
= ME_DeleteTextAtCursor(editor
, 1, 1);
2609 if (!bDeletionSucceeded
&& !editor
->bEmulateVersion10
) { /* v4.1 */
2610 /* Deletion was prevented so the cursor is moved back to where it was.
2611 * (e.g. this happens when trying to delete cell boundaries)
2613 ME_ArrowKey(editor
, VK_RIGHT
, FALSE
, FALSE
);
2615 ME_CommitCoalescingUndo(editor
);
2619 table_move_from_row_start( editor
);
2620 ME_UpdateSelectionLinkAttribute(editor
);
2621 ME_UpdateRepaint(editor
, FALSE
);
2622 ME_SendRequestResize(editor
, FALSE
);
2625 if (!editor
->bEmulateVersion10
)
2626 return handle_enter(editor
);
2631 set_selection( editor
, 0, -1 );
2637 return paste_special( editor
, 0, NULL
, FALSE
);
2642 return copy_or_cut(editor
, nKey
== 'X');
2660 if (nKey
!= VK_SHIFT
&& nKey
!= VK_CONTROL
&& nKey
&& nKey
!= VK_MENU
)
2661 editor
->nUDArrowX
= -1;
2668 chf
.cbSize
= sizeof(chf
);
2670 ME_GetSelectionCharFormat(editor
, &chf
);
2671 ME_DumpStyleToBuf(&chf
, buf
);
2672 MessageBoxA(NULL
, buf
, "Style dump", MB_OK
);
2676 ME_CheckCharOffsets(editor
);
2683 static LRESULT
handle_wm_char( ME_TextEditor
*editor
, WCHAR wstr
, LPARAM flags
)
2685 if (editor
->bMouseCaptured
)
2688 if (editor
->props
& TXTBIT_READONLY
)
2690 editor_beep( editor
, MB_ICONERROR
);
2691 return 0; /* FIXME really 0 ? */
2694 if (editor
->bEmulateVersion10
&& wstr
== '\r')
2695 handle_enter(editor
);
2697 if ((unsigned)wstr
>= ' ' || wstr
== '\t')
2699 ME_Cursor cursor
= editor
->pCursors
[0];
2700 ME_Paragraph
*para
= cursor
.para
;
2702 BOOL ctrl_is_down
= GetKeyState(VK_CONTROL
) & 0x8000;
2703 ME_GetSelectionOfs(editor
, &from
, &to
);
2705 /* v4.1 allows tabs to be inserted with ctrl key down */
2706 !(ctrl_is_down
&& !editor
->bEmulateVersion10
))
2708 BOOL selected_row
= FALSE
;
2710 if (ME_IsSelection(editor
) &&
2711 cursor
.run
->nCharOfs
+ cursor
.nOffset
== 0 &&
2712 to
== ME_GetCursorOfs(&editor
->pCursors
[0]) && para_prev( para
))
2714 para
= para_prev( para
);
2715 selected_row
= TRUE
;
2717 if (para_in_table( para
))
2719 table_handle_tab( editor
, selected_row
);
2720 ME_CommitUndo(editor
);
2724 else if (!editor
->bEmulateVersion10
) /* v4.1 */
2726 if (para
->nFlags
& MEPF_ROWEND
)
2730 para
= para_next( para
);
2731 if (para
->nFlags
& MEPF_ROWSTART
) para
= para_next( para
);
2732 editor
->pCursors
[0].para
= para
;
2733 editor
->pCursors
[0].run
= para_first_run( para
);
2734 editor
->pCursors
[0].nOffset
= 0;
2735 editor
->pCursors
[1] = editor
->pCursors
[0];
2739 else /* v1.0 - 3.0 */
2741 if (para_in_table( para
) && cursor
.run
->nFlags
& MERF_ENDPARA
&& from
== to
)
2743 /* Text should not be inserted at the end of the table. */
2744 editor_beep( editor
, -1 );
2748 /* FIXME maybe it would make sense to call EM_REPLACESEL instead ? */
2749 /* WM_CHAR is restricted to nTextLimit */
2750 if(editor
->nTextLimit
> ME_GetTextLength(editor
) - (to
-from
))
2752 ME_Style
*style
= style_get_insert_style( editor
, editor
->pCursors
);
2753 ME_ContinueCoalescingTransaction(editor
);
2754 ME_InsertTextFromCursor(editor
, 0, &wstr
, 1, style
);
2755 ME_ReleaseStyle(style
);
2756 ME_CommitCoalescingUndo(editor
);
2757 ITextHost_TxSetCursor(editor
->texthost
, NULL
, FALSE
);
2760 ME_UpdateSelectionLinkAttribute(editor
);
2761 ME_UpdateRepaint(editor
, FALSE
);
2766 /* Process the message and calculate the new click count.
2768 * returns: The click count if it is mouse down event, else returns 0. */
2769 static int ME_CalculateClickCount(ME_TextEditor
*editor
, UINT msg
, WPARAM wParam
,
2772 static int clickNum
= 0;
2773 if (msg
< WM_MOUSEFIRST
|| msg
> WM_MOUSELAST
)
2776 if ((msg
== WM_LBUTTONDBLCLK
) ||
2777 (msg
== WM_RBUTTONDBLCLK
) ||
2778 (msg
== WM_MBUTTONDBLCLK
) ||
2779 (msg
== WM_XBUTTONDBLCLK
))
2781 msg
-= (WM_LBUTTONDBLCLK
- WM_LBUTTONDOWN
);
2784 if ((msg
== WM_LBUTTONDOWN
) ||
2785 (msg
== WM_RBUTTONDOWN
) ||
2786 (msg
== WM_MBUTTONDOWN
) ||
2787 (msg
== WM_XBUTTONDOWN
))
2789 static MSG prevClickMsg
;
2791 /* Compare the editor instead of the hwnd so that the this
2792 * can still be done for windowless richedit controls. */
2793 clickMsg
.hwnd
= (HWND
)editor
;
2794 clickMsg
.message
= msg
;
2795 clickMsg
.wParam
= wParam
;
2796 clickMsg
.lParam
= lParam
;
2797 clickMsg
.time
= GetMessageTime();
2798 clickMsg
.pt
.x
= (short)LOWORD(lParam
);
2799 clickMsg
.pt
.y
= (short)HIWORD(lParam
);
2800 if ((clickNum
!= 0) &&
2801 (clickMsg
.message
== prevClickMsg
.message
) &&
2802 (clickMsg
.hwnd
== prevClickMsg
.hwnd
) &&
2803 (clickMsg
.wParam
== prevClickMsg
.wParam
) &&
2804 (clickMsg
.time
- prevClickMsg
.time
< GetDoubleClickTime()) &&
2805 (abs(clickMsg
.pt
.x
- prevClickMsg
.pt
.x
) < GetSystemMetrics(SM_CXDOUBLECLK
)/2) &&
2806 (abs(clickMsg
.pt
.y
- prevClickMsg
.pt
.y
) < GetSystemMetrics(SM_CYDOUBLECLK
)/2))
2812 prevClickMsg
= clickMsg
;
2819 static BOOL
is_link( ME_Run
*run
)
2821 return (run
->style
->fmt
.dwMask
& CFM_LINK
) && (run
->style
->fmt
.dwEffects
& CFE_LINK
);
2824 void editor_set_cursor( ME_TextEditor
*editor
, int x
, int y
)
2827 static HCURSOR cursor_arrow
, cursor_hand
, cursor_ibeam
, cursor_reverse
;
2832 cursor_arrow
= LoadCursorW( NULL
, MAKEINTRESOURCEW( IDC_ARROW
) );
2833 cursor_hand
= LoadCursorW( NULL
, MAKEINTRESOURCEW( IDC_HAND
) );
2834 cursor_ibeam
= LoadCursorW( NULL
, MAKEINTRESOURCEW( IDC_IBEAM
) );
2835 cursor_reverse
= LoadCursorW( dll_instance
, MAKEINTRESOURCEW( OCR_REVERSE
) );
2838 cursor
= cursor_ibeam
;
2840 if ((editor
->nSelectionType
== stLine
&& editor
->bMouseCaptured
) ||
2841 (!editor
->bEmulateVersion10
&& y
< editor
->rcFormat
.top
&& x
< editor
->rcFormat
.left
))
2842 cursor
= cursor_reverse
;
2843 else if (y
< editor
->rcFormat
.top
|| y
> editor
->rcFormat
.bottom
)
2845 if (editor
->bEmulateVersion10
) cursor
= cursor_arrow
;
2846 else cursor
= cursor_ibeam
;
2848 else if (x
< editor
->rcFormat
.left
) cursor
= cursor_reverse
;
2849 else if (cursor_from_coords( editor
, x
, y
, &pos
))
2851 ME_Run
*run
= pos
.run
;
2853 if (is_link( run
)) cursor
= cursor_hand
;
2855 else if (ME_IsSelection( editor
))
2858 int offset
= ME_GetCursorOfs( &pos
);
2860 ME_GetSelectionOfs( editor
, &start
, &end
);
2861 if (start
<= offset
&& end
>= offset
) cursor
= cursor_arrow
;
2865 ITextHost_TxSetCursor( editor
->texthost
, cursor
, cursor
== cursor_ibeam
);
2868 static LONG
ME_GetSelectionType(ME_TextEditor
*editor
)
2870 LONG sel_type
= SEL_EMPTY
;
2873 ME_GetSelectionOfs(editor
, &start
, &end
);
2875 sel_type
= SEL_EMPTY
;
2878 LONG object_count
= 0, character_count
= 0;
2881 for (i
= 0; i
< end
- start
; i
++)
2885 cursor_from_char_ofs( editor
, start
+ i
, &cursor
);
2886 if (cursor
.run
->reobj
) object_count
++;
2887 else character_count
++;
2888 if (character_count
>= 2 && object_count
>= 2)
2889 return (SEL_TEXT
| SEL_MULTICHAR
| SEL_OBJECT
| SEL_MULTIOBJECT
);
2891 if (character_count
)
2893 sel_type
|= SEL_TEXT
;
2894 if (character_count
>= 2)
2895 sel_type
|= SEL_MULTICHAR
;
2899 sel_type
|= SEL_OBJECT
;
2900 if (object_count
>= 2)
2901 sel_type
|= SEL_MULTIOBJECT
;
2907 static BOOL
ME_ShowContextMenu(ME_TextEditor
*editor
, int x
, int y
)
2914 if (!editor
->lpOleCallback
|| !editor
->have_texthost2
) return FALSE
;
2915 if (FAILED( ITextHost2_TxGetWindow( editor
->texthost
, &hwnd
))) return FALSE
;
2916 parent
= GetParent( hwnd
);
2917 if (!parent
) parent
= hwnd
;
2919 ME_GetSelectionOfs( editor
, &selrange
.cpMin
, &selrange
.cpMax
);
2920 seltype
= ME_GetSelectionType( editor
);
2921 if (SUCCEEDED( IRichEditOleCallback_GetContextMenu( editor
->lpOleCallback
, seltype
, NULL
, &selrange
, &menu
) ))
2923 TrackPopupMenu( menu
, TPM_LEFTALIGN
| TPM_RIGHTBUTTON
, x
, y
, 0, parent
, NULL
);
2924 DestroyMenu( menu
);
2929 ME_TextEditor
*ME_MakeEditor(ITextHost
*texthost
, BOOL bEmulateVersion10
)
2931 ME_TextEditor
*ed
= heap_alloc(sizeof(*ed
));
2936 ed
->sizeWindow
.cx
= ed
->sizeWindow
.cy
= 0;
2937 if (ITextHost_QueryInterface( texthost
, &IID_ITextHost2
, (void **)&ed
->texthost
) == S_OK
)
2939 ITextHost_Release( texthost
);
2940 ed
->have_texthost2
= TRUE
;
2944 ed
->texthost
= (ITextHost2
*)texthost
;
2945 ed
->have_texthost2
= FALSE
;
2948 ed
->bEmulateVersion10
= bEmulateVersion10
;
2949 ed
->in_place_active
= FALSE
;
2951 ITextHost_TxGetPropertyBits( ed
->texthost
, TXTBIT_RICHTEXT
| TXTBIT_MULTILINE
| TXTBIT_READONLY
|
2952 TXTBIT_USEPASSWORD
| TXTBIT_HIDESELECTION
| TXTBIT_SAVESELECTION
|
2953 TXTBIT_AUTOWORDSEL
| TXTBIT_VERTICAL
| TXTBIT_WORDWRAP
| TXTBIT_ALLOWBEEP
|
2956 ITextHost_TxGetScrollBars( ed
->texthost
, &ed
->scrollbars
);
2957 ed
->pBuffer
= ME_MakeText();
2958 ed
->nZoomNumerator
= ed
->nZoomDenominator
= 0;
2959 ed
->nAvailWidth
= 0; /* wrap to client area */
2960 list_init( &ed
->style_list
);
2961 ME_MakeFirstParagraph(ed
);
2962 /* The four cursors are for:
2963 * 0 - The position where the caret is shown
2964 * 1 - The anchored end of the selection (for normal selection)
2965 * 2 & 3 - The anchored start and end respectively for word, line,
2966 * or paragraph selection.
2969 ed
->pCursors
= heap_alloc(ed
->nCursors
* sizeof(*ed
->pCursors
));
2970 ME_SetCursorToStart(ed
, &ed
->pCursors
[0]);
2971 ed
->pCursors
[1] = ed
->pCursors
[0];
2972 ed
->pCursors
[2] = ed
->pCursors
[0];
2973 ed
->pCursors
[3] = ed
->pCursors
[1];
2974 ed
->nLastTotalLength
= ed
->nTotalLength
= 0;
2975 ed
->nLastTotalWidth
= ed
->nTotalWidth
= 0;
2978 ed
->nModifyStep
= 0;
2979 ed
->nTextLimit
= TEXT_LIMIT_DEFAULT
;
2980 list_init( &ed
->undo_stack
);
2981 list_init( &ed
->redo_stack
);
2982 ed
->nUndoStackSize
= 0;
2983 ed
->nUndoLimit
= STACK_SIZE_DEFAULT
;
2984 ed
->nUndoMode
= umAddToUndo
;
2985 ed
->undo_ctl_state
= undoActive
;
2986 ed
->nParagraphs
= 1;
2987 ed
->nLastSelStart
= ed
->nLastSelEnd
= 0;
2988 ed
->last_sel_start_para
= ed
->last_sel_end_para
= ed
->pCursors
[0].para
;
2989 ed
->bHideSelection
= FALSE
;
2990 ed
->pfnWordBreak
= NULL
;
2992 ed
->lpOleCallback
= NULL
;
2993 ed
->mode
= TM_MULTILEVELUNDO
| TM_MULTICODEPAGE
;
2994 ed
->mode
|= (ed
->props
& TXTBIT_RICHTEXT
) ? TM_RICHTEXT
: TM_PLAINTEXT
;
2995 ed
->AutoURLDetect_bEnable
= FALSE
;
2996 ed
->bHaveFocus
= FALSE
;
2997 ed
->bMouseCaptured
= FALSE
;
2998 ed
->caret_hidden
= FALSE
;
2999 ed
->caret_height
= 0;
3000 for (i
=0; i
<HFONT_CACHE_SIZE
; i
++)
3002 ed
->pFontCache
[i
].nRefs
= 0;
3003 ed
->pFontCache
[i
].nAge
= 0;
3004 ed
->pFontCache
[i
].hFont
= NULL
;
3007 ME_CheckCharOffsets(ed
);
3008 SetRectEmpty(&ed
->rcFormat
);
3009 hr
= ITextHost_TxGetSelectionBarWidth( ed
->texthost
, &selbarwidth
);
3010 /* FIXME: Convert selbarwidth from HIMETRIC to pixels */
3011 if (hr
== S_OK
&& selbarwidth
) ed
->selofs
= SELECTIONBAR_WIDTH
;
3012 else ed
->selofs
= 0;
3013 ed
->nSelectionType
= stPosition
;
3015 ed
->password_char
= 0;
3016 if (ed
->props
& TXTBIT_USEPASSWORD
)
3017 ITextHost_TxGetPasswordChar( ed
->texthost
, &ed
->password_char
);
3019 ed
->bWordWrap
= (ed
->props
& TXTBIT_WORDWRAP
) && (ed
->props
& TXTBIT_MULTILINE
);
3021 ed
->notified_cr
.cpMin
= ed
->notified_cr
.cpMax
= 0;
3023 /* Default scrollbar information */
3024 ed
->vert_si
.cbSize
= sizeof(SCROLLINFO
);
3025 ed
->vert_si
.nMin
= 0;
3026 ed
->vert_si
.nMax
= 0;
3027 ed
->vert_si
.nPage
= 0;
3028 ed
->vert_si
.nPos
= 0;
3029 ed
->vert_sb_enabled
= 0;
3031 ed
->horz_si
.cbSize
= sizeof(SCROLLINFO
);
3032 ed
->horz_si
.nMin
= 0;
3033 ed
->horz_si
.nMax
= 0;
3034 ed
->horz_si
.nPage
= 0;
3035 ed
->horz_si
.nPos
= 0;
3036 ed
->horz_sb_enabled
= 0;
3038 if (ed
->scrollbars
& ES_DISABLENOSCROLL
)
3040 if (ed
->scrollbars
& WS_VSCROLL
)
3042 ITextHost_TxSetScrollRange( ed
->texthost
, SB_VERT
, 0, 1, TRUE
);
3043 ITextHost_TxEnableScrollBar( ed
->texthost
, SB_VERT
, ESB_DISABLE_BOTH
);
3045 if (ed
->scrollbars
& WS_HSCROLL
)
3047 ITextHost_TxSetScrollRange( ed
->texthost
, SB_HORZ
, 0, 1, TRUE
);
3048 ITextHost_TxEnableScrollBar( ed
->texthost
, SB_HORZ
, ESB_DISABLE_BOTH
);
3052 ed
->wheel_remain
= 0;
3054 ed
->back_style
= TXTBACK_OPAQUE
;
3055 ITextHost_TxGetBackStyle( ed
->texthost
, &ed
->back_style
);
3057 list_init( &ed
->reobj_list
);
3058 OleInitialize(NULL
);
3063 void ME_DestroyEditor(ME_TextEditor
*editor
)
3065 ME_DisplayItem
*p
= editor
->pBuffer
->pFirst
, *pNext
= NULL
;
3066 ME_Style
*s
, *cursor2
;
3069 ME_ClearTempStyle(editor
);
3070 ME_EmptyUndoStack(editor
);
3071 editor
->pBuffer
->pFirst
= NULL
;
3075 if (p
->type
== diParagraph
)
3076 para_destroy( editor
, &p
->member
.para
);
3078 ME_DestroyDisplayItem(p
);
3082 LIST_FOR_EACH_ENTRY_SAFE( s
, cursor2
, &editor
->style_list
, ME_Style
, entry
)
3083 ME_DestroyStyle( s
);
3085 ME_ReleaseStyle(editor
->pBuffer
->pDefaultStyle
);
3086 for (i
=0; i
<HFONT_CACHE_SIZE
; i
++)
3088 if (editor
->pFontCache
[i
].hFont
)
3089 DeleteObject(editor
->pFontCache
[i
].hFont
);
3091 if(editor
->lpOleCallback
)
3092 IRichEditOleCallback_Release(editor
->lpOleCallback
);
3096 heap_free(editor
->pBuffer
);
3097 heap_free(editor
->pCursors
);
3101 static inline int get_default_line_height( ME_TextEditor
*editor
)
3105 if (editor
->pBuffer
&& editor
->pBuffer
->pDefaultStyle
)
3106 height
= editor
->pBuffer
->pDefaultStyle
->tm
.tmHeight
;
3107 if (height
<= 0) height
= 24;
3112 static inline int calc_wheel_change( int *remain
, int amount_per_click
)
3114 int change
= amount_per_click
* (float)*remain
/ WHEEL_DELTA
;
3115 *remain
-= WHEEL_DELTA
* change
/ amount_per_click
;
3119 void link_notify(ME_TextEditor
*editor
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
3122 ME_Cursor cursor
; /* The start of the clicked text. */
3126 x
= (short)LOWORD(lParam
);
3127 y
= (short)HIWORD(lParam
);
3128 if (!cursor_from_coords( editor
, x
, y
, &cursor
)) return;
3130 if (is_link( cursor
.run
))
3131 { /* The clicked run has CFE_LINK set */
3132 info
.nmhdr
.hwndFrom
= NULL
;
3133 info
.nmhdr
.idFrom
= 0;
3134 info
.nmhdr
.code
= EN_LINK
;
3136 info
.wParam
= wParam
;
3137 info
.lParam
= lParam
;
3140 /* find the first contiguous run with CFE_LINK set */
3141 info
.chrg
.cpMin
= ME_GetCursorOfs(&cursor
);
3143 while ((run
= run_prev( run
)) && is_link( run
))
3144 info
.chrg
.cpMin
-= run
->len
;
3146 /* find the last contiguous run with CFE_LINK set */
3147 info
.chrg
.cpMax
= ME_GetCursorOfs(&cursor
) + cursor
.run
->len
;
3149 while ((run
= run_next( run
)) && is_link( run
))
3150 info
.chrg
.cpMax
+= run
->len
;
3152 ITextHost_TxNotify(editor
->texthost
, info
.nmhdr
.code
, &info
);
3156 void ME_ReplaceSel(ME_TextEditor
*editor
, BOOL can_undo
, const WCHAR
*str
, int len
)
3162 nStartCursor
= ME_GetSelectionOfs(editor
, &from
, &to
);
3163 style
= ME_GetSelectionInsertStyle(editor
);
3164 ME_InternalDeleteText(editor
, &editor
->pCursors
[nStartCursor
], to
-from
, FALSE
);
3165 ME_InsertTextFromCursor(editor
, 0, str
, len
, style
);
3166 ME_ReleaseStyle(style
);
3167 /* drop temporary style if line end */
3169 * FIXME question: does abc\n mean: put abc,
3170 * clear temp style, put \n? (would require a change)
3172 if (len
>0 && str
[len
-1] == '\n')
3173 ME_ClearTempStyle(editor
);
3174 ME_CommitUndo(editor
);
3175 ME_UpdateSelectionLinkAttribute(editor
);
3177 ME_EmptyUndoStack(editor
);
3178 ME_UpdateRepaint(editor
, FALSE
);
3181 static void ME_SetText(ME_TextEditor
*editor
, void *text
, BOOL unicode
)
3183 LONG codepage
= unicode
? CP_UNICODE
: CP_ACP
;
3186 LPWSTR wszText
= ME_ToUnicode(codepage
, text
, &textLen
);
3187 ME_InsertTextFromCursor(editor
, 0, wszText
, textLen
, editor
->pBuffer
->pDefaultStyle
);
3188 ME_EndToUnicode(codepage
, wszText
);
3191 static LRESULT
handle_EM_SETCHARFORMAT( ME_TextEditor
*editor
, WPARAM flags
, const CHARFORMAT2W
*fmt_in
)
3194 BOOL changed
= TRUE
;
3195 ME_Cursor start
, end
;
3197 if (!cfany_to_cf2w( &fmt
, fmt_in
)) return 0;
3199 if (flags
& SCF_ALL
)
3201 if (editor
->mode
& TM_PLAINTEXT
)
3203 ME_SetDefaultCharFormat( editor
, &fmt
);
3207 ME_SetCursorToStart( editor
, &start
);
3208 ME_SetCharFormat( editor
, &start
, NULL
, &fmt
);
3209 editor
->nModifyStep
= 1;
3212 else if (flags
& SCF_SELECTION
)
3214 if (editor
->mode
& TM_PLAINTEXT
) return 0;
3215 if (flags
& SCF_WORD
)
3217 end
= editor
->pCursors
[0];
3218 ME_MoveCursorWords( editor
, &end
, +1 );
3220 ME_MoveCursorWords( editor
, &start
, -1 );
3221 ME_SetCharFormat( editor
, &start
, &end
, &fmt
);
3223 changed
= ME_IsSelection( editor
);
3224 ME_SetSelectionCharFormat( editor
, &fmt
);
3225 if (changed
) editor
->nModifyStep
= 1;
3227 else /* SCF_DEFAULT */
3229 ME_SetDefaultCharFormat( editor
, &fmt
);
3232 ME_CommitUndo( editor
);
3235 ME_WrapMarkedParagraphs( editor
);
3236 ME_UpdateScrollBar( editor
);
3241 #define UNSUPPORTED_MSG(e) \
3243 FIXME(#e ": stub\n"); \
3244 *phresult = S_FALSE; \
3247 /* Handle messages for windowless and windowed richedit controls.
3249 * The LRESULT that is returned is a return value for window procs,
3250 * and the phresult parameter is the COM return code needed by the
3251 * text services interface. */
3252 LRESULT
editor_handle_message( ME_TextEditor
*editor
, UINT msg
, WPARAM wParam
,
3253 LPARAM lParam
, HRESULT
* phresult
)
3259 UNSUPPORTED_MSG(EM_DISPLAYBAND
)
3260 UNSUPPORTED_MSG(EM_FINDWORDBREAK
)
3261 UNSUPPORTED_MSG(EM_FMTLINES
)
3262 UNSUPPORTED_MSG(EM_FORMATRANGE
)
3263 UNSUPPORTED_MSG(EM_GETBIDIOPTIONS
)
3264 UNSUPPORTED_MSG(EM_GETEDITSTYLE
)
3265 UNSUPPORTED_MSG(EM_GETIMECOMPMODE
)
3266 UNSUPPORTED_MSG(EM_GETIMESTATUS
)
3267 UNSUPPORTED_MSG(EM_SETIMESTATUS
)
3268 UNSUPPORTED_MSG(EM_GETLANGOPTIONS
)
3269 UNSUPPORTED_MSG(EM_GETREDONAME
)
3270 UNSUPPORTED_MSG(EM_GETTYPOGRAPHYOPTIONS
)
3271 UNSUPPORTED_MSG(EM_GETUNDONAME
)
3272 UNSUPPORTED_MSG(EM_GETWORDBREAKPROCEX
)
3273 UNSUPPORTED_MSG(EM_SETBIDIOPTIONS
)
3274 UNSUPPORTED_MSG(EM_SETEDITSTYLE
)
3275 UNSUPPORTED_MSG(EM_SETLANGOPTIONS
)
3276 UNSUPPORTED_MSG(EM_SETMARGINS
)
3277 UNSUPPORTED_MSG(EM_SETPALETTE
)
3278 UNSUPPORTED_MSG(EM_SETTABSTOPS
)
3279 UNSUPPORTED_MSG(EM_SETTYPOGRAPHYOPTIONS
)
3280 UNSUPPORTED_MSG(EM_SETWORDBREAKPROCEX
)
3282 /* Messages specific to Richedit controls */
3285 return ME_StreamIn(editor
, wParam
, (EDITSTREAM
*)lParam
, TRUE
);
3287 return ME_StreamOut(editor
, wParam
, (EDITSTREAM
*)lParam
);
3288 case EM_EMPTYUNDOBUFFER
:
3289 ME_EmptyUndoStack(editor
);
3293 /* Note: wParam/lParam can be NULL */
3295 LONG
*pfrom
= wParam
? (LONG
*)wParam
: &from
;
3296 LONG
*pto
= lParam
? (LONG
*)lParam
: &to
;
3297 ME_GetSelectionOfs(editor
, pfrom
, pto
);
3298 if ((*pfrom
|*pto
) & 0xFFFF0000)
3300 return MAKELONG(*pfrom
,*pto
);
3304 CHARRANGE
*pRange
= (CHARRANGE
*)lParam
;
3305 ME_GetSelectionOfs(editor
, &pRange
->cpMin
, &pRange
->cpMax
);
3306 TRACE("EM_EXGETSEL = (%ld,%ld)\n", pRange
->cpMin
, pRange
->cpMax
);
3309 case EM_SETUNDOLIMIT
:
3311 editor_enable_undo(editor
);
3312 if ((int)wParam
< 0)
3313 editor
->nUndoLimit
= STACK_SIZE_DEFAULT
;
3315 editor
->nUndoLimit
= min(wParam
, STACK_SIZE_MAX
);
3316 /* Setting a max stack size keeps wine from getting killed
3317 for hogging memory. Windows allocates all this memory at once, so
3318 no program would realistically set a value above our maximum. */
3319 return editor
->nUndoLimit
;
3322 return !list_empty( &editor
->undo_stack
);
3324 return !list_empty( &editor
->redo_stack
);
3325 case WM_UNDO
: /* FIXME: actually not the same */
3327 return ME_Undo(editor
);
3329 return ME_Redo(editor
);
3330 case EM_SETFONTSIZE
:
3333 LONG tmp_size
, size
;
3334 BOOL is_increase
= ((LONG
)wParam
> 0);
3336 if (editor
->mode
& TM_PLAINTEXT
)
3339 cf
.cbSize
= sizeof(cf
);
3340 cf
.dwMask
= CFM_SIZE
;
3341 ME_GetSelectionCharFormat(editor
, &cf
);
3342 tmp_size
= (cf
.yHeight
/ 20) + wParam
;
3346 else if (tmp_size
> 12 && tmp_size
< 28 && tmp_size
% 2)
3347 size
= tmp_size
+ (is_increase
? 1 : -1);
3348 else if (tmp_size
> 28 && tmp_size
< 36)
3349 size
= is_increase
? 36 : 28;
3350 else if (tmp_size
> 36 && tmp_size
< 48)
3351 size
= is_increase
? 48 : 36;
3352 else if (tmp_size
> 48 && tmp_size
< 72)
3353 size
= is_increase
? 72 : 48;
3354 else if (tmp_size
> 72 && tmp_size
< 80)
3355 size
= is_increase
? 80 : 72;
3356 else if (tmp_size
> 80 && tmp_size
< 1638)
3357 size
= 10 * (is_increase
? (tmp_size
/ 10 + 1) : (tmp_size
/ 10));
3358 else if (tmp_size
>= 1638)
3363 cf
.yHeight
= size
* 20; /* convert twips to points */
3364 ME_SetSelectionCharFormat(editor
, &cf
);
3365 ME_CommitUndo(editor
);
3366 ME_WrapMarkedParagraphs(editor
);
3367 ME_UpdateScrollBar(editor
);
3373 return set_selection( editor
, wParam
, lParam
);
3375 case EM_SETSCROLLPOS
:
3377 POINT
*point
= (POINT
*)lParam
;
3378 scroll_abs( editor
, point
->x
, point
->y
, TRUE
);
3381 case EM_AUTOURLDETECT
:
3383 if (wParam
==1 || wParam
==0)
3385 editor
->AutoURLDetect_bEnable
= (BOOL
)wParam
;
3388 return E_INVALIDARG
;
3390 case EM_GETAUTOURLDETECT
:
3392 return editor
->AutoURLDetect_bEnable
;
3396 CHARRANGE range
= *(CHARRANGE
*)lParam
;
3398 return set_selection( editor
, range
.cpMin
, range
.cpMax
);
3403 SETTEXTEX
*pStruct
= (SETTEXTEX
*)wParam
;
3407 BOOL bRtf
, bUnicode
, bSelection
, bUTF8
;
3408 int oldModify
= editor
->nModifyStep
;
3409 static const char utf8_bom
[] = {0xef, 0xbb, 0xbf};
3411 if (!pStruct
) return 0;
3413 /* If we detect ascii rtf at the start of the string,
3414 * we know it isn't unicode. */
3415 bRtf
= (lParam
&& (!strncmp((char *)lParam
, "{\\rtf", 5) ||
3416 !strncmp((char *)lParam
, "{\\urtf", 6)));
3417 bUnicode
= !bRtf
&& pStruct
->codepage
== CP_UNICODE
;
3418 bUTF8
= (lParam
&& (!strncmp((char *)lParam
, utf8_bom
, 3)));
3420 TRACE("EM_SETTEXTEX - %s, flags %ld, cp %d\n",
3421 bUnicode
? debugstr_w((LPCWSTR
)lParam
) : debugstr_a((LPCSTR
)lParam
),
3422 pStruct
->flags
, pStruct
->codepage
);
3424 bSelection
= (pStruct
->flags
& ST_SELECTION
) != 0;
3426 int nStartCursor
= ME_GetSelectionOfs(editor
, &from
, &to
);
3427 style
= ME_GetSelectionInsertStyle(editor
);
3428 ME_InternalDeleteText(editor
, &editor
->pCursors
[nStartCursor
], to
- from
, FALSE
);
3431 ME_SetCursorToStart(editor
, &start
);
3432 ME_InternalDeleteText(editor
, &start
, ME_GetTextLength(editor
), FALSE
);
3433 style
= editor
->pBuffer
->pDefaultStyle
;
3437 ME_StreamInRTFString(editor
, bSelection
, (char *)lParam
);
3439 /* FIXME: The length returned doesn't include the rtf control
3440 * characters, only the actual text. */
3441 len
= lParam
? strlen((char *)lParam
) : 0;
3444 if (bUTF8
&& !bUnicode
) {
3445 wszText
= ME_ToUnicode(CP_UTF8
, (void *)(lParam
+3), &len
);
3446 ME_InsertTextFromCursor(editor
, 0, wszText
, len
, style
);
3447 ME_EndToUnicode(CP_UTF8
, wszText
);
3449 wszText
= ME_ToUnicode(pStruct
->codepage
, (void *)lParam
, &len
);
3450 ME_InsertTextFromCursor(editor
, 0, wszText
, len
, style
);
3451 ME_EndToUnicode(pStruct
->codepage
, wszText
);
3456 ME_ReleaseStyle(style
);
3457 ME_UpdateSelectionLinkAttribute(editor
);
3461 ME_SetCursorToStart(editor
, &cursor
);
3462 ME_UpdateLinkAttribute(editor
, &cursor
, INT_MAX
);
3464 ME_CommitUndo(editor
);
3465 if (!(pStruct
->flags
& ST_KEEPUNDO
))
3467 editor
->nModifyStep
= oldModify
;
3468 ME_EmptyUndoStack(editor
);
3470 ME_UpdateRepaint(editor
, FALSE
);
3473 case EM_SELECTIONTYPE
:
3474 return ME_GetSelectionType(editor
);
3476 return editor
->nModifyStep
== 0 ? 0 : -1;
3480 editor
->nModifyStep
= 1;
3482 editor
->nModifyStep
= 0;
3486 case EM_SETEVENTMASK
:
3488 DWORD nOldMask
= editor
->nEventMask
;
3490 editor
->nEventMask
= lParam
;
3493 case EM_GETEVENTMASK
:
3494 return editor
->nEventMask
;
3495 case EM_SETCHARFORMAT
:
3496 return handle_EM_SETCHARFORMAT( editor
, wParam
, (CHARFORMAT2W
*)lParam
);
3497 case EM_GETCHARFORMAT
:
3499 CHARFORMAT2W tmp
, *dst
= (CHARFORMAT2W
*)lParam
;
3500 if (dst
->cbSize
!= sizeof(CHARFORMATA
) &&
3501 dst
->cbSize
!= sizeof(CHARFORMATW
) &&
3502 dst
->cbSize
!= sizeof(CHARFORMAT2A
) &&
3503 dst
->cbSize
!= sizeof(CHARFORMAT2W
))
3505 tmp
.cbSize
= sizeof(tmp
);
3507 ME_GetDefaultCharFormat(editor
, &tmp
);
3509 ME_GetSelectionCharFormat(editor
, &tmp
);
3510 cf2w_to_cfany(dst
, &tmp
);
3513 case EM_SETPARAFORMAT
:
3515 BOOL result
= editor_set_selection_para_fmt( editor
, (PARAFORMAT2
*)lParam
);
3516 ME_WrapMarkedParagraphs(editor
);
3517 ME_UpdateScrollBar(editor
);
3518 ME_CommitUndo(editor
);
3521 case EM_GETPARAFORMAT
:
3522 editor_get_selection_para_fmt( editor
, (PARAFORMAT2
*)lParam
);
3523 return ((PARAFORMAT2
*)lParam
)->dwMask
;
3524 case EM_GETFIRSTVISIBLELINE
:
3526 ME_Paragraph
*para
= editor_first_para( editor
);
3528 int y
= editor
->vert_si
.nPos
;
3531 while (para_next( para
))
3533 if (y
< para
->pt
.y
+ para
->nHeight
) break;
3534 count
+= para
->nRows
;
3535 para
= para_next( para
);
3538 row
= para_first_row( para
);
3541 if (y
< para
->pt
.y
+ row
->pt
.y
+ row
->nHeight
) break;
3543 row
= row_next( row
);
3547 case EM_HIDESELECTION
:
3549 editor
->bHideSelection
= (wParam
!= 0);
3550 ME_InvalidateSelection(editor
);
3555 if (!(editor
->props
& TXTBIT_MULTILINE
))
3557 ME_ScrollDown( editor
, lParam
* get_default_line_height( editor
) );
3563 int nStartCursor
= ME_GetSelectionOfs(editor
, &from
, &to
);
3564 ME_InternalDeleteText(editor
, &editor
->pCursors
[nStartCursor
], to
-from
, FALSE
);
3565 ME_CommitUndo(editor
);
3566 ME_UpdateRepaint(editor
, TRUE
);
3571 WCHAR
*text
= (WCHAR
*)lParam
;
3572 int len
= text
? lstrlenW( text
) : 0;
3574 TRACE( "EM_REPLACESEL - %s\n", debugstr_w( text
) );
3575 ME_ReplaceSel( editor
, !!wParam
, text
, len
);
3578 case EM_SCROLLCARET
:
3579 editor_ensure_visible( editor
, &editor
->pCursors
[0] );
3586 BOOL bRepaint
= LOWORD(lParam
);
3589 wParam
= (WPARAM
)GetStockObject(SYSTEM_FONT
);
3591 if (!GetObjectW((HGDIOBJ
)wParam
, sizeof(LOGFONTW
), &lf
))
3594 hDC
= ITextHost_TxGetDC(editor
->texthost
);
3595 ME_CharFormatFromLogFont(hDC
, &lf
, &fmt
);
3596 ITextHost_TxReleaseDC(editor
->texthost
, hDC
);
3597 if (editor
->mode
& TM_RICHTEXT
) {
3599 ME_SetCursorToStart(editor
, &start
);
3600 ME_SetCharFormat(editor
, &start
, NULL
, &fmt
);
3602 ME_SetDefaultCharFormat(editor
, &fmt
);
3604 ME_CommitUndo(editor
);
3605 editor_mark_rewrap_all( editor
);
3606 ME_WrapMarkedParagraphs(editor
);
3607 ME_UpdateScrollBar(editor
);
3615 ME_SetCursorToStart(editor
, &cursor
);
3616 ME_InternalDeleteText(editor
, &cursor
, ME_GetTextLength(editor
), FALSE
);
3619 TRACE("WM_SETTEXT lParam==%Ix\n",lParam
);
3620 if (!strncmp((char *)lParam
, "{\\rtf", 5) ||
3621 !strncmp((char *)lParam
, "{\\urtf", 6))
3623 /* Undocumented: WM_SETTEXT supports RTF text */
3624 ME_StreamInRTFString(editor
, 0, (char *)lParam
);
3627 ME_SetText( editor
, (void*)lParam
, TRUE
);
3630 TRACE("WM_SETTEXT - NULL\n");
3631 ME_SetCursorToStart(editor
, &cursor
);
3632 ME_UpdateLinkAttribute(editor
, &cursor
, INT_MAX
);
3633 set_selection_cursors(editor
, 0, 0);
3634 editor
->nModifyStep
= 0;
3635 ME_CommitUndo(editor
);
3636 ME_EmptyUndoStack(editor
);
3637 ME_UpdateRepaint(editor
, FALSE
);
3641 return paste_special( editor
, 0, NULL
, TRUE
);
3643 case WM_MBUTTONDOWN
:
3647 case EM_PASTESPECIAL
:
3648 paste_special( editor
, wParam
, (REPASTESPECIAL
*)lParam
, FALSE
);
3652 copy_or_cut(editor
, msg
== WM_CUT
);
3654 case WM_GETTEXTLENGTH
:
3656 GETTEXTLENGTHEX how
;
3657 how
.flags
= GTL_CLOSE
| (editor
->bEmulateVersion10
? 0 : GTL_USECRLF
) | GTL_NUMCHARS
;
3658 how
.codepage
= CP_UNICODE
;
3659 return ME_GetTextLengthEx(editor
, &how
);
3661 case EM_GETTEXTLENGTHEX
:
3662 return ME_GetTextLengthEx(editor
, (GETTEXTLENGTHEX
*)wParam
);
3666 ex
.cb
= wParam
* sizeof(WCHAR
);
3667 ex
.flags
= GT_USECRLF
;
3668 ex
.codepage
= CP_UNICODE
;
3669 ex
.lpDefaultChar
= NULL
;
3670 ex
.lpUsedDefChar
= NULL
;
3671 return ME_GetTextEx(editor
, &ex
, lParam
);
3674 return ME_GetTextEx(editor
, (GETTEXTEX
*)wParam
, lParam
);
3678 int nStartCur
= ME_GetSelectionOfs(editor
, &nFrom
, &nTo
);
3679 ME_Cursor
*from
= &editor
->pCursors
[nStartCur
];
3680 return get_text_range( editor
, (WCHAR
*)lParam
, from
, nTo
- nFrom
);
3682 case EM_GETSCROLLPOS
:
3684 POINT
*point
= (POINT
*)lParam
;
3685 point
->x
= editor
->horz_si
.nPos
;
3686 point
->y
= editor
->vert_si
.nPos
;
3687 /* 16-bit scaled value is returned as stored in scrollinfo */
3688 if (editor
->horz_si
.nMax
> 0xffff)
3689 point
->x
= MulDiv(point
->x
, 0xffff, editor
->horz_si
.nMax
);
3690 if (editor
->vert_si
.nMax
> 0xffff)
3691 point
->y
= MulDiv(point
->y
, 0xffff, editor
->vert_si
.nMax
);
3694 case EM_GETTEXTRANGE
:
3696 TEXTRANGEW
*rng
= (TEXTRANGEW
*)lParam
;
3698 int nStart
= rng
->chrg
.cpMin
;
3699 int nEnd
= rng
->chrg
.cpMax
;
3700 int textlength
= ME_GetTextLength(editor
);
3702 TRACE( "EM_GETTEXTRANGE min = %ld max = %ld textlength = %d\n", rng
->chrg
.cpMin
, rng
->chrg
.cpMax
, textlength
);
3703 if (nStart
< 0) return 0;
3704 if ((nStart
== 0 && nEnd
== -1) || nEnd
> textlength
)
3706 if (nStart
>= nEnd
) return 0;
3708 cursor_from_char_ofs( editor
, nStart
, &start
);
3709 return get_text_range( editor
, rng
->lpstrText
, &start
, nEnd
- nStart
);
3715 const unsigned int nMaxChars
= *(WORD
*) lParam
;
3716 unsigned int nCharsLeft
= nMaxChars
;
3717 char *dest
= (char *) lParam
;
3718 ME_Cursor start
, end
;
3720 TRACE( "EM_GETLINE: row=%d, nMaxChars=%d\n", (int)wParam
, nMaxChars
);
3722 row
= row_from_row_number( editor
, wParam
);
3723 if (row
== NULL
) return 0;
3725 row_first_cursor( row
, &start
);
3726 row_end_cursor( row
, &end
, TRUE
);
3732 int ofs
= (run
== start
.run
) ? start
.nOffset
: 0;
3733 int len
= (run
== end
.run
) ? end
.nOffset
: run
->len
;
3735 str
= get_text( run
, ofs
);
3736 nCopy
= min( nCharsLeft
, len
);
3738 memcpy(dest
, str
, nCopy
* sizeof(WCHAR
));
3739 dest
+= nCopy
* sizeof(WCHAR
);
3740 nCharsLeft
-= nCopy
;
3741 if (run
== end
.run
) break;
3742 run
= row_next_run( row
, run
);
3745 /* append line termination, space allowing */
3746 if (nCharsLeft
> 0) *((WCHAR
*)dest
) = '\0';
3748 TRACE("EM_GETLINE: got %u characters\n", nMaxChars
- nCharsLeft
);
3749 return nMaxChars
- nCharsLeft
;
3751 case EM_GETLINECOUNT
:
3753 int count
= editor
->total_rows
;
3754 ME_Run
*prev_run
, *last_run
;
3756 last_run
= para_end_run( para_prev( editor_end_para( editor
) ) );
3757 prev_run
= run_prev_all_paras( last_run
);
3759 if (editor
->bEmulateVersion10
&& prev_run
&& last_run
->nCharOfs
== 0 &&
3760 prev_run
->len
== 1 && *get_text( prev_run
, 0 ) == '\r')
3762 /* In 1.0 emulation, the last solitary \r at the very end of the text
3763 (if one exists) is NOT a line break.
3764 FIXME: this is an ugly hack. This should have a more regular model. */
3768 count
= max(1, count
);
3769 TRACE("EM_GETLINECOUNT: count==%d\n", count
);
3772 case EM_LINEFROMCHAR
:
3774 if (wParam
== -1) wParam
= ME_GetCursorOfs( editor
->pCursors
+ 1 );
3775 return row_number_from_char_ofs( editor
, wParam
);
3777 case EM_EXLINEFROMCHAR
:
3779 if (lParam
== -1) lParam
= ME_GetCursorOfs( editor
->pCursors
+ 1 );
3780 return row_number_from_char_ofs( editor
, lParam
);
3788 if (wParam
== -1) row
= row_from_cursor( editor
->pCursors
);
3789 else row
= row_from_row_number( editor
, wParam
);
3790 if (!row
) return -1;
3792 row_first_cursor( row
, &cursor
);
3793 ofs
= ME_GetCursorOfs( &cursor
);
3794 TRACE( "EM_LINEINDEX: nCharOfs==%d\n", ofs
);
3800 int start_ofs
, end_ofs
;
3803 if (wParam
> ME_GetTextLength(editor
))
3807 FIXME("EM_LINELENGTH: returning number of unselected characters on lines with selection unsupported.\n");
3810 cursor_from_char_ofs( editor
, wParam
, &cursor
);
3811 row
= row_from_cursor( &cursor
);
3812 row_first_cursor( row
, &cursor
);
3813 start_ofs
= ME_GetCursorOfs( &cursor
);
3814 row_end_cursor( row
, &cursor
, FALSE
);
3815 end_ofs
= ME_GetCursorOfs( &cursor
);
3816 TRACE( "EM_LINELENGTH(%Id)==%d\n", wParam
, end_ofs
- start_ofs
);
3817 return end_ofs
- start_ofs
;
3819 case EM_EXLIMITTEXT
:
3821 if ((int)lParam
< 0)
3824 editor
->nTextLimit
= 65536;
3826 editor
->nTextLimit
= (int) lParam
;
3832 editor
->nTextLimit
= 65536;
3834 editor
->nTextLimit
= (int) wParam
;
3837 case EM_GETLIMITTEXT
:
3839 return editor
->nTextLimit
;
3844 FINDTEXTW
*ft
= (FINDTEXTW
*)lParam
;
3845 return ME_FindText(editor
, wParam
, &ft
->chrg
, ft
->lpstrText
, NULL
);
3848 case EM_FINDTEXTEXW
:
3850 FINDTEXTEXW
*ex
= (FINDTEXTEXW
*)lParam
;
3851 return ME_FindText(editor
, wParam
, &ex
->chrg
, ex
->lpstrText
, &ex
->chrgText
);
3854 if (!wParam
|| !lParam
)
3856 *(int *)wParam
= editor
->nZoomNumerator
;
3857 *(int *)lParam
= editor
->nZoomDenominator
;
3860 return ME_SetZoom(editor
, wParam
, lParam
);
3861 case EM_CHARFROMPOS
:
3864 POINTL
*pt
= (POINTL
*)lParam
;
3866 cursor_from_coords(editor
, pt
->x
, pt
->y
, &cursor
);
3867 return ME_GetCursorOfs(&cursor
);
3869 case EM_POSFROMCHAR
:
3872 int nCharOfs
, nLength
;
3876 /* detect which API version we're dealing with */
3877 if (wParam
>= 0x40000)
3879 nLength
= ME_GetTextLength(editor
);
3880 nCharOfs
= min(nCharOfs
, nLength
);
3881 nCharOfs
= max(nCharOfs
, 0);
3883 cursor_from_char_ofs( editor
, nCharOfs
, &cursor
);
3884 pt
.y
= cursor
.run
->pt
.y
;
3885 pt
.x
= cursor
.run
->pt
.x
+
3886 ME_PointFromChar( editor
, cursor
.run
, cursor
.nOffset
, TRUE
);
3887 pt
.y
+= cursor
.para
->pt
.y
+ editor
->rcFormat
.top
;
3888 pt
.x
+= editor
->rcFormat
.left
;
3890 pt
.x
-= editor
->horz_si
.nPos
;
3891 pt
.y
-= editor
->vert_si
.nPos
;
3893 if (wParam
>= 0x40000) *(POINTL
*)wParam
= pt
;
3895 return (wParam
>= 0x40000) ? 0 : MAKELONG( pt
.x
, pt
.y
);
3897 case WM_LBUTTONDBLCLK
:
3898 case WM_LBUTTONDOWN
:
3900 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
3901 ITextHost_TxSetFocus(editor
->texthost
);
3902 ME_LButtonDown(editor
, (short)LOWORD(lParam
), (short)HIWORD(lParam
),
3903 ME_CalculateClickCount(editor
, msg
, wParam
, lParam
));
3904 ITextHost_TxSetCapture(editor
->texthost
, TRUE
);
3905 editor
->bMouseCaptured
= TRUE
;
3906 link_notify( editor
, msg
, wParam
, lParam
);
3910 if (editor
->bMouseCaptured
)
3911 ME_MouseMove(editor
, (short)LOWORD(lParam
), (short)HIWORD(lParam
));
3913 link_notify( editor
, msg
, wParam
, lParam
);
3916 if (editor
->bMouseCaptured
) {
3917 ITextHost_TxSetCapture(editor
->texthost
, FALSE
);
3918 editor
->bMouseCaptured
= FALSE
;
3920 if (editor
->nSelectionType
== stDocument
)
3921 editor
->nSelectionType
= stPosition
;
3924 link_notify( editor
, msg
, wParam
, lParam
);
3928 case WM_RBUTTONDOWN
:
3929 case WM_RBUTTONDBLCLK
:
3930 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
3931 link_notify( editor
, msg
, wParam
, lParam
);
3933 case WM_CONTEXTMENU
:
3934 if (!ME_ShowContextMenu(editor
, (short)LOWORD(lParam
), (short)HIWORD(lParam
)))
3938 editor
->bHaveFocus
= TRUE
;
3939 create_caret(editor
);
3940 update_caret(editor
);
3941 ITextHost_TxNotify( editor
->texthost
, EN_SETFOCUS
, NULL
);
3942 if (!editor
->bHideSelection
&& (editor
->props
& TXTBIT_HIDESELECTION
))
3943 ME_InvalidateSelection( editor
);
3946 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
3947 editor
->bHaveFocus
= FALSE
;
3948 editor
->wheel_remain
= 0;
3951 ITextHost_TxNotify( editor
->texthost
, EN_KILLFOCUS
, NULL
);
3952 if (!editor
->bHideSelection
&& (editor
->props
& TXTBIT_HIDESELECTION
))
3953 ME_InvalidateSelection( editor
);
3956 TRACE("editor wnd command = %d\n", LOWORD(wParam
));
3959 if (ME_KeyDown(editor
, LOWORD(wParam
)))
3963 return handle_wm_char( editor
, wParam
, lParam
);
3965 if (wParam
== UNICODE_NOCHAR
) return TRUE
;
3966 if (wParam
<= 0x000fffff)
3968 if (wParam
> 0xffff) /* convert to surrogates */
3971 handle_wm_char( editor
, (wParam
>> 10) + 0xd800, 0 );
3972 handle_wm_char( editor
, (wParam
& 0x03ff) + 0xdc00, 0 );
3975 handle_wm_char( editor
, wParam
, 0 );
3978 case EM_STOPGROUPTYPING
:
3979 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
3983 const int scrollUnit
= 7;
3985 switch(LOWORD(wParam
))
3988 scroll_abs( editor
, 0, 0, TRUE
);
3991 scroll_abs( editor
, editor
->horz_si
.nMax
- (int)editor
->horz_si
.nPage
,
3992 editor
->vert_si
.nMax
- (int)editor
->vert_si
.nPage
, TRUE
);
3995 ME_ScrollLeft(editor
, scrollUnit
);
3998 ME_ScrollRight(editor
, scrollUnit
);
4001 ME_ScrollLeft(editor
, editor
->sizeWindow
.cx
);
4004 ME_ScrollRight(editor
, editor
->sizeWindow
.cx
);
4007 case SB_THUMBPOSITION
:
4009 int pos
= HIWORD(wParam
);
4010 if (editor
->horz_si
.nMax
> 0xffff)
4011 pos
= MulDiv(pos
, editor
->horz_si
.nMax
, 0xffff);
4012 scroll_h_abs( editor
, pos
, FALSE
);
4018 case EM_SCROLL
: /* fall through */
4022 int lineHeight
= get_default_line_height( editor
);
4024 origNPos
= editor
->vert_si
.nPos
;
4026 switch(LOWORD(wParam
))
4029 scroll_abs( editor
, 0, 0, TRUE
);
4032 scroll_abs( editor
, editor
->horz_si
.nMax
- (int)editor
->horz_si
.nPage
,
4033 editor
->vert_si
.nMax
- (int)editor
->vert_si
.nPage
, TRUE
);
4036 ME_ScrollUp(editor
,lineHeight
);
4039 ME_ScrollDown(editor
,lineHeight
);
4042 ME_ScrollUp(editor
,editor
->sizeWindow
.cy
);
4045 ME_ScrollDown(editor
,editor
->sizeWindow
.cy
);
4048 case SB_THUMBPOSITION
:
4050 int pos
= HIWORD(wParam
);
4051 if (editor
->vert_si
.nMax
> 0xffff)
4052 pos
= MulDiv(pos
, editor
->vert_si
.nMax
, 0xffff);
4053 scroll_v_abs( editor
, pos
, FALSE
);
4057 if (msg
== EM_SCROLL
)
4058 return 0x00010000 | (((editor
->vert_si
.nPos
- origNPos
)/lineHeight
) & 0xffff);
4063 int delta
= GET_WHEEL_DELTA_WPARAM( wParam
);
4064 BOOL ctrl_is_down
= GetKeyState( VK_CONTROL
) & 0x8000;
4066 /* if scrolling changes direction, ignore left overs */
4067 if ((delta
< 0 && editor
->wheel_remain
< 0) ||
4068 (delta
> 0 && editor
->wheel_remain
> 0))
4069 editor
->wheel_remain
+= delta
;
4071 editor
->wheel_remain
= delta
;
4073 if (editor
->wheel_remain
)
4077 if (!editor
->nZoomNumerator
|| !editor
->nZoomDenominator
)
4081 numerator
= editor
->nZoomNumerator
* 100 / editor
->nZoomDenominator
;
4083 numerator
+= calc_wheel_change( &editor
->wheel_remain
, 10 );
4084 if (numerator
>= 10 && numerator
<= 500)
4085 ME_SetZoom(editor
, numerator
, 100);
4090 SystemParametersInfoW( SPI_GETWHEELSCROLLLINES
, 0, &max_lines
, 0 );
4092 lines
= calc_wheel_change( &editor
->wheel_remain
, (int)max_lines
);
4094 ME_ScrollDown( editor
, -lines
* get_default_line_height( editor
) );
4099 case EM_REQUESTRESIZE
:
4100 ME_SendRequestResize(editor
, TRUE
);
4102 /* IME messages to make richedit controls IME aware */
4103 case WM_IME_SETCONTEXT
:
4104 case WM_IME_CONTROL
:
4106 case WM_IME_COMPOSITIONFULL
:
4108 case WM_IME_STARTCOMPOSITION
:
4110 ME_DeleteSelection(editor
);
4111 editor
->imeStartIndex
= ME_GetCursorOfs(&editor
->pCursors
[0]);
4112 ME_CommitUndo(editor
);
4113 ME_UpdateRepaint(editor
, FALSE
);
4116 case WM_IME_COMPOSITION
:
4120 ME_Style
*style
= style_get_insert_style( editor
, editor
->pCursors
);
4121 hIMC
= ITextHost_TxImmGetContext(editor
->texthost
);
4122 ME_DeleteSelection(editor
);
4123 ME_SaveTempStyle(editor
, style
);
4124 if (lParam
& (GCS_RESULTSTR
|GCS_COMPSTR
))
4126 LPWSTR lpCompStr
= NULL
;
4128 DWORD dwIndex
= lParam
& GCS_RESULTSTR
;
4130 dwIndex
= GCS_COMPSTR
;
4132 dwBufLen
= ImmGetCompositionStringW(hIMC
, dwIndex
, NULL
, 0);
4133 lpCompStr
= HeapAlloc(GetProcessHeap(),0,dwBufLen
+ sizeof(WCHAR
));
4134 ImmGetCompositionStringW(hIMC
, dwIndex
, lpCompStr
, dwBufLen
);
4135 lpCompStr
[dwBufLen
/sizeof(WCHAR
)] = 0;
4136 ME_InsertTextFromCursor(editor
,0,lpCompStr
,dwBufLen
/sizeof(WCHAR
),style
);
4137 HeapFree(GetProcessHeap(), 0, lpCompStr
);
4139 if (dwIndex
== GCS_COMPSTR
)
4140 set_selection_cursors(editor
,editor
->imeStartIndex
,
4141 editor
->imeStartIndex
+ dwBufLen
/sizeof(WCHAR
));
4143 ME_ReleaseStyle(style
);
4144 ME_CommitUndo(editor
);
4145 ME_UpdateRepaint(editor
, FALSE
);
4148 case WM_IME_ENDCOMPOSITION
:
4150 ME_DeleteSelection(editor
);
4151 editor
->imeStartIndex
=-1;
4154 case EM_GETOLEINTERFACE
:
4155 IRichEditOle_AddRef( editor
->richole
);
4156 *(IRichEditOle
**)lParam
= editor
->richole
;
4159 case EM_SETOLECALLBACK
:
4160 if(editor
->lpOleCallback
)
4161 IRichEditOleCallback_Release(editor
->lpOleCallback
);
4162 editor
->lpOleCallback
= (IRichEditOleCallback
*)lParam
;
4163 if(editor
->lpOleCallback
)
4164 IRichEditOleCallback_AddRef(editor
->lpOleCallback
);
4166 case EM_GETWORDBREAKPROC
:
4167 return (LRESULT
)editor
->pfnWordBreak
;
4168 case EM_SETWORDBREAKPROC
:
4170 EDITWORDBREAKPROCW pfnOld
= editor
->pfnWordBreak
;
4172 editor
->pfnWordBreak
= (EDITWORDBREAKPROCW
)lParam
;
4173 return (LRESULT
)pfnOld
;
4175 case EM_GETTEXTMODE
:
4176 return editor
->mode
;
4177 case EM_SETTEXTMODE
:
4182 if (ME_GetTextLength(editor
) ||
4183 !list_empty( &editor
->undo_stack
) || !list_empty( &editor
->redo_stack
))
4184 return E_UNEXPECTED
;
4186 /* Check for mutually exclusive flags in adjacent bits of wParam */
4187 if ((wParam
& (TM_RICHTEXT
| TM_MULTILEVELUNDO
| TM_MULTICODEPAGE
)) &
4188 (wParam
& (TM_PLAINTEXT
| TM_SINGLELEVELUNDO
| TM_SINGLECODEPAGE
)) << 1)
4189 return E_INVALIDARG
;
4191 if (wParam
& (TM_RICHTEXT
| TM_PLAINTEXT
))
4193 mask
|= TM_RICHTEXT
| TM_PLAINTEXT
;
4194 changes
|= wParam
& (TM_RICHTEXT
| TM_PLAINTEXT
);
4195 if (wParam
& TM_PLAINTEXT
) {
4196 /* Clear selection since it should be possible to select the
4197 * end of text run for rich text */
4198 ME_InvalidateSelection(editor
);
4199 ME_SetCursorToStart(editor
, &editor
->pCursors
[0]);
4200 editor
->pCursors
[1] = editor
->pCursors
[0];
4201 /* plain text can only have the default style. */
4202 ME_ClearTempStyle(editor
);
4203 ME_AddRefStyle(editor
->pBuffer
->pDefaultStyle
);
4204 ME_ReleaseStyle( editor
->pCursors
[0].run
->style
);
4205 editor
->pCursors
[0].run
->style
= editor
->pBuffer
->pDefaultStyle
;
4208 /* FIXME: Currently no support for undo level and code page options */
4209 editor
->mode
= (editor
->mode
& ~mask
) | changes
;
4212 case EM_SETTARGETDEVICE
:
4215 BOOL
new = (lParam
== 0 && (editor
->props
& TXTBIT_MULTILINE
));
4216 if (editor
->nAvailWidth
|| editor
->bWordWrap
!= new)
4218 editor
->bWordWrap
= new;
4219 editor
->nAvailWidth
= 0; /* wrap to client area */
4220 ME_RewrapRepaint(editor
);
4223 int width
= max(0, lParam
);
4224 if ((editor
->props
& TXTBIT_MULTILINE
) &&
4225 (!editor
->bWordWrap
|| editor
->nAvailWidth
!= width
))
4227 editor
->nAvailWidth
= width
;
4228 editor
->bWordWrap
= TRUE
;
4229 ME_RewrapRepaint(editor
);
4231 FIXME("EM_SETTARGETDEVICE doesn't use non-NULL target devices\n");
4236 *phresult
= S_FALSE
;
4242 /* Fill buffer with srcChars unicode characters from the start cursor.
4244 * buffer: destination buffer
4245 * buflen: length of buffer in characters excluding the NULL terminator.
4246 * start: start of editor text to copy into buffer.
4247 * srcChars: Number of characters to use from the editor text.
4248 * bCRLF: if true, replaces all end of lines with \r\n pairs.
4250 * returns the number of characters written excluding the NULL terminator.
4252 * The written text is always NULL terminated.
4254 int ME_GetTextW(ME_TextEditor
*editor
, WCHAR
*buffer
, int buflen
,
4255 const ME_Cursor
*start
, int srcChars
, BOOL bCRLF
,
4258 ME_Run
*run
, *next_run
;
4259 const WCHAR
*pStart
= buffer
;
4263 /* bCRLF flag is only honored in 2.0 and up. 1.0 must always return text verbatim */
4264 if (editor
->bEmulateVersion10
) bCRLF
= FALSE
;
4267 next_run
= run_next_all_paras( run
);
4269 nLen
= run
->len
- start
->nOffset
;
4270 str
= get_text( run
, start
->nOffset
);
4272 while (srcChars
&& buflen
&& next_run
)
4274 if (bCRLF
&& run
->nFlags
& MERF_ENDPARA
&& ~run
->nFlags
& MERF_ENDCELL
)
4276 if (buflen
== 1) break;
4277 /* FIXME: native fails to reduce srcChars here for WM_GETTEXT or
4278 * EM_GETTEXTEX, however, this is done for copying text which
4279 * also uses this function. */
4280 srcChars
-= min(nLen
, srcChars
);
4286 nLen
= min(nLen
, srcChars
);
4290 nLen
= min(nLen
, buflen
);
4293 CopyMemory(buffer
, str
, sizeof(WCHAR
) * nLen
);
4298 next_run
= run_next_all_paras( run
);
4301 str
= get_text( run
, 0 );
4303 /* append '\r' to the last paragraph. */
4304 if (run
== para_end_run( para_prev( editor_end_para( editor
) ) ) && bEOP
)
4310 return buffer
- pStart
;
4313 static int __cdecl
wchar_comp( const void *key
, const void *elem
)
4315 return *(const WCHAR
*)key
- *(const WCHAR
*)elem
;
4318 /* neutral characters end the url if the next non-neutral character is a space character,
4319 otherwise they are included in the url. */
4320 static BOOL
isurlneutral( WCHAR c
)
4322 /* NB this list is sorted */
4323 static const WCHAR neutral_chars
[] = L
"!\"'(),-.:;<>?[]{}";
4325 /* Some shortcuts */
4326 if (isalnum( c
)) return FALSE
;
4327 if (c
> L
'}') return FALSE
;
4329 return !!bsearch( &c
, neutral_chars
, ARRAY_SIZE( neutral_chars
) - 1, sizeof(c
), wchar_comp
);
4333 * This proc takes a selection, and scans it forward in order to select the span
4334 * of a possible URL candidate. A possible URL candidate must start with isalnum
4335 * or one of the following special characters: *|/\+%#@ and must consist entirely
4336 * of the characters allowed to start the URL, plus : (colon) which may occur
4337 * at most once, and not at either end.
4339 static BOOL
ME_FindNextURLCandidate(ME_TextEditor
*editor
,
4340 const ME_Cursor
*start
,
4342 ME_Cursor
*candidate_min
,
4343 ME_Cursor
*candidate_max
)
4345 ME_Cursor cursor
= *start
, neutral_end
, space_end
;
4346 BOOL candidateStarted
= FALSE
, quoted
= FALSE
;
4351 WCHAR
*str
= get_text( cursor
.run
, 0 );
4352 int run_len
= cursor
.run
->len
;
4354 nChars
-= run_len
- cursor
.nOffset
;
4356 /* Find start of candidate */
4357 if (!candidateStarted
)
4359 while (cursor
.nOffset
< run_len
)
4361 c
= str
[cursor
.nOffset
];
4362 if (!iswspace( c
) && !isurlneutral( c
))
4364 *candidate_min
= cursor
;
4365 candidateStarted
= TRUE
;
4366 neutral_end
.para
= NULL
;
4367 space_end
.para
= NULL
;
4371 quoted
= (c
== '<');
4376 /* Find end of candidate */
4377 if (candidateStarted
)
4379 while (cursor
.nOffset
< run_len
)
4381 c
= str
[cursor
.nOffset
];
4384 if (quoted
&& c
!= '\r')
4386 if (!space_end
.para
)
4388 if (neutral_end
.para
)
4389 space_end
= neutral_end
;
4397 else if (isurlneutral( c
))
4399 if (quoted
&& c
== '>')
4401 neutral_end
.para
= NULL
;
4402 space_end
.para
= NULL
;
4405 if (!neutral_end
.para
)
4406 neutral_end
= cursor
;
4409 neutral_end
.para
= NULL
;
4416 if (!cursor_next_run( &cursor
, TRUE
))
4421 if (candidateStarted
)
4424 *candidate_max
= space_end
;
4425 else if (neutral_end
.para
)
4426 *candidate_max
= neutral_end
;
4428 *candidate_max
= cursor
;
4431 *candidate_max
= *candidate_min
= cursor
;
4436 * This proc evaluates the selection and returns TRUE if it can be considered an URL
4438 static BOOL
ME_IsCandidateAnURL(ME_TextEditor
*editor
, const ME_Cursor
*start
, int nChars
)
4440 #define MAX_PREFIX_LEN 9
4441 #define X(str) str, ARRAY_SIZE(str) - 1
4443 const WCHAR text
[MAX_PREFIX_LEN
];
4460 WCHAR bufferW
[MAX_PREFIX_LEN
+ 1];
4463 ME_GetTextW(editor
, bufferW
, MAX_PREFIX_LEN
, start
, nChars
, FALSE
, FALSE
);
4464 for (i
= 0; i
< ARRAY_SIZE(prefixes
); i
++)
4466 if (nChars
< prefixes
[i
].length
) continue;
4467 if (!memcmp(prefixes
[i
].text
, bufferW
, prefixes
[i
].length
* sizeof(WCHAR
)))
4471 #undef MAX_PREFIX_LEN
4475 * This proc walks through the indicated selection and evaluates whether each
4476 * section identified by ME_FindNextURLCandidate and in-between sections have
4477 * their proper CFE_LINK attributes set or unset. If the CFE_LINK attribute is
4478 * not what it is supposed to be, this proc sets or unsets it as appropriate.
4480 * Since this function can cause runs to be split, do not depend on the value
4481 * of the start cursor at the end of the function.
4483 * nChars may be set to INT_MAX to update to the end of the text.
4485 * Returns TRUE if at least one section was modified.
4487 static BOOL
ME_UpdateLinkAttribute(ME_TextEditor
*editor
, ME_Cursor
*start
, int nChars
)
4489 BOOL modified
= FALSE
;
4490 ME_Cursor startCur
= *start
;
4492 if (!editor
->AutoURLDetect_bEnable
) return FALSE
;
4497 ME_Cursor candidateStart
, candidateEnd
;
4499 if (ME_FindNextURLCandidate(editor
, &startCur
, nChars
,
4500 &candidateStart
, &candidateEnd
))
4502 /* Section before candidate is not an URL */
4503 int cMin
= ME_GetCursorOfs(&candidateStart
);
4504 int cMax
= ME_GetCursorOfs(&candidateEnd
);
4506 if (!ME_IsCandidateAnURL(editor
, &candidateStart
, cMax
- cMin
))
4507 candidateStart
= candidateEnd
;
4508 nChars
-= cMax
- ME_GetCursorOfs(&startCur
);
4512 /* No more candidates until end of selection */
4516 if (startCur
.run
!= candidateStart
.run
||
4517 startCur
.nOffset
!= candidateStart
.nOffset
)
4519 /* CFE_LINK effect should be consistently unset */
4520 link
.cbSize
= sizeof(link
);
4521 ME_GetCharFormat(editor
, &startCur
, &candidateStart
, &link
);
4522 if (!(link
.dwMask
& CFM_LINK
) || (link
.dwEffects
& CFE_LINK
))
4524 /* CFE_LINK must be unset from this range */
4525 memset(&link
, 0, sizeof(CHARFORMAT2W
));
4526 link
.cbSize
= sizeof(link
);
4527 link
.dwMask
= CFM_LINK
;
4529 ME_SetCharFormat(editor
, &startCur
, &candidateStart
, &link
);
4530 /* Update candidateEnd since setting character formats may split
4531 * runs, which can cause a cursor to be at an invalid offset within
4533 while (candidateEnd
.nOffset
>= candidateEnd
.run
->len
)
4535 candidateEnd
.nOffset
-= candidateEnd
.run
->len
;
4536 candidateEnd
.run
= run_next_all_paras( candidateEnd
.run
);
4541 if (candidateStart
.run
!= candidateEnd
.run
||
4542 candidateStart
.nOffset
!= candidateEnd
.nOffset
)
4544 /* CFE_LINK effect should be consistently set */
4545 link
.cbSize
= sizeof(link
);
4546 ME_GetCharFormat(editor
, &candidateStart
, &candidateEnd
, &link
);
4547 if (!(link
.dwMask
& CFM_LINK
) || !(link
.dwEffects
& CFE_LINK
))
4549 /* CFE_LINK must be set on this range */
4550 memset(&link
, 0, sizeof(CHARFORMAT2W
));
4551 link
.cbSize
= sizeof(link
);
4552 link
.dwMask
= CFM_LINK
;
4553 link
.dwEffects
= CFE_LINK
;
4554 ME_SetCharFormat(editor
, &candidateStart
, &candidateEnd
, &link
);
4558 startCur
= candidateEnd
;
4559 } while (nChars
> 0);