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("%08x %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 editor_insert_oleobj(editor
, &reobject
);
1178 if (lpObject
) IOleObject_Release(lpObject
);
1179 if (lpClientSite
) IOleClientSite_Release(lpClientSite
);
1180 if (lpStorage
) IStorage_Release(lpStorage
);
1181 if (lpDataObject
) IDataObject_Release(lpDataObject
);
1182 if (lpOleCache
) IOleCache_Release(lpOleCache
);
1187 static void ME_RTFReadShpPictGroup( RTF_Info
*info
)
1195 if (info
->rtfClass
== rtfEOF
) return;
1196 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1198 if (--level
== 0) break;
1200 else if (RTFCheckCM( info
, rtfGroup
, rtfBeginGroup
))
1206 RTFRouteToken( info
);
1207 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1212 RTFRouteToken( info
); /* feed "}" back to router */
1216 static DWORD
read_hex_data( RTF_Info
*info
, BYTE
**out
)
1218 DWORD read
= 0, size
= 1024;
1224 if (info
->rtfClass
!= rtfText
)
1226 ERR("Called with incorrect token\n");
1230 buf
= HeapAlloc( GetProcessHeap(), 0, size
);
1233 val
= info
->rtfMajor
;
1234 for (flip
= TRUE
;; flip
= !flip
)
1236 RTFGetToken( info
);
1237 if (info
->rtfClass
== rtfEOF
)
1239 HeapFree( GetProcessHeap(), 0, buf
);
1242 if (info
->rtfClass
!= rtfText
) break;
1248 buf
= HeapReAlloc( GetProcessHeap(), 0, buf
, size
);
1251 buf
[read
++] = RTFCharToHex(val
) * 16 + RTFCharToHex(info
->rtfMajor
);
1254 val
= info
->rtfMajor
;
1256 if (flip
) FIXME("wrong hex string\n");
1262 static void ME_RTFReadPictGroup(RTF_Info
*info
)
1265 BYTE
*buffer
= NULL
;
1270 enum gfxkind
{gfx_unknown
= 0, gfx_enhmetafile
, gfx_metafile
, gfx_dib
} gfx
= gfx_unknown
;
1278 RTFGetToken( info
);
1280 if (info
->rtfClass
== rtfText
)
1285 size
= read_hex_data( info
, &buffer
);
1289 RTFSkipGroup( info
);
1291 } /* We potentially have a new token so fall through. */
1293 if (info
->rtfClass
== rtfEOF
) return;
1295 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1297 if (--level
== 0) break;
1300 if (RTFCheckCM( info
, rtfGroup
, rtfBeginGroup
))
1305 if (!RTFCheckCM( info
, rtfControl
, rtfPictAttr
))
1307 RTFRouteToken( info
);
1308 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1313 if (RTFCheckMM( info
, rtfPictAttr
, rtfWinMetafile
))
1315 mfp
.mm
= info
->rtfParam
;
1318 else if (RTFCheckMM( info
, rtfPictAttr
, rtfDevIndBitmap
))
1320 if (info
->rtfParam
!= 0) FIXME("dibitmap should be 0 (%d)\n", info
->rtfParam
);
1323 else if (RTFCheckMM( info
, rtfPictAttr
, rtfEmfBlip
))
1324 gfx
= gfx_enhmetafile
;
1325 else if (RTFCheckMM( info
, rtfPictAttr
, rtfPicWid
))
1326 mfp
.xExt
= info
->rtfParam
;
1327 else if (RTFCheckMM( info
, rtfPictAttr
, rtfPicHt
))
1328 mfp
.yExt
= info
->rtfParam
;
1329 else if (RTFCheckMM( info
, rtfPictAttr
, rtfPicGoalWid
))
1330 sz
.cx
= info
->rtfParam
;
1331 else if (RTFCheckMM( info
, rtfPictAttr
, rtfPicGoalHt
))
1332 sz
.cy
= info
->rtfParam
;
1334 FIXME("Non supported attribute: %d %d %d\n", info
->rtfClass
, info
->rtfMajor
, info
->rtfMinor
);
1341 case gfx_enhmetafile
:
1342 if ((hemf
= SetEnhMetaFileBits( size
, buffer
)))
1343 insert_static_object( info
->editor
, hemf
, NULL
, &sz
);
1346 if ((hemf
= SetWinMetaFileBits( size
, buffer
, NULL
, &mfp
)))
1347 insert_static_object( info
->editor
, hemf
, NULL
, &sz
);
1351 BITMAPINFO
*bi
= (BITMAPINFO
*)buffer
;
1353 unsigned nc
= bi
->bmiHeader
.biClrUsed
;
1355 /* not quite right, especially for bitfields type of compression */
1356 if (!nc
&& bi
->bmiHeader
.biBitCount
<= 8)
1357 nc
= 1 << bi
->bmiHeader
.biBitCount
;
1358 if ((hbmp
= CreateDIBitmap( hdc
, &bi
->bmiHeader
,
1359 CBM_INIT
, (char*)(bi
+ 1) + nc
* sizeof(RGBQUAD
),
1360 bi
, DIB_RGB_COLORS
)) )
1361 insert_static_object( info
->editor
, NULL
, hbmp
, &sz
);
1362 ReleaseDC( 0, hdc
);
1369 HeapFree( GetProcessHeap(), 0, buffer
);
1370 RTFRouteToken( info
); /* feed "}" back to router */
1374 /* for now, lookup the \result part and use it, whatever the object */
1375 static void ME_RTFReadObjectGroup(RTF_Info
*info
)
1380 if (info
->rtfClass
== rtfEOF
)
1382 if (RTFCheckCM(info
, rtfGroup
, rtfEndGroup
))
1384 if (RTFCheckCM(info
, rtfGroup
, rtfBeginGroup
))
1387 if (info
->rtfClass
== rtfEOF
)
1389 if (RTFCheckCMM(info
, rtfControl
, rtfDestination
, rtfObjResult
))
1393 while (RTFGetToken (info
) != rtfEOF
)
1395 if (info
->rtfClass
== rtfGroup
)
1397 if (info
->rtfMajor
== rtfBeginGroup
) level
++;
1398 else if (info
->rtfMajor
== rtfEndGroup
&& --level
< 0) break;
1400 RTFRouteToken(info
);
1403 else RTFSkipGroup(info
);
1406 if (!RTFCheckCM (info
, rtfControl
, rtfObjAttr
))
1408 FIXME("Non supported attribute: %d %d %d\n", info
->rtfClass
, info
->rtfMajor
, info
->rtfMinor
);
1412 RTFRouteToken(info
); /* feed "}" back to router */
1415 static void ME_RTFReadParnumGroup( RTF_Info
*info
)
1417 int level
= 1, type
= -1;
1418 WORD indent
= 0, start
= 1;
1419 WCHAR txt_before
= 0, txt_after
= 0;
1423 RTFGetToken( info
);
1425 if (RTFCheckCMM( info
, rtfControl
, rtfDestination
, rtfParNumTextBefore
) ||
1426 RTFCheckCMM( info
, rtfControl
, rtfDestination
, rtfParNumTextAfter
))
1428 int loc
= info
->rtfMinor
;
1430 RTFGetToken( info
);
1431 if (info
->rtfClass
== rtfText
)
1433 if (loc
== rtfParNumTextBefore
)
1434 txt_before
= info
->rtfMajor
;
1436 txt_after
= info
->rtfMajor
;
1439 /* falling through to catch EOFs and group level changes */
1442 if (info
->rtfClass
== rtfEOF
)
1445 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1447 if (--level
== 0) break;
1451 if (RTFCheckCM( info
, rtfGroup
, rtfBeginGroup
))
1457 /* Ignore non para-attr */
1458 if (!RTFCheckCM( info
, rtfControl
, rtfParAttr
))
1461 switch (info
->rtfMinor
)
1463 case rtfParLevel
: /* Para level is ignored */
1470 case rtfParNumDecimal
:
1473 case rtfParNumULetter
:
1474 type
= PFN_UCLETTER
;
1476 case rtfParNumURoman
:
1479 case rtfParNumLLetter
:
1480 type
= PFN_LCLETTER
;
1482 case rtfParNumLRoman
:
1486 case rtfParNumIndent
:
1487 indent
= info
->rtfParam
;
1489 case rtfParNumStartAt
:
1490 start
= info
->rtfParam
;
1497 info
->fmt
.dwMask
|= (PFM_NUMBERING
| PFM_NUMBERINGSTART
| PFM_NUMBERINGSTYLE
| PFM_NUMBERINGTAB
);
1498 info
->fmt
.wNumbering
= type
;
1499 info
->fmt
.wNumberingStart
= start
;
1500 info
->fmt
.wNumberingStyle
= PFNS_PAREN
;
1501 if (type
!= PFN_BULLET
)
1503 if (txt_before
== 0 && txt_after
== 0)
1504 info
->fmt
.wNumberingStyle
= PFNS_PLAIN
;
1505 else if (txt_after
== '.')
1506 info
->fmt
.wNumberingStyle
= PFNS_PERIOD
;
1507 else if (txt_before
== '(' && txt_after
== ')')
1508 info
->fmt
.wNumberingStyle
= PFNS_PARENS
;
1510 info
->fmt
.wNumberingTab
= indent
;
1513 TRACE("type %d indent %d start %d txt before %04x txt after %04x\n",
1514 type
, indent
, start
, txt_before
, txt_after
);
1516 RTFRouteToken( info
); /* feed "}" back to router */
1519 static void ME_RTFReadHook(RTF_Info
*info
)
1521 switch(info
->rtfClass
)
1524 switch(info
->rtfMajor
)
1527 if (info
->stackTop
< maxStack
) {
1528 info
->stack
[info
->stackTop
].style
= info
->style
;
1529 ME_AddRefStyle(info
->style
);
1530 info
->stack
[info
->stackTop
].codePage
= info
->codePage
;
1531 info
->stack
[info
->stackTop
].unicodeLength
= info
->unicodeLength
;
1534 info
->styleChanged
= FALSE
;
1538 RTFFlushOutputBuffer(info
);
1540 if (info
->stackTop
<= 0)
1541 info
->rtfClass
= rtfEOF
;
1542 if (info
->stackTop
< 0)
1545 ME_ReleaseStyle(info
->style
);
1546 info
->style
= info
->stack
[info
->stackTop
].style
;
1547 info
->codePage
= info
->stack
[info
->stackTop
].codePage
;
1548 info
->unicodeLength
= info
->stack
[info
->stackTop
].unicodeLength
;
1557 ME_StreamInFill(ME_InStream
*stream
)
1559 stream
->editstream
->dwError
= stream
->editstream
->pfnCallback(stream
->editstream
->dwCookie
,
1560 (BYTE
*)stream
->buffer
,
1561 sizeof(stream
->buffer
),
1562 (LONG
*)&stream
->dwSize
);
1566 static LRESULT
ME_StreamIn(ME_TextEditor
*editor
, DWORD format
, EDITSTREAM
*stream
, BOOL stripLastCR
)
1570 int from
, to
, nUndoMode
;
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%X\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 set_selection_cursors(editor
, 0, 0);
1613 ME_InternalDeleteText(editor
, &editor
->pCursors
[1],
1614 ME_GetTextLength(editor
), FALSE
);
1616 ME_ClearTempStyle(editor
);
1617 editor_set_default_para_fmt( editor
, &editor
->pCursors
[0].para
->fmt
);
1621 /* Back up undo mode to a local variable */
1622 nUndoMode
= editor
->nUndoMode
;
1624 /* Only create an undo if SFF_SELECTION is set */
1625 if (!(format
& SFF_SELECTION
))
1626 editor
->nUndoMode
= umIgnore
;
1628 inStream
.editstream
= stream
;
1629 inStream
.editstream
->dwError
= 0;
1630 inStream
.dwSize
= 0;
1631 inStream
.dwUsed
= 0;
1633 if (format
& SF_RTF
)
1635 /* Check if it's really RTF, and if it is not, use plain text */
1636 ME_StreamInFill(&inStream
);
1637 if (!inStream
.editstream
->dwError
)
1639 if ((!editor
->bEmulateVersion10
&& strncmp(inStream
.buffer
, "{\\rtf", 5) && strncmp(inStream
.buffer
, "{\\urtf", 6))
1640 || (editor
->bEmulateVersion10
&& *inStream
.buffer
!= '{'))
1643 inStream
.editstream
->dwError
= -16;
1648 if (!invalidRTF
&& !inStream
.editstream
->dwError
)
1651 from
= ME_GetCursorOfs(&editor
->pCursors
[0]);
1652 if (format
& SF_RTF
) {
1654 /* setup the RTF parser */
1655 memset(&parser
, 0, sizeof parser
);
1656 RTFSetEditStream(&parser
, &inStream
);
1657 parser
.rtfFormat
= format
&(SF_TEXT
|SF_RTF
);
1658 parser
.editor
= editor
;
1659 parser
.style
= style
;
1660 WriterInit(&parser
);
1662 RTFSetReadHook(&parser
, ME_RTFReadHook
);
1663 RTFSetDestinationCallback(&parser
, rtfShpPict
, ME_RTFReadShpPictGroup
);
1664 RTFSetDestinationCallback(&parser
, rtfPict
, ME_RTFReadPictGroup
);
1665 RTFSetDestinationCallback(&parser
, rtfObject
, ME_RTFReadObjectGroup
);
1666 RTFSetDestinationCallback(&parser
, rtfParNumbering
, ME_RTFReadParnumGroup
);
1667 if (!parser
.editor
->bEmulateVersion10
) /* v4.1 */
1669 RTFSetDestinationCallback(&parser
, rtfNoNestTables
, RTFSkipGroup
);
1670 RTFSetDestinationCallback(&parser
, rtfNestTableProps
, RTFReadGroup
);
1674 /* do the parsing */
1676 RTFFlushOutputBuffer(&parser
);
1677 if (!editor
->bEmulateVersion10
) /* v4.1 */
1679 if (parser
.tableDef
&& parser
.tableDef
->row_start
&&
1680 (parser
.nestingLevel
> 0 || parser
.canInheritInTbl
))
1682 /* Delete any incomplete table row at the end of the rich text. */
1686 parser
.rtfMinor
= rtfRow
;
1687 /* Complete the table row before deleting it.
1688 * By doing it this way we will have the current paragraph format set
1689 * properly to reflect that is not in the complete table, and undo items
1690 * will be added for this change to the current paragraph format. */
1691 if (parser
.nestingLevel
> 0)
1693 while (parser
.nestingLevel
> 1)
1694 ME_RTFSpecialCharHook(&parser
); /* Decrements nestingLevel */
1695 para
= parser
.tableDef
->row_start
;
1696 ME_RTFSpecialCharHook(&parser
);
1700 para
= parser
.tableDef
->row_start
;
1701 ME_RTFSpecialCharHook(&parser
);
1702 assert( para
->nFlags
& MEPF_ROWEND
);
1703 para
= para_next( para
);
1706 editor
->pCursors
[1].para
= para
;
1707 editor
->pCursors
[1].run
= para_first_run( para
);
1708 editor
->pCursors
[1].nOffset
= 0;
1709 nOfs
= ME_GetCursorOfs(&editor
->pCursors
[1]);
1710 nChars
= ME_GetCursorOfs(&editor
->pCursors
[0]) - nOfs
;
1711 ME_InternalDeleteText(editor
, &editor
->pCursors
[1], nChars
, TRUE
);
1712 if (parser
.tableDef
) parser
.tableDef
->row_start
= NULL
;
1715 RTFDestroy(&parser
);
1717 if (parser
.stackTop
> 0)
1719 while (--parser
.stackTop
>= 0)
1721 ME_ReleaseStyle(parser
.style
);
1722 parser
.style
= parser
.stack
[parser
.stackTop
].style
;
1724 if (!inStream
.editstream
->dwError
)
1725 inStream
.editstream
->dwError
= HRESULT_FROM_WIN32(ERROR_HANDLE_EOF
);
1728 /* Remove last line break, as mandated by tests. This is not affected by
1729 CR/LF counters, since RTF streaming presents only \para tokens, which
1730 are converted according to the standard rules: \r for 2.0, \r\n for 1.0
1732 if (stripLastCR
&& !(format
& SFF_SELECTION
)) {
1734 ME_GetSelection(editor
, &selStart
, &selEnd
);
1735 newto
= ME_GetCursorOfs(selEnd
);
1736 if (newto
> to
+ (editor
->bEmulateVersion10
? 1 : 0)) {
1737 WCHAR lastchar
[3] = {'\0', '\0'};
1738 int linebreakSize
= editor
->bEmulateVersion10
? 2 : 1;
1739 ME_Cursor linebreakCursor
= *selEnd
, lastcharCursor
= *selEnd
;
1742 /* Set the final eop to the char fmt of the last char */
1743 cf
.cbSize
= sizeof(cf
);
1744 cf
.dwMask
= CFM_ALL2
;
1745 ME_MoveCursorChars(editor
, &lastcharCursor
, -1, FALSE
);
1746 ME_GetCharFormat(editor
, &lastcharCursor
, &linebreakCursor
, &cf
);
1747 set_selection_cursors(editor
, newto
, -1);
1748 ME_SetSelectionCharFormat(editor
, &cf
);
1749 set_selection_cursors(editor
, newto
, newto
);
1751 ME_MoveCursorChars(editor
, &linebreakCursor
, -linebreakSize
, FALSE
);
1752 ME_GetTextW(editor
, lastchar
, 2, &linebreakCursor
, linebreakSize
, FALSE
, FALSE
);
1753 if (lastchar
[0] == '\r' && (lastchar
[1] == '\n' || lastchar
[1] == '\0')) {
1754 ME_InternalDeleteText(editor
, &linebreakCursor
, linebreakSize
, FALSE
);
1758 to
= ME_GetCursorOfs(&editor
->pCursors
[0]);
1759 num_read
= to
- from
;
1761 style
= parser
.style
;
1763 else if (format
& SF_TEXT
)
1765 num_read
= ME_StreamInText(editor
, format
, &inStream
, style
);
1766 to
= ME_GetCursorOfs(&editor
->pCursors
[0]);
1769 ERR("EM_STREAMIN without SF_TEXT or SF_RTF\n");
1770 /* put the cursor at the top */
1771 if (!(format
& SFF_SELECTION
))
1772 set_selection_cursors(editor
, 0, 0);
1773 cursor_from_char_ofs( editor
, from
, &start
);
1774 ME_UpdateLinkAttribute(editor
, &start
, to
- from
);
1777 /* Restore saved undo mode */
1778 editor
->nUndoMode
= nUndoMode
;
1780 /* even if we didn't add an undo, we need to commit anything on the stack */
1781 ME_CommitUndo(editor
);
1783 /* If SFF_SELECTION isn't set, delete any undos from before we started too */
1784 if (!(format
& SFF_SELECTION
))
1785 ME_EmptyUndoStack(editor
);
1787 ME_ReleaseStyle(style
);
1788 editor
->nEventMask
= nEventMask
;
1789 ME_UpdateRepaint(editor
, FALSE
);
1790 if (!(format
& SFF_SELECTION
)) {
1791 ME_ClearTempStyle(editor
);
1793 ME_SendSelChange(editor
);
1794 ME_SendRequestResize(editor
, FALSE
);
1800 typedef struct tagME_RTFStringStreamStruct
1805 } ME_RTFStringStreamStruct
;
1807 static DWORD CALLBACK
ME_ReadFromRTFString(DWORD_PTR dwCookie
, LPBYTE lpBuff
, LONG cb
, LONG
*pcb
)
1809 ME_RTFStringStreamStruct
*pStruct
= (ME_RTFStringStreamStruct
*)dwCookie
;
1812 count
= min(cb
, pStruct
->length
- pStruct
->pos
);
1813 memmove(lpBuff
, pStruct
->string
+ pStruct
->pos
, count
);
1814 pStruct
->pos
+= count
;
1820 ME_StreamInRTFString(ME_TextEditor
*editor
, BOOL selection
, char *string
)
1823 ME_RTFStringStreamStruct data
;
1825 data
.string
= string
;
1826 data
.length
= strlen(string
);
1828 es
.dwCookie
= (DWORD_PTR
)&data
;
1829 es
.pfnCallback
= ME_ReadFromRTFString
;
1830 ME_StreamIn(editor
, SF_RTF
| (selection
? SFF_SELECTION
: 0), &es
, TRUE
);
1835 ME_FindText(ME_TextEditor
*editor
, DWORD flags
, const CHARRANGE
*chrg
, const WCHAR
*text
, CHARRANGE
*chrgText
)
1837 const int nLen
= lstrlenW(text
);
1838 const int nTextLen
= ME_GetTextLength(editor
);
1841 WCHAR wLastChar
= ' ';
1843 TRACE("flags==0x%08x, chrg->cpMin==%d, chrg->cpMax==%d text==%s\n",
1844 flags
, chrg
->cpMin
, chrg
->cpMax
, debugstr_w(text
));
1846 if (flags
& ~(FR_DOWN
| FR_MATCHCASE
| FR_WHOLEWORD
))
1847 FIXME("Flags 0x%08x not implemented\n",
1848 flags
& ~(FR_DOWN
| FR_MATCHCASE
| FR_WHOLEWORD
));
1851 if (chrg
->cpMax
== -1)
1854 nMax
= chrg
->cpMax
> nTextLen
? nTextLen
: chrg
->cpMax
;
1856 /* In 1.0 emulation, if cpMax reaches end of text, add the FR_DOWN flag */
1857 if (editor
->bEmulateVersion10
&& nMax
== nTextLen
)
1862 /* In 1.0 emulation, cpMin must always be no greater than cpMax */
1863 if (editor
->bEmulateVersion10
&& nMax
< nMin
)
1867 chrgText
->cpMin
= -1;
1868 chrgText
->cpMax
= -1;
1873 /* when searching up, if cpMin < cpMax, then instead of searching
1874 * on [cpMin,cpMax], we search on [0,cpMin], otherwise, search on
1875 * [cpMax, cpMin]. The exception is when cpMax is -1, in which
1876 * case, it is always bigger than cpMin.
1878 if (!editor
->bEmulateVersion10
&& !(flags
& FR_DOWN
))
1882 nMax
= nMin
> nTextLen
? nTextLen
: nMin
;
1883 if (nMin
< nSwap
|| chrg
->cpMax
== -1)
1889 if (!nLen
|| nMin
< 0 || nMax
< 0 || nMax
< nMin
)
1892 chrgText
->cpMin
= chrgText
->cpMax
= -1;
1896 if (flags
& FR_DOWN
) /* Forward search */
1898 /* If possible, find the character before where the search starts */
1899 if ((flags
& FR_WHOLEWORD
) && nMin
)
1901 cursor_from_char_ofs( editor
, nMin
- 1, &cursor
);
1902 wLastChar
= *get_text( cursor
.run
, cursor
.nOffset
);
1903 ME_MoveCursorChars(editor
, &cursor
, 1, FALSE
);
1905 else cursor_from_char_ofs( editor
, nMin
, &cursor
);
1907 while (cursor
.run
&& ME_GetCursorOfs(&cursor
) + nLen
<= nMax
)
1909 ME_Run
*run
= cursor
.run
;
1910 int nCurStart
= cursor
.nOffset
;
1913 while (run
&& ME_CharCompare( *get_text( run
, nCurStart
+ nMatched
), text
[nMatched
], (flags
& FR_MATCHCASE
)))
1915 if ((flags
& FR_WHOLEWORD
) && iswalnum(wLastChar
))
1919 if (nMatched
== nLen
)
1921 ME_Run
*next_run
= run
;
1922 int nNextStart
= nCurStart
;
1925 /* Check to see if next character is a whitespace */
1926 if (flags
& FR_WHOLEWORD
)
1928 if (nCurStart
+ nMatched
== run
->len
)
1930 next_run
= run_next_all_paras( run
);
1931 nNextStart
= -nMatched
;
1935 wNextChar
= *get_text( next_run
, nNextStart
+ nMatched
);
1939 if (iswalnum(wNextChar
))
1943 cursor
.nOffset
+= cursor
.para
->nCharOfs
+ cursor
.run
->nCharOfs
;
1946 chrgText
->cpMin
= cursor
.nOffset
;
1947 chrgText
->cpMax
= cursor
.nOffset
+ nLen
;
1949 TRACE("found at %d-%d\n", cursor
.nOffset
, cursor
.nOffset
+ nLen
);
1950 return cursor
.nOffset
;
1952 if (nCurStart
+ nMatched
== run
->len
)
1954 run
= run_next_all_paras( run
);
1955 nCurStart
= -nMatched
;
1959 wLastChar
= *get_text( run
, nCurStart
+ nMatched
);
1964 if (cursor
.nOffset
== cursor
.run
->len
)
1966 if (run_next_all_paras( cursor
.run
))
1968 cursor
.run
= run_next_all_paras( cursor
.run
);
1969 cursor
.para
= cursor
.run
->para
;
1977 else /* Backward search */
1979 /* If possible, find the character after where the search ends */
1980 if ((flags
& FR_WHOLEWORD
) && nMax
< nTextLen
- 1)
1982 cursor_from_char_ofs( editor
, nMax
+ 1, &cursor
);
1983 wLastChar
= *get_text( cursor
.run
, cursor
.nOffset
);
1984 ME_MoveCursorChars(editor
, &cursor
, -1, FALSE
);
1986 else cursor_from_char_ofs( editor
, nMax
, &cursor
);
1988 while (cursor
.run
&& ME_GetCursorOfs(&cursor
) - nLen
>= nMin
)
1990 ME_Run
*run
= cursor
.run
;
1991 ME_Paragraph
*para
= cursor
.para
;
1992 int nCurEnd
= cursor
.nOffset
;
1995 if (nCurEnd
== 0 && run_prev_all_paras( run
))
1997 run
= run_prev_all_paras( run
);
2002 while (run
&& ME_CharCompare( *get_text( run
, nCurEnd
- nMatched
- 1 ),
2003 text
[nLen
- nMatched
- 1], (flags
& FR_MATCHCASE
) ))
2005 if ((flags
& FR_WHOLEWORD
) && iswalnum(wLastChar
))
2009 if (nMatched
== nLen
)
2011 ME_Run
*prev_run
= run
;
2012 int nPrevEnd
= nCurEnd
;
2016 /* Check to see if previous character is a whitespace */
2017 if (flags
& FR_WHOLEWORD
)
2019 if (nPrevEnd
- nMatched
== 0)
2021 prev_run
= run_prev_all_paras( run
);
2022 if (prev_run
) nPrevEnd
= prev_run
->len
+ nMatched
;
2025 if (prev_run
) wPrevChar
= *get_text( prev_run
, nPrevEnd
- nMatched
- 1 );
2026 else wPrevChar
= ' ';
2028 if (iswalnum(wPrevChar
))
2032 nStart
= para
->nCharOfs
+ run
->nCharOfs
+ nCurEnd
- nMatched
;
2035 chrgText
->cpMin
= nStart
;
2036 chrgText
->cpMax
= nStart
+ nLen
;
2038 TRACE("found at %d-%d\n", nStart
, nStart
+ nLen
);
2041 if (nCurEnd
- nMatched
== 0)
2043 if (run_prev_all_paras( run
))
2045 run
= run_prev_all_paras( run
);
2048 /* Don't care about pCurItem becoming NULL here; it's already taken
2049 * care of in the exterior loop condition */
2050 nCurEnd
= run
->len
+ nMatched
;
2054 wLastChar
= *get_text( run
, nCurEnd
- nMatched
- 1 );
2059 if (cursor
.nOffset
< 0)
2061 if (run_prev_all_paras( cursor
.run
) )
2063 cursor
.run
= run_prev_all_paras( cursor
.run
);
2064 cursor
.para
= cursor
.run
->para
;
2065 cursor
.nOffset
= cursor
.run
->len
;
2072 TRACE("not found\n");
2074 chrgText
->cpMin
= chrgText
->cpMax
= -1;
2078 static int ME_GetTextEx(ME_TextEditor
*editor
, GETTEXTEX
*ex
, LPARAM pText
)
2083 if (!ex
->cb
|| !pText
) return 0;
2085 if (ex
->flags
& ~(GT_SELECTION
| GT_USECRLF
))
2086 FIXME("GETTEXTEX flags 0x%08x not supported\n", ex
->flags
& ~(GT_SELECTION
| GT_USECRLF
));
2088 if (ex
->flags
& GT_SELECTION
)
2091 int nStartCur
= ME_GetSelectionOfs(editor
, &from
, &to
);
2092 start
= editor
->pCursors
[nStartCur
];
2097 ME_SetCursorToStart(editor
, &start
);
2100 if (ex
->codepage
== CP_UNICODE
)
2102 return ME_GetTextW(editor
, (LPWSTR
)pText
, ex
->cb
/ sizeof(WCHAR
) - 1,
2103 &start
, nChars
, ex
->flags
& GT_USECRLF
, FALSE
);
2107 /* potentially each char may be a CR, why calculate the exact value with O(N) when
2108 we can just take a bigger buffer? :)
2109 The above assumption still holds with CR/LF counters, since CR->CRLF expansion
2110 occurs only in richedit 2.0 mode, in which line breaks have only one CR
2112 int crlfmul
= (ex
->flags
& GT_USECRLF
) ? 2 : 1;
2117 buflen
= min(crlfmul
* nChars
, ex
->cb
- 1);
2118 buffer
= heap_alloc((buflen
+ 1) * sizeof(WCHAR
));
2120 nChars
= ME_GetTextW(editor
, buffer
, buflen
, &start
, nChars
, ex
->flags
& GT_USECRLF
, FALSE
);
2121 rc
= WideCharToMultiByte(ex
->codepage
, 0, buffer
, nChars
+ 1,
2122 (LPSTR
)pText
, ex
->cb
, ex
->lpDefaultChar
, ex
->lpUsedDefChar
);
2123 if (rc
) rc
--; /* do not count 0 terminator */
2130 static int get_text_range( ME_TextEditor
*editor
, WCHAR
*buffer
,
2131 const ME_Cursor
*start
, int len
)
2133 if (!buffer
) return 0;
2134 return ME_GetTextW( editor
, buffer
, INT_MAX
, start
, len
, FALSE
, FALSE
);
2137 int set_selection( ME_TextEditor
*editor
, int to
, int from
)
2141 TRACE("%d - %d\n", to
, from
);
2143 if (!editor
->bHideSelection
) ME_InvalidateSelection( editor
);
2144 end
= set_selection_cursors( editor
, to
, from
);
2145 if (!editor
->bHideSelection
) ME_InvalidateSelection( editor
);
2146 update_caret( editor
);
2147 ME_SendSelChange( editor
);
2152 typedef struct tagME_GlobalDestStruct
2156 } ME_GlobalDestStruct
;
2158 static DWORD CALLBACK
ME_ReadFromHGLOBALUnicode(DWORD_PTR dwCookie
, LPBYTE lpBuff
, LONG cb
, LONG
*pcb
)
2160 ME_GlobalDestStruct
*pData
= (ME_GlobalDestStruct
*)dwCookie
;
2165 pDest
= (WORD
*)lpBuff
;
2166 pSrc
= GlobalLock(pData
->hData
);
2167 for (i
= 0; i
<cb
&& pSrc
[pData
->nLength
+i
]; i
++) {
2168 pDest
[i
] = pSrc
[pData
->nLength
+i
];
2170 pData
->nLength
+= i
;
2172 GlobalUnlock(pData
->hData
);
2176 static DWORD CALLBACK
ME_ReadFromHGLOBALRTF(DWORD_PTR dwCookie
, LPBYTE lpBuff
, LONG cb
, LONG
*pcb
)
2178 ME_GlobalDestStruct
*pData
= (ME_GlobalDestStruct
*)dwCookie
;
2183 pSrc
= GlobalLock(pData
->hData
);
2184 for (i
= 0; i
<cb
&& pSrc
[pData
->nLength
+i
]; i
++) {
2185 pDest
[i
] = pSrc
[pData
->nLength
+i
];
2187 pData
->nLength
+= i
;
2189 GlobalUnlock(pData
->hData
);
2193 static HRESULT
paste_rtf(ME_TextEditor
*editor
, FORMATETC
*fmt
, STGMEDIUM
*med
)
2196 ME_GlobalDestStruct gds
;
2199 gds
.hData
= med
->u
.hGlobal
;
2201 es
.dwCookie
= (DWORD_PTR
)&gds
;
2202 es
.pfnCallback
= ME_ReadFromHGLOBALRTF
;
2203 hr
= ME_StreamIn( editor
, SF_RTF
| SFF_SELECTION
, &es
, FALSE
) == 0 ? E_FAIL
: S_OK
;
2204 ReleaseStgMedium( med
);
2208 static HRESULT
paste_text(ME_TextEditor
*editor
, FORMATETC
*fmt
, STGMEDIUM
*med
)
2211 ME_GlobalDestStruct gds
;
2214 gds
.hData
= med
->u
.hGlobal
;
2216 es
.dwCookie
= (DWORD_PTR
)&gds
;
2217 es
.pfnCallback
= ME_ReadFromHGLOBALUnicode
;
2218 hr
= ME_StreamIn( editor
, SF_TEXT
| SF_UNICODE
| SFF_SELECTION
, &es
, FALSE
) == 0 ? E_FAIL
: S_OK
;
2219 ReleaseStgMedium( med
);
2223 static HRESULT
paste_emf(ME_TextEditor
*editor
, FORMATETC
*fmt
, STGMEDIUM
*med
)
2228 hr
= insert_static_object( editor
, med
->u
.hEnhMetaFile
, NULL
, &sz
);
2231 ME_CommitUndo( editor
);
2232 ME_UpdateRepaint( editor
, FALSE
);
2235 ReleaseStgMedium( med
);
2240 static struct paste_format
2243 HRESULT (*paste
)(ME_TextEditor
*, FORMATETC
*, STGMEDIUM
*);
2247 {{ -1, NULL
, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
}, paste_rtf
, L
"Rich Text Format" },
2248 {{ CF_UNICODETEXT
, NULL
, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
}, paste_text
},
2249 {{ CF_ENHMETAFILE
, NULL
, DVASPECT_CONTENT
, -1, TYMED_ENHMF
}, paste_emf
},
2253 static void init_paste_formats(void)
2255 struct paste_format
*format
;
2260 for (format
= paste_formats
; format
->fmt
.cfFormat
; format
++)
2263 format
->fmt
.cfFormat
= RegisterClipboardFormatW( format
->name
);
2269 static BOOL
paste_special(ME_TextEditor
*editor
, UINT cf
, REPASTESPECIAL
*ps
, BOOL check_only
)
2273 struct paste_format
*format
;
2276 /* Protect read-only edit control from modification */
2277 if (editor
->props
& TXTBIT_READONLY
)
2279 if (!check_only
) editor_beep( editor
, MB_ICONERROR
);
2283 init_paste_formats();
2285 if (ps
&& ps
->dwAspect
!= DVASPECT_CONTENT
)
2286 FIXME("Ignoring aspect %x\n", ps
->dwAspect
);
2288 hr
= OleGetClipboard( &data
);
2289 if (hr
!= S_OK
) return FALSE
;
2291 if (cf
== CF_TEXT
) cf
= CF_UNICODETEXT
;
2294 for (format
= paste_formats
; format
->fmt
.cfFormat
; format
++)
2296 if (cf
&& cf
!= format
->fmt
.cfFormat
) continue;
2297 hr
= IDataObject_QueryGetData( data
, &format
->fmt
);
2302 hr
= IDataObject_GetData( data
, &format
->fmt
, &med
);
2303 if (hr
!= S_OK
) goto done
;
2304 hr
= format
->paste( editor
, &format
->fmt
, &med
);
2311 IDataObject_Release( data
);
2316 static HRESULT
editor_copy( ME_TextEditor
*editor
, ME_Cursor
*start
, int chars
, IDataObject
**data_out
)
2318 IDataObject
*data
= NULL
;
2321 if (editor
->lpOleCallback
)
2324 range
.cpMin
= ME_GetCursorOfs( start
);
2325 range
.cpMax
= range
.cpMin
+ chars
;
2326 hr
= IRichEditOleCallback_GetClipboardData( editor
->lpOleCallback
, &range
, RECO_COPY
, &data
);
2329 if (FAILED( hr
) || !data
)
2330 hr
= ME_GetDataObject( editor
, start
, chars
, &data
);
2332 if (SUCCEEDED( hr
))
2338 hr
= OleSetClipboard( data
);
2339 IDataObject_Release( data
);
2346 HRESULT
editor_copy_or_cut( ME_TextEditor
*editor
, BOOL cut
, ME_Cursor
*start
, int count
,
2347 IDataObject
**data_out
)
2351 if (cut
&& (editor
->props
& TXTBIT_READONLY
))
2353 return E_ACCESSDENIED
;
2356 hr
= editor_copy( editor
, start
, count
, data_out
);
2357 if (SUCCEEDED(hr
) && cut
)
2359 ME_InternalDeleteText( editor
, start
, count
, FALSE
);
2360 ME_CommitUndo( editor
);
2361 ME_UpdateRepaint( editor
, TRUE
);
2366 static BOOL
copy_or_cut( ME_TextEditor
*editor
, BOOL cut
)
2370 int start_cursor
= ME_GetSelectionOfs( editor
, &offs
, &count
);
2371 ME_Cursor
*sel_start
= &editor
->pCursors
[start_cursor
];
2373 if (editor
->password_char
) return FALSE
;
2376 hr
= editor_copy_or_cut( editor
, cut
, sel_start
, count
, NULL
);
2377 if (FAILED( hr
)) editor_beep( editor
, MB_ICONERROR
);
2379 return SUCCEEDED( hr
);
2382 static void ME_UpdateSelectionLinkAttribute(ME_TextEditor
*editor
)
2384 ME_Paragraph
*start_para
, *end_para
;
2385 ME_Cursor
*from
, *to
, start
;
2388 if (!editor
->AutoURLDetect_bEnable
) return;
2390 ME_GetSelection(editor
, &from
, &to
);
2392 /* Find paragraph previous to the one that contains start cursor */
2393 start_para
= from
->para
;
2394 if (para_prev( start_para
)) start_para
= para_prev( start_para
);
2396 /* Find paragraph that contains end cursor */
2397 end_para
= para_next( to
->para
);
2399 start
.para
= start_para
;
2400 start
.run
= para_first_run( start_para
);
2402 num_chars
= end_para
->nCharOfs
- start_para
->nCharOfs
;
2404 ME_UpdateLinkAttribute( editor
, &start
, num_chars
);
2407 static BOOL
handle_enter(ME_TextEditor
*editor
)
2409 BOOL shift_is_down
= GetKeyState(VK_SHIFT
) & 0x8000;
2411 if (editor
->props
& TXTBIT_MULTILINE
)
2413 ME_Cursor cursor
= editor
->pCursors
[0];
2414 ME_Paragraph
*para
= cursor
.para
;
2416 ME_Style
*style
, *eop_style
;
2418 if (editor
->props
& TXTBIT_READONLY
)
2420 editor_beep( editor
, MB_ICONERROR
);
2424 ME_GetSelectionOfs(editor
, &from
, &to
);
2425 if (editor
->nTextLimit
> ME_GetTextLength(editor
) - (to
-from
))
2427 if (!editor
->bEmulateVersion10
) /* v4.1 */
2429 if (para
->nFlags
& MEPF_ROWEND
)
2431 /* Add a new table row after this row. */
2432 para
= table_append_row( editor
, para
);
2433 para
= para_next( para
);
2434 editor
->pCursors
[0].para
= para
;
2435 editor
->pCursors
[0].run
= para_first_run( para
);
2436 editor
->pCursors
[0].nOffset
= 0;
2437 editor
->pCursors
[1] = editor
->pCursors
[0];
2438 ME_CommitUndo(editor
);
2439 ME_UpdateRepaint(editor
, FALSE
);
2442 else if (para
== editor
->pCursors
[1].para
&&
2443 cursor
.nOffset
+ cursor
.run
->nCharOfs
== 0 &&
2444 para_prev( para
) && para_prev( para
)->nFlags
& MEPF_ROWSTART
&&
2445 !para_prev( para
)->nCharOfs
)
2447 /* Insert a newline before the table. */
2448 para
= para_prev( para
);
2449 para
->nFlags
&= ~MEPF_ROWSTART
;
2450 editor
->pCursors
[0].para
= para
;
2451 editor
->pCursors
[0].run
= para_first_run( para
);
2452 editor
->pCursors
[1] = editor
->pCursors
[0];
2453 ME_InsertTextFromCursor( editor
, 0, L
"\r", 1, editor
->pCursors
[0].run
->style
);
2454 para
= editor_first_para( editor
);
2455 editor_set_default_para_fmt( editor
, ¶
->fmt
);
2457 para_mark_rewrap( editor
, para
);
2458 editor
->pCursors
[0].para
= para
;
2459 editor
->pCursors
[0].run
= para_first_run( para
);
2460 editor
->pCursors
[1] = editor
->pCursors
[0];
2461 para_next( para
)->nFlags
|= MEPF_ROWSTART
;
2462 ME_CommitCoalescingUndo(editor
);
2463 ME_UpdateRepaint(editor
, FALSE
);
2467 else /* v1.0 - 3.0 */
2469 ME_Paragraph
*para
= cursor
.para
;
2470 if (para_in_table( para
))
2472 if (cursor
.run
->nFlags
& MERF_ENDPARA
)
2476 ME_ContinueCoalescingTransaction(editor
);
2477 para
= table_append_row( editor
, para
);
2478 editor
->pCursors
[0].para
= para
;
2479 editor
->pCursors
[0].run
= para_first_run( para
);
2480 editor
->pCursors
[0].nOffset
= 0;
2481 editor
->pCursors
[1] = editor
->pCursors
[0];
2482 ME_CommitCoalescingUndo(editor
);
2483 ME_UpdateRepaint(editor
, FALSE
);
2489 ME_ContinueCoalescingTransaction(editor
);
2490 if (cursor
.run
->nCharOfs
+ cursor
.nOffset
== 0 &&
2491 para_prev( para
) && !para_in_table( para_prev( para
) ))
2493 /* Insert newline before table */
2494 cursor
.run
= para_end_run( para_prev( para
) );
2497 editor
->pCursors
[0].run
= cursor
.run
;
2498 editor
->pCursors
[0].para
= para_prev( para
);
2500 editor
->pCursors
[0].nOffset
= 0;
2501 editor
->pCursors
[1] = editor
->pCursors
[0];
2502 ME_InsertTextFromCursor( editor
, 0, L
"\r", 1, editor
->pCursors
[0].run
->style
);
2506 editor
->pCursors
[1] = editor
->pCursors
[0];
2507 para
= table_append_row( editor
, para
);
2508 editor
->pCursors
[0].para
= para
;
2509 editor
->pCursors
[0].run
= para_first_run( para
);
2510 editor
->pCursors
[0].nOffset
= 0;
2511 editor
->pCursors
[1] = editor
->pCursors
[0];
2513 ME_CommitCoalescingUndo(editor
);
2514 ME_UpdateRepaint(editor
, FALSE
);
2520 style
= style_get_insert_style( editor
, editor
->pCursors
);
2522 /* Normally the new eop style is the insert style, however in a list it is copied from the existing
2523 eop style (this prevents the list label style changing when the new eop is inserted).
2524 No extra ref is taken here on eop_style. */
2525 if (para
->fmt
.wNumbering
)
2526 eop_style
= para
->eop_run
->style
;
2529 ME_ContinueCoalescingTransaction(editor
);
2531 ME_InsertEndRowFromCursor(editor
, 0);
2533 if (!editor
->bEmulateVersion10
)
2534 ME_InsertTextFromCursor(editor
, 0, L
"\r", 1, eop_style
);
2536 ME_InsertTextFromCursor(editor
, 0, L
"\r\n", 2, eop_style
);
2537 ME_CommitCoalescingUndo(editor
);
2540 ME_UpdateSelectionLinkAttribute(editor
);
2541 ME_UpdateRepaint(editor
, FALSE
);
2542 ME_SaveTempStyle(editor
, style
); /* set the temp insert style for the new para */
2543 ME_ReleaseStyle(style
);
2551 ME_KeyDown(ME_TextEditor
*editor
, WORD nKey
)
2553 BOOL ctrl_is_down
= GetKeyState(VK_CONTROL
) & 0x8000;
2554 BOOL shift_is_down
= GetKeyState(VK_SHIFT
) & 0x8000;
2556 if (editor
->bMouseCaptured
)
2558 if (nKey
!= VK_SHIFT
&& nKey
!= VK_CONTROL
&& nKey
!= VK_MENU
)
2559 editor
->nSelectionType
= stPosition
;
2567 editor
->nUDArrowX
= -1;
2573 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
2574 ME_ArrowKey(editor
, nKey
, shift_is_down
, ctrl_is_down
);
2578 editor
->nUDArrowX
= -1;
2579 /* FIXME backspace and delete aren't the same, they act different wrt paragraph style of the merged paragraph */
2580 if (editor
->props
& TXTBIT_READONLY
)
2582 if (ME_IsSelection(editor
))
2584 ME_DeleteSelection(editor
);
2585 ME_CommitUndo(editor
);
2587 else if (nKey
== VK_DELETE
)
2589 /* Delete stops group typing.
2590 * (See MSDN remarks on EM_STOPGROUPTYPING message) */
2591 ME_DeleteTextAtCursor(editor
, 1, 1);
2592 ME_CommitUndo(editor
);
2594 else if (ME_ArrowKey(editor
, VK_LEFT
, FALSE
, FALSE
))
2596 BOOL bDeletionSucceeded
;
2597 /* Backspace can be grouped for a single undo */
2598 ME_ContinueCoalescingTransaction(editor
);
2599 bDeletionSucceeded
= ME_DeleteTextAtCursor(editor
, 1, 1);
2600 if (!bDeletionSucceeded
&& !editor
->bEmulateVersion10
) { /* v4.1 */
2601 /* Deletion was prevented so the cursor is moved back to where it was.
2602 * (e.g. this happens when trying to delete cell boundaries)
2604 ME_ArrowKey(editor
, VK_RIGHT
, FALSE
, FALSE
);
2606 ME_CommitCoalescingUndo(editor
);
2610 table_move_from_row_start( editor
);
2611 ME_UpdateSelectionLinkAttribute(editor
);
2612 ME_UpdateRepaint(editor
, FALSE
);
2613 ME_SendRequestResize(editor
, FALSE
);
2616 if (!editor
->bEmulateVersion10
)
2617 return handle_enter(editor
);
2622 set_selection( editor
, 0, -1 );
2628 return paste_special( editor
, 0, NULL
, FALSE
);
2633 return copy_or_cut(editor
, nKey
== 'X');
2651 if (nKey
!= VK_SHIFT
&& nKey
!= VK_CONTROL
&& nKey
&& nKey
!= VK_MENU
)
2652 editor
->nUDArrowX
= -1;
2659 chf
.cbSize
= sizeof(chf
);
2661 ME_GetSelectionCharFormat(editor
, &chf
);
2662 ME_DumpStyleToBuf(&chf
, buf
);
2663 MessageBoxA(NULL
, buf
, "Style dump", MB_OK
);
2667 ME_CheckCharOffsets(editor
);
2674 static LRESULT
handle_wm_char( ME_TextEditor
*editor
, WCHAR wstr
, LPARAM flags
)
2676 if (editor
->bMouseCaptured
)
2679 if (editor
->props
& TXTBIT_READONLY
)
2681 editor_beep( editor
, MB_ICONERROR
);
2682 return 0; /* FIXME really 0 ? */
2685 if (editor
->bEmulateVersion10
&& wstr
== '\r')
2686 handle_enter(editor
);
2688 if ((unsigned)wstr
>= ' ' || wstr
== '\t')
2690 ME_Cursor cursor
= editor
->pCursors
[0];
2691 ME_Paragraph
*para
= cursor
.para
;
2693 BOOL ctrl_is_down
= GetKeyState(VK_CONTROL
) & 0x8000;
2694 ME_GetSelectionOfs(editor
, &from
, &to
);
2696 /* v4.1 allows tabs to be inserted with ctrl key down */
2697 !(ctrl_is_down
&& !editor
->bEmulateVersion10
))
2699 BOOL selected_row
= FALSE
;
2701 if (ME_IsSelection(editor
) &&
2702 cursor
.run
->nCharOfs
+ cursor
.nOffset
== 0 &&
2703 to
== ME_GetCursorOfs(&editor
->pCursors
[0]) && para_prev( para
))
2705 para
= para_prev( para
);
2706 selected_row
= TRUE
;
2708 if (para_in_table( para
))
2710 table_handle_tab( editor
, selected_row
);
2711 ME_CommitUndo(editor
);
2715 else if (!editor
->bEmulateVersion10
) /* v4.1 */
2717 if (para
->nFlags
& MEPF_ROWEND
)
2721 para
= para_next( para
);
2722 if (para
->nFlags
& MEPF_ROWSTART
) para
= para_next( para
);
2723 editor
->pCursors
[0].para
= para
;
2724 editor
->pCursors
[0].run
= para_first_run( para
);
2725 editor
->pCursors
[0].nOffset
= 0;
2726 editor
->pCursors
[1] = editor
->pCursors
[0];
2730 else /* v1.0 - 3.0 */
2732 if (para_in_table( para
) && cursor
.run
->nFlags
& MERF_ENDPARA
&& from
== to
)
2734 /* Text should not be inserted at the end of the table. */
2735 editor_beep( editor
, -1 );
2739 /* FIXME maybe it would make sense to call EM_REPLACESEL instead ? */
2740 /* WM_CHAR is restricted to nTextLimit */
2741 if(editor
->nTextLimit
> ME_GetTextLength(editor
) - (to
-from
))
2743 ME_Style
*style
= style_get_insert_style( editor
, editor
->pCursors
);
2744 ME_ContinueCoalescingTransaction(editor
);
2745 ME_InsertTextFromCursor(editor
, 0, &wstr
, 1, style
);
2746 ME_ReleaseStyle(style
);
2747 ME_CommitCoalescingUndo(editor
);
2748 ITextHost_TxSetCursor(editor
->texthost
, NULL
, FALSE
);
2751 ME_UpdateSelectionLinkAttribute(editor
);
2752 ME_UpdateRepaint(editor
, FALSE
);
2757 /* Process the message and calculate the new click count.
2759 * returns: The click count if it is mouse down event, else returns 0. */
2760 static int ME_CalculateClickCount(ME_TextEditor
*editor
, UINT msg
, WPARAM wParam
,
2763 static int clickNum
= 0;
2764 if (msg
< WM_MOUSEFIRST
|| msg
> WM_MOUSELAST
)
2767 if ((msg
== WM_LBUTTONDBLCLK
) ||
2768 (msg
== WM_RBUTTONDBLCLK
) ||
2769 (msg
== WM_MBUTTONDBLCLK
) ||
2770 (msg
== WM_XBUTTONDBLCLK
))
2772 msg
-= (WM_LBUTTONDBLCLK
- WM_LBUTTONDOWN
);
2775 if ((msg
== WM_LBUTTONDOWN
) ||
2776 (msg
== WM_RBUTTONDOWN
) ||
2777 (msg
== WM_MBUTTONDOWN
) ||
2778 (msg
== WM_XBUTTONDOWN
))
2780 static MSG prevClickMsg
;
2782 /* Compare the editor instead of the hwnd so that the this
2783 * can still be done for windowless richedit controls. */
2784 clickMsg
.hwnd
= (HWND
)editor
;
2785 clickMsg
.message
= msg
;
2786 clickMsg
.wParam
= wParam
;
2787 clickMsg
.lParam
= lParam
;
2788 clickMsg
.time
= GetMessageTime();
2789 clickMsg
.pt
.x
= (short)LOWORD(lParam
);
2790 clickMsg
.pt
.y
= (short)HIWORD(lParam
);
2791 if ((clickNum
!= 0) &&
2792 (clickMsg
.message
== prevClickMsg
.message
) &&
2793 (clickMsg
.hwnd
== prevClickMsg
.hwnd
) &&
2794 (clickMsg
.wParam
== prevClickMsg
.wParam
) &&
2795 (clickMsg
.time
- prevClickMsg
.time
< GetDoubleClickTime()) &&
2796 (abs(clickMsg
.pt
.x
- prevClickMsg
.pt
.x
) < GetSystemMetrics(SM_CXDOUBLECLK
)/2) &&
2797 (abs(clickMsg
.pt
.y
- prevClickMsg
.pt
.y
) < GetSystemMetrics(SM_CYDOUBLECLK
)/2))
2803 prevClickMsg
= clickMsg
;
2810 static BOOL
is_link( ME_Run
*run
)
2812 return (run
->style
->fmt
.dwMask
& CFM_LINK
) && (run
->style
->fmt
.dwEffects
& CFE_LINK
);
2815 void editor_set_cursor( ME_TextEditor
*editor
, int x
, int y
)
2819 static HCURSOR cursor_arrow
, cursor_hand
, cursor_ibeam
, cursor_reverse
;
2824 cursor_arrow
= LoadCursorW( NULL
, MAKEINTRESOURCEW( IDC_ARROW
) );
2825 cursor_hand
= LoadCursorW( NULL
, MAKEINTRESOURCEW( IDC_HAND
) );
2826 cursor_ibeam
= LoadCursorW( NULL
, MAKEINTRESOURCEW( IDC_IBEAM
) );
2827 cursor_reverse
= LoadCursorW( dll_instance
, MAKEINTRESOURCEW( OCR_REVERSE
) );
2830 cursor
= cursor_ibeam
;
2832 if ((editor
->nSelectionType
== stLine
&& editor
->bMouseCaptured
) ||
2833 (!editor
->bEmulateVersion10
&& y
< editor
->rcFormat
.top
&& x
< editor
->rcFormat
.left
))
2834 cursor
= cursor_reverse
;
2835 else if (y
< editor
->rcFormat
.top
|| y
> editor
->rcFormat
.bottom
)
2837 if (editor
->bEmulateVersion10
) cursor
= cursor_arrow
;
2838 else cursor
= cursor_ibeam
;
2840 else if (x
< editor
->rcFormat
.left
) cursor
= cursor_reverse
;
2843 ME_CharFromPos( editor
, x
, y
, &pos
, &is_exact
);
2846 ME_Run
*run
= pos
.run
;
2848 if (is_link( run
)) cursor
= cursor_hand
;
2850 else if (ME_IsSelection( editor
))
2852 int start
, end
, offset
= ME_GetCursorOfs( &pos
);
2854 ME_GetSelectionOfs( editor
, &start
, &end
);
2855 if (start
<= offset
&& end
>= offset
) cursor
= cursor_arrow
;
2860 ITextHost_TxSetCursor( editor
->texthost
, cursor
, cursor
== cursor_ibeam
);
2863 static LONG
ME_GetSelectionType(ME_TextEditor
*editor
)
2865 LONG sel_type
= SEL_EMPTY
;
2868 ME_GetSelectionOfs(editor
, &start
, &end
);
2870 sel_type
= SEL_EMPTY
;
2873 LONG object_count
= 0, character_count
= 0;
2876 for (i
= 0; i
< end
- start
; i
++)
2880 cursor_from_char_ofs( editor
, start
+ i
, &cursor
);
2881 if (cursor
.run
->reobj
) object_count
++;
2882 else character_count
++;
2883 if (character_count
>= 2 && object_count
>= 2)
2884 return (SEL_TEXT
| SEL_MULTICHAR
| SEL_OBJECT
| SEL_MULTIOBJECT
);
2886 if (character_count
)
2888 sel_type
|= SEL_TEXT
;
2889 if (character_count
>= 2)
2890 sel_type
|= SEL_MULTICHAR
;
2894 sel_type
|= SEL_OBJECT
;
2895 if (object_count
>= 2)
2896 sel_type
|= SEL_MULTIOBJECT
;
2902 static BOOL
ME_ShowContextMenu(ME_TextEditor
*editor
, int x
, int y
)
2909 if (!editor
->lpOleCallback
|| !editor
->have_texthost2
) return FALSE
;
2910 if (FAILED( ITextHost2_TxGetWindow( editor
->texthost
, &hwnd
))) return FALSE
;
2911 parent
= GetParent( hwnd
);
2912 if (!parent
) parent
= hwnd
;
2914 ME_GetSelectionOfs( editor
, &selrange
.cpMin
, &selrange
.cpMax
);
2915 seltype
= ME_GetSelectionType( editor
);
2916 if (SUCCEEDED( IRichEditOleCallback_GetContextMenu( editor
->lpOleCallback
, seltype
, NULL
, &selrange
, &menu
) ))
2918 TrackPopupMenu( menu
, TPM_LEFTALIGN
| TPM_RIGHTBUTTON
, x
, y
, 0, parent
, NULL
);
2919 DestroyMenu( menu
);
2924 ME_TextEditor
*ME_MakeEditor(ITextHost
*texthost
, BOOL bEmulateVersion10
)
2926 ME_TextEditor
*ed
= heap_alloc(sizeof(*ed
));
2931 ed
->sizeWindow
.cx
= ed
->sizeWindow
.cy
= 0;
2932 if (ITextHost_QueryInterface( texthost
, &IID_ITextHost2
, (void **)&ed
->texthost
) == S_OK
)
2934 ITextHost_Release( texthost
);
2935 ed
->have_texthost2
= TRUE
;
2939 ed
->texthost
= (ITextHost2
*)texthost
;
2940 ed
->have_texthost2
= FALSE
;
2943 ed
->bEmulateVersion10
= bEmulateVersion10
;
2944 ed
->in_place_active
= FALSE
;
2946 ITextHost_TxGetPropertyBits( ed
->texthost
, TXTBIT_RICHTEXT
| TXTBIT_MULTILINE
| TXTBIT_READONLY
|
2947 TXTBIT_USEPASSWORD
| TXTBIT_HIDESELECTION
| TXTBIT_SAVESELECTION
|
2948 TXTBIT_AUTOWORDSEL
| TXTBIT_VERTICAL
| TXTBIT_WORDWRAP
| TXTBIT_ALLOWBEEP
|
2951 ITextHost_TxGetScrollBars( ed
->texthost
, &ed
->scrollbars
);
2952 ed
->pBuffer
= ME_MakeText();
2953 ed
->nZoomNumerator
= ed
->nZoomDenominator
= 0;
2954 ed
->nAvailWidth
= 0; /* wrap to client area */
2955 list_init( &ed
->style_list
);
2956 ME_MakeFirstParagraph(ed
);
2957 /* The four cursors are for:
2958 * 0 - The position where the caret is shown
2959 * 1 - The anchored end of the selection (for normal selection)
2960 * 2 & 3 - The anchored start and end respectively for word, line,
2961 * or paragraph selection.
2964 ed
->pCursors
= heap_alloc(ed
->nCursors
* sizeof(*ed
->pCursors
));
2965 ME_SetCursorToStart(ed
, &ed
->pCursors
[0]);
2966 ed
->pCursors
[1] = ed
->pCursors
[0];
2967 ed
->pCursors
[2] = ed
->pCursors
[0];
2968 ed
->pCursors
[3] = ed
->pCursors
[1];
2969 ed
->nLastTotalLength
= ed
->nTotalLength
= 0;
2970 ed
->nLastTotalWidth
= ed
->nTotalWidth
= 0;
2973 ed
->nModifyStep
= 0;
2974 ed
->nTextLimit
= TEXT_LIMIT_DEFAULT
;
2975 list_init( &ed
->undo_stack
);
2976 list_init( &ed
->redo_stack
);
2977 ed
->nUndoStackSize
= 0;
2978 ed
->nUndoLimit
= STACK_SIZE_DEFAULT
;
2979 ed
->nUndoMode
= umAddToUndo
;
2980 ed
->nParagraphs
= 1;
2981 ed
->nLastSelStart
= ed
->nLastSelEnd
= 0;
2982 ed
->last_sel_start_para
= ed
->last_sel_end_para
= ed
->pCursors
[0].para
;
2983 ed
->bHideSelection
= FALSE
;
2984 ed
->pfnWordBreak
= NULL
;
2986 ed
->lpOleCallback
= NULL
;
2987 ed
->mode
= TM_MULTILEVELUNDO
| TM_MULTICODEPAGE
;
2988 ed
->mode
|= (ed
->props
& TXTBIT_RICHTEXT
) ? TM_RICHTEXT
: TM_PLAINTEXT
;
2989 ed
->AutoURLDetect_bEnable
= FALSE
;
2990 ed
->bHaveFocus
= FALSE
;
2991 ed
->bMouseCaptured
= FALSE
;
2992 ed
->caret_hidden
= FALSE
;
2993 ed
->caret_height
= 0;
2994 for (i
=0; i
<HFONT_CACHE_SIZE
; i
++)
2996 ed
->pFontCache
[i
].nRefs
= 0;
2997 ed
->pFontCache
[i
].nAge
= 0;
2998 ed
->pFontCache
[i
].hFont
= NULL
;
3001 ME_CheckCharOffsets(ed
);
3002 SetRectEmpty(&ed
->rcFormat
);
3003 hr
= ITextHost_TxGetSelectionBarWidth( ed
->texthost
, &selbarwidth
);
3004 /* FIXME: Convert selbarwidth from HIMETRIC to pixels */
3005 if (hr
== S_OK
&& selbarwidth
) ed
->selofs
= SELECTIONBAR_WIDTH
;
3006 else ed
->selofs
= 0;
3007 ed
->nSelectionType
= stPosition
;
3009 ed
->password_char
= 0;
3010 if (ed
->props
& TXTBIT_USEPASSWORD
)
3011 ITextHost_TxGetPasswordChar( ed
->texthost
, &ed
->password_char
);
3013 ed
->bWordWrap
= (ed
->props
& TXTBIT_WORDWRAP
) && (ed
->props
& TXTBIT_MULTILINE
);
3015 ed
->notified_cr
.cpMin
= ed
->notified_cr
.cpMax
= 0;
3017 /* Default scrollbar information */
3018 ed
->vert_si
.cbSize
= sizeof(SCROLLINFO
);
3019 ed
->vert_si
.nMin
= 0;
3020 ed
->vert_si
.nMax
= 0;
3021 ed
->vert_si
.nPage
= 0;
3022 ed
->vert_si
.nPos
= 0;
3023 ed
->vert_sb_enabled
= 0;
3025 ed
->horz_si
.cbSize
= sizeof(SCROLLINFO
);
3026 ed
->horz_si
.nMin
= 0;
3027 ed
->horz_si
.nMax
= 0;
3028 ed
->horz_si
.nPage
= 0;
3029 ed
->horz_si
.nPos
= 0;
3030 ed
->horz_sb_enabled
= 0;
3032 if (ed
->scrollbars
& ES_DISABLENOSCROLL
)
3034 if (ed
->scrollbars
& WS_VSCROLL
)
3036 ITextHost_TxSetScrollRange( ed
->texthost
, SB_VERT
, 0, 1, TRUE
);
3037 ITextHost_TxEnableScrollBar( ed
->texthost
, SB_VERT
, ESB_DISABLE_BOTH
);
3039 if (ed
->scrollbars
& WS_HSCROLL
)
3041 ITextHost_TxSetScrollRange( ed
->texthost
, SB_HORZ
, 0, 1, TRUE
);
3042 ITextHost_TxEnableScrollBar( ed
->texthost
, SB_HORZ
, ESB_DISABLE_BOTH
);
3046 ed
->wheel_remain
= 0;
3048 ed
->back_style
= TXTBACK_OPAQUE
;
3049 ITextHost_TxGetBackStyle( ed
->texthost
, &ed
->back_style
);
3051 list_init( &ed
->reobj_list
);
3052 OleInitialize(NULL
);
3057 void ME_DestroyEditor(ME_TextEditor
*editor
)
3059 ME_DisplayItem
*p
= editor
->pBuffer
->pFirst
, *pNext
= NULL
;
3060 ME_Style
*s
, *cursor2
;
3063 ME_ClearTempStyle(editor
);
3064 ME_EmptyUndoStack(editor
);
3065 editor
->pBuffer
->pFirst
= NULL
;
3069 if (p
->type
== diParagraph
)
3070 para_destroy( editor
, &p
->member
.para
);
3072 ME_DestroyDisplayItem(p
);
3076 LIST_FOR_EACH_ENTRY_SAFE( s
, cursor2
, &editor
->style_list
, ME_Style
, entry
)
3077 ME_DestroyStyle( s
);
3079 ME_ReleaseStyle(editor
->pBuffer
->pDefaultStyle
);
3080 for (i
=0; i
<HFONT_CACHE_SIZE
; i
++)
3082 if (editor
->pFontCache
[i
].hFont
)
3083 DeleteObject(editor
->pFontCache
[i
].hFont
);
3085 if(editor
->lpOleCallback
)
3086 IRichEditOleCallback_Release(editor
->lpOleCallback
);
3090 heap_free(editor
->pBuffer
);
3091 heap_free(editor
->pCursors
);
3095 static inline int get_default_line_height( ME_TextEditor
*editor
)
3099 if (editor
->pBuffer
&& editor
->pBuffer
->pDefaultStyle
)
3100 height
= editor
->pBuffer
->pDefaultStyle
->tm
.tmHeight
;
3101 if (height
<= 0) height
= 24;
3106 static inline int calc_wheel_change( int *remain
, int amount_per_click
)
3108 int change
= amount_per_click
* (float)*remain
/ WHEEL_DELTA
;
3109 *remain
-= WHEEL_DELTA
* change
/ amount_per_click
;
3113 void link_notify(ME_TextEditor
*editor
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
3117 ME_Cursor cursor
; /* The start of the clicked text. */
3121 x
= (short)LOWORD(lParam
);
3122 y
= (short)HIWORD(lParam
);
3123 ME_CharFromPos(editor
, x
, y
, &cursor
, &isExact
);
3124 if (!isExact
) return;
3126 if (is_link( cursor
.run
))
3127 { /* The clicked run has CFE_LINK set */
3128 info
.nmhdr
.hwndFrom
= NULL
;
3129 info
.nmhdr
.idFrom
= 0;
3130 info
.nmhdr
.code
= EN_LINK
;
3132 info
.wParam
= wParam
;
3133 info
.lParam
= lParam
;
3136 /* find the first contiguous run with CFE_LINK set */
3137 info
.chrg
.cpMin
= ME_GetCursorOfs(&cursor
);
3139 while ((run
= run_prev( run
)) && is_link( run
))
3140 info
.chrg
.cpMin
-= run
->len
;
3142 /* find the last contiguous run with CFE_LINK set */
3143 info
.chrg
.cpMax
= ME_GetCursorOfs(&cursor
) + cursor
.run
->len
;
3145 while ((run
= run_next( run
)) && is_link( run
))
3146 info
.chrg
.cpMax
+= run
->len
;
3148 ITextHost_TxNotify(editor
->texthost
, info
.nmhdr
.code
, &info
);
3152 void ME_ReplaceSel(ME_TextEditor
*editor
, BOOL can_undo
, const WCHAR
*str
, int len
)
3154 int from
, to
, nStartCursor
;
3157 nStartCursor
= ME_GetSelectionOfs(editor
, &from
, &to
);
3158 style
= ME_GetSelectionInsertStyle(editor
);
3159 ME_InternalDeleteText(editor
, &editor
->pCursors
[nStartCursor
], to
-from
, FALSE
);
3160 ME_InsertTextFromCursor(editor
, 0, str
, len
, style
);
3161 ME_ReleaseStyle(style
);
3162 /* drop temporary style if line end */
3164 * FIXME question: does abc\n mean: put abc,
3165 * clear temp style, put \n? (would require a change)
3167 if (len
>0 && str
[len
-1] == '\n')
3168 ME_ClearTempStyle(editor
);
3169 ME_CommitUndo(editor
);
3170 ME_UpdateSelectionLinkAttribute(editor
);
3172 ME_EmptyUndoStack(editor
);
3173 ME_UpdateRepaint(editor
, FALSE
);
3176 static void ME_SetText(ME_TextEditor
*editor
, void *text
, BOOL unicode
)
3178 LONG codepage
= unicode
? CP_UNICODE
: CP_ACP
;
3181 LPWSTR wszText
= ME_ToUnicode(codepage
, text
, &textLen
);
3182 ME_InsertTextFromCursor(editor
, 0, wszText
, textLen
, editor
->pBuffer
->pDefaultStyle
);
3183 ME_EndToUnicode(codepage
, wszText
);
3186 static LRESULT
handle_EM_SETCHARFORMAT( ME_TextEditor
*editor
, WPARAM flags
, const CHARFORMAT2W
*fmt_in
)
3189 BOOL changed
= TRUE
;
3190 ME_Cursor start
, end
;
3192 if (!cfany_to_cf2w( &fmt
, fmt_in
)) return 0;
3194 if (flags
& SCF_ALL
)
3196 if (editor
->mode
& TM_PLAINTEXT
)
3198 ME_SetDefaultCharFormat( editor
, &fmt
);
3202 ME_SetCursorToStart( editor
, &start
);
3203 ME_SetCharFormat( editor
, &start
, NULL
, &fmt
);
3204 editor
->nModifyStep
= 1;
3207 else if (flags
& SCF_SELECTION
)
3209 if (editor
->mode
& TM_PLAINTEXT
) return 0;
3210 if (flags
& SCF_WORD
)
3212 end
= editor
->pCursors
[0];
3213 ME_MoveCursorWords( editor
, &end
, +1 );
3215 ME_MoveCursorWords( editor
, &start
, -1 );
3216 ME_SetCharFormat( editor
, &start
, &end
, &fmt
);
3218 changed
= ME_IsSelection( editor
);
3219 ME_SetSelectionCharFormat( editor
, &fmt
);
3220 if (changed
) editor
->nModifyStep
= 1;
3222 else /* SCF_DEFAULT */
3224 ME_SetDefaultCharFormat( editor
, &fmt
);
3227 ME_CommitUndo( editor
);
3230 ME_WrapMarkedParagraphs( editor
);
3231 ME_UpdateScrollBar( editor
);
3236 #define UNSUPPORTED_MSG(e) \
3238 FIXME(#e ": stub\n"); \
3239 *phresult = S_FALSE; \
3242 /* Handle messages for windowless and windowed richedit controls.
3244 * The LRESULT that is returned is a return value for window procs,
3245 * and the phresult parameter is the COM return code needed by the
3246 * text services interface. */
3247 LRESULT
editor_handle_message( ME_TextEditor
*editor
, UINT msg
, WPARAM wParam
,
3248 LPARAM lParam
, HRESULT
* phresult
)
3254 UNSUPPORTED_MSG(EM_DISPLAYBAND
)
3255 UNSUPPORTED_MSG(EM_FINDWORDBREAK
)
3256 UNSUPPORTED_MSG(EM_FMTLINES
)
3257 UNSUPPORTED_MSG(EM_FORMATRANGE
)
3258 UNSUPPORTED_MSG(EM_GETBIDIOPTIONS
)
3259 UNSUPPORTED_MSG(EM_GETEDITSTYLE
)
3260 UNSUPPORTED_MSG(EM_GETIMECOMPMODE
)
3261 UNSUPPORTED_MSG(EM_GETIMESTATUS
)
3262 UNSUPPORTED_MSG(EM_SETIMESTATUS
)
3263 UNSUPPORTED_MSG(EM_GETLANGOPTIONS
)
3264 UNSUPPORTED_MSG(EM_GETREDONAME
)
3265 UNSUPPORTED_MSG(EM_GETTYPOGRAPHYOPTIONS
)
3266 UNSUPPORTED_MSG(EM_GETUNDONAME
)
3267 UNSUPPORTED_MSG(EM_GETWORDBREAKPROCEX
)
3268 UNSUPPORTED_MSG(EM_SETBIDIOPTIONS
)
3269 UNSUPPORTED_MSG(EM_SETEDITSTYLE
)
3270 UNSUPPORTED_MSG(EM_SETLANGOPTIONS
)
3271 UNSUPPORTED_MSG(EM_SETMARGINS
)
3272 UNSUPPORTED_MSG(EM_SETPALETTE
)
3273 UNSUPPORTED_MSG(EM_SETTABSTOPS
)
3274 UNSUPPORTED_MSG(EM_SETTYPOGRAPHYOPTIONS
)
3275 UNSUPPORTED_MSG(EM_SETWORDBREAKPROCEX
)
3277 /* Messages specific to Richedit controls */
3280 return ME_StreamIn(editor
, wParam
, (EDITSTREAM
*)lParam
, TRUE
);
3282 return ME_StreamOut(editor
, wParam
, (EDITSTREAM
*)lParam
);
3283 case EM_EMPTYUNDOBUFFER
:
3284 ME_EmptyUndoStack(editor
);
3288 /* Note: wParam/lParam can be NULL */
3290 PUINT pfrom
= wParam
? (PUINT
)wParam
: &from
;
3291 PUINT pto
= lParam
? (PUINT
)lParam
: &to
;
3292 ME_GetSelectionOfs(editor
, (int *)pfrom
, (int *)pto
);
3293 if ((*pfrom
|*pto
) & 0xFFFF0000)
3295 return MAKELONG(*pfrom
,*pto
);
3299 CHARRANGE
*pRange
= (CHARRANGE
*)lParam
;
3300 ME_GetSelectionOfs(editor
, &pRange
->cpMin
, &pRange
->cpMax
);
3301 TRACE("EM_EXGETSEL = (%d,%d)\n", pRange
->cpMin
, pRange
->cpMax
);
3304 case EM_SETUNDOLIMIT
:
3306 if ((int)wParam
< 0)
3307 editor
->nUndoLimit
= STACK_SIZE_DEFAULT
;
3309 editor
->nUndoLimit
= min(wParam
, STACK_SIZE_MAX
);
3310 /* Setting a max stack size keeps wine from getting killed
3311 for hogging memory. Windows allocates all this memory at once, so
3312 no program would realistically set a value above our maximum. */
3313 return editor
->nUndoLimit
;
3316 return !list_empty( &editor
->undo_stack
);
3318 return !list_empty( &editor
->redo_stack
);
3319 case WM_UNDO
: /* FIXME: actually not the same */
3321 return ME_Undo(editor
);
3323 return ME_Redo(editor
);
3324 case EM_SETFONTSIZE
:
3327 LONG tmp_size
, size
;
3328 BOOL is_increase
= ((LONG
)wParam
> 0);
3330 if (editor
->mode
& TM_PLAINTEXT
)
3333 cf
.cbSize
= sizeof(cf
);
3334 cf
.dwMask
= CFM_SIZE
;
3335 ME_GetSelectionCharFormat(editor
, &cf
);
3336 tmp_size
= (cf
.yHeight
/ 20) + wParam
;
3340 else if (tmp_size
> 12 && tmp_size
< 28 && tmp_size
% 2)
3341 size
= tmp_size
+ (is_increase
? 1 : -1);
3342 else if (tmp_size
> 28 && tmp_size
< 36)
3343 size
= is_increase
? 36 : 28;
3344 else if (tmp_size
> 36 && tmp_size
< 48)
3345 size
= is_increase
? 48 : 36;
3346 else if (tmp_size
> 48 && tmp_size
< 72)
3347 size
= is_increase
? 72 : 48;
3348 else if (tmp_size
> 72 && tmp_size
< 80)
3349 size
= is_increase
? 80 : 72;
3350 else if (tmp_size
> 80 && tmp_size
< 1638)
3351 size
= 10 * (is_increase
? (tmp_size
/ 10 + 1) : (tmp_size
/ 10));
3352 else if (tmp_size
>= 1638)
3357 cf
.yHeight
= size
* 20; /* convert twips to points */
3358 ME_SetSelectionCharFormat(editor
, &cf
);
3359 ME_CommitUndo(editor
);
3360 ME_WrapMarkedParagraphs(editor
);
3361 ME_UpdateScrollBar(editor
);
3367 return set_selection( editor
, wParam
, lParam
);
3369 case EM_SETSCROLLPOS
:
3371 POINT
*point
= (POINT
*)lParam
;
3372 scroll_abs( editor
, point
->x
, point
->y
, TRUE
);
3375 case EM_AUTOURLDETECT
:
3377 if (wParam
==1 || wParam
==0)
3379 editor
->AutoURLDetect_bEnable
= (BOOL
)wParam
;
3382 return E_INVALIDARG
;
3384 case EM_GETAUTOURLDETECT
:
3386 return editor
->AutoURLDetect_bEnable
;
3390 CHARRANGE range
= *(CHARRANGE
*)lParam
;
3392 return set_selection( editor
, range
.cpMin
, range
.cpMax
);
3397 SETTEXTEX
*pStruct
= (SETTEXTEX
*)wParam
;
3400 BOOL bRtf
, bUnicode
, bSelection
, bUTF8
;
3401 int oldModify
= editor
->nModifyStep
;
3402 static const char utf8_bom
[] = {0xef, 0xbb, 0xbf};
3404 if (!pStruct
) return 0;
3406 /* If we detect ascii rtf at the start of the string,
3407 * we know it isn't unicode. */
3408 bRtf
= (lParam
&& (!strncmp((char *)lParam
, "{\\rtf", 5) ||
3409 !strncmp((char *)lParam
, "{\\urtf", 6)));
3410 bUnicode
= !bRtf
&& pStruct
->codepage
== CP_UNICODE
;
3411 bUTF8
= (lParam
&& (!strncmp((char *)lParam
, utf8_bom
, 3)));
3413 TRACE("EM_SETTEXTEX - %s, flags %d, cp %d\n",
3414 bUnicode
? debugstr_w((LPCWSTR
)lParam
) : debugstr_a((LPCSTR
)lParam
),
3415 pStruct
->flags
, pStruct
->codepage
);
3417 bSelection
= (pStruct
->flags
& ST_SELECTION
) != 0;
3419 int nStartCursor
= ME_GetSelectionOfs(editor
, &from
, &to
);
3420 style
= ME_GetSelectionInsertStyle(editor
);
3421 ME_InternalDeleteText(editor
, &editor
->pCursors
[nStartCursor
], to
- from
, FALSE
);
3424 ME_SetCursorToStart(editor
, &start
);
3425 ME_InternalDeleteText(editor
, &start
, ME_GetTextLength(editor
), FALSE
);
3426 style
= editor
->pBuffer
->pDefaultStyle
;
3430 ME_StreamInRTFString(editor
, bSelection
, (char *)lParam
);
3432 /* FIXME: The length returned doesn't include the rtf control
3433 * characters, only the actual text. */
3434 len
= lParam
? strlen((char *)lParam
) : 0;
3437 if (bUTF8
&& !bUnicode
) {
3438 wszText
= ME_ToUnicode(CP_UTF8
, (void *)(lParam
+3), &len
);
3439 ME_InsertTextFromCursor(editor
, 0, wszText
, len
, style
);
3440 ME_EndToUnicode(CP_UTF8
, wszText
);
3442 wszText
= ME_ToUnicode(pStruct
->codepage
, (void *)lParam
, &len
);
3443 ME_InsertTextFromCursor(editor
, 0, wszText
, len
, style
);
3444 ME_EndToUnicode(pStruct
->codepage
, wszText
);
3449 ME_ReleaseStyle(style
);
3450 ME_UpdateSelectionLinkAttribute(editor
);
3454 ME_SetCursorToStart(editor
, &cursor
);
3455 ME_UpdateLinkAttribute(editor
, &cursor
, INT_MAX
);
3457 ME_CommitUndo(editor
);
3458 if (!(pStruct
->flags
& ST_KEEPUNDO
))
3460 editor
->nModifyStep
= oldModify
;
3461 ME_EmptyUndoStack(editor
);
3463 ME_UpdateRepaint(editor
, FALSE
);
3466 case EM_SELECTIONTYPE
:
3467 return ME_GetSelectionType(editor
);
3469 return editor
->nModifyStep
== 0 ? 0 : -1;
3473 editor
->nModifyStep
= 1;
3475 editor
->nModifyStep
= 0;
3479 case EM_SETEVENTMASK
:
3481 DWORD nOldMask
= editor
->nEventMask
;
3483 editor
->nEventMask
= lParam
;
3486 case EM_GETEVENTMASK
:
3487 return editor
->nEventMask
;
3488 case EM_SETCHARFORMAT
:
3489 return handle_EM_SETCHARFORMAT( editor
, wParam
, (CHARFORMAT2W
*)lParam
);
3490 case EM_GETCHARFORMAT
:
3492 CHARFORMAT2W tmp
, *dst
= (CHARFORMAT2W
*)lParam
;
3493 if (dst
->cbSize
!= sizeof(CHARFORMATA
) &&
3494 dst
->cbSize
!= sizeof(CHARFORMATW
) &&
3495 dst
->cbSize
!= sizeof(CHARFORMAT2A
) &&
3496 dst
->cbSize
!= sizeof(CHARFORMAT2W
))
3498 tmp
.cbSize
= sizeof(tmp
);
3500 ME_GetDefaultCharFormat(editor
, &tmp
);
3502 ME_GetSelectionCharFormat(editor
, &tmp
);
3503 cf2w_to_cfany(dst
, &tmp
);
3506 case EM_SETPARAFORMAT
:
3508 BOOL result
= editor_set_selection_para_fmt( editor
, (PARAFORMAT2
*)lParam
);
3509 ME_WrapMarkedParagraphs(editor
);
3510 ME_UpdateScrollBar(editor
);
3511 ME_CommitUndo(editor
);
3514 case EM_GETPARAFORMAT
:
3515 editor_get_selection_para_fmt( editor
, (PARAFORMAT2
*)lParam
);
3516 return ((PARAFORMAT2
*)lParam
)->dwMask
;
3517 case EM_GETFIRSTVISIBLELINE
:
3519 ME_Paragraph
*para
= editor_first_para( editor
);
3521 int y
= editor
->vert_si
.nPos
;
3524 while (para_next( para
))
3526 if (y
< para
->pt
.y
+ para
->nHeight
) break;
3527 count
+= para
->nRows
;
3528 para
= para_next( para
);
3531 row
= para_first_row( para
);
3534 if (y
< para
->pt
.y
+ row
->pt
.y
+ row
->nHeight
) break;
3536 row
= row_next( row
);
3540 case EM_HIDESELECTION
:
3542 editor
->bHideSelection
= (wParam
!= 0);
3543 ME_InvalidateSelection(editor
);
3548 if (!(editor
->props
& TXTBIT_MULTILINE
))
3550 ME_ScrollDown( editor
, lParam
* get_default_line_height( editor
) );
3556 int nStartCursor
= ME_GetSelectionOfs(editor
, &from
, &to
);
3557 ME_InternalDeleteText(editor
, &editor
->pCursors
[nStartCursor
], to
-from
, FALSE
);
3558 ME_CommitUndo(editor
);
3559 ME_UpdateRepaint(editor
, TRUE
);
3564 WCHAR
*text
= (WCHAR
*)lParam
;
3565 int len
= text
? lstrlenW( text
) : 0;
3567 TRACE( "EM_REPLACESEL - %s\n", debugstr_w( text
) );
3568 ME_ReplaceSel( editor
, !!wParam
, text
, len
);
3571 case EM_SCROLLCARET
:
3572 editor_ensure_visible( editor
, &editor
->pCursors
[0] );
3579 BOOL bRepaint
= LOWORD(lParam
);
3582 wParam
= (WPARAM
)GetStockObject(SYSTEM_FONT
);
3584 if (!GetObjectW((HGDIOBJ
)wParam
, sizeof(LOGFONTW
), &lf
))
3587 hDC
= ITextHost_TxGetDC(editor
->texthost
);
3588 ME_CharFormatFromLogFont(hDC
, &lf
, &fmt
);
3589 ITextHost_TxReleaseDC(editor
->texthost
, hDC
);
3590 if (editor
->mode
& TM_RICHTEXT
) {
3592 ME_SetCursorToStart(editor
, &start
);
3593 ME_SetCharFormat(editor
, &start
, NULL
, &fmt
);
3595 ME_SetDefaultCharFormat(editor
, &fmt
);
3597 ME_CommitUndo(editor
);
3598 editor_mark_rewrap_all( editor
);
3599 ME_WrapMarkedParagraphs(editor
);
3600 ME_UpdateScrollBar(editor
);
3608 ME_SetCursorToStart(editor
, &cursor
);
3609 ME_InternalDeleteText(editor
, &cursor
, ME_GetTextLength(editor
), FALSE
);
3612 TRACE("WM_SETTEXT lParam==%lx\n",lParam
);
3613 if (!strncmp((char *)lParam
, "{\\rtf", 5) ||
3614 !strncmp((char *)lParam
, "{\\urtf", 6))
3616 /* Undocumented: WM_SETTEXT supports RTF text */
3617 ME_StreamInRTFString(editor
, 0, (char *)lParam
);
3620 ME_SetText( editor
, (void*)lParam
, TRUE
);
3623 TRACE("WM_SETTEXT - NULL\n");
3624 ME_SetCursorToStart(editor
, &cursor
);
3625 ME_UpdateLinkAttribute(editor
, &cursor
, INT_MAX
);
3626 set_selection_cursors(editor
, 0, 0);
3627 editor
->nModifyStep
= 0;
3628 ME_CommitUndo(editor
);
3629 ME_EmptyUndoStack(editor
);
3630 ME_UpdateRepaint(editor
, FALSE
);
3634 return paste_special( editor
, 0, NULL
, TRUE
);
3636 case WM_MBUTTONDOWN
:
3640 case EM_PASTESPECIAL
:
3641 paste_special( editor
, wParam
, (REPASTESPECIAL
*)lParam
, FALSE
);
3645 copy_or_cut(editor
, msg
== WM_CUT
);
3647 case WM_GETTEXTLENGTH
:
3649 GETTEXTLENGTHEX how
;
3650 how
.flags
= GTL_CLOSE
| (editor
->bEmulateVersion10
? 0 : GTL_USECRLF
) | GTL_NUMCHARS
;
3651 how
.codepage
= CP_UNICODE
;
3652 return ME_GetTextLengthEx(editor
, &how
);
3654 case EM_GETTEXTLENGTHEX
:
3655 return ME_GetTextLengthEx(editor
, (GETTEXTLENGTHEX
*)wParam
);
3659 ex
.cb
= wParam
* sizeof(WCHAR
);
3660 ex
.flags
= GT_USECRLF
;
3661 ex
.codepage
= CP_UNICODE
;
3662 ex
.lpDefaultChar
= NULL
;
3663 ex
.lpUsedDefChar
= NULL
;
3664 return ME_GetTextEx(editor
, &ex
, lParam
);
3667 return ME_GetTextEx(editor
, (GETTEXTEX
*)wParam
, lParam
);
3670 int nFrom
, nTo
, nStartCur
= ME_GetSelectionOfs(editor
, &nFrom
, &nTo
);
3671 ME_Cursor
*from
= &editor
->pCursors
[nStartCur
];
3672 return get_text_range( editor
, (WCHAR
*)lParam
, from
, nTo
- nFrom
);
3674 case EM_GETSCROLLPOS
:
3676 POINT
*point
= (POINT
*)lParam
;
3677 point
->x
= editor
->horz_si
.nPos
;
3678 point
->y
= editor
->vert_si
.nPos
;
3679 /* 16-bit scaled value is returned as stored in scrollinfo */
3680 if (editor
->horz_si
.nMax
> 0xffff)
3681 point
->x
= MulDiv(point
->x
, 0xffff, editor
->horz_si
.nMax
);
3682 if (editor
->vert_si
.nMax
> 0xffff)
3683 point
->y
= MulDiv(point
->y
, 0xffff, editor
->vert_si
.nMax
);
3686 case EM_GETTEXTRANGE
:
3688 TEXTRANGEW
*rng
= (TEXTRANGEW
*)lParam
;
3690 int nStart
= rng
->chrg
.cpMin
;
3691 int nEnd
= rng
->chrg
.cpMax
;
3692 int textlength
= ME_GetTextLength(editor
);
3694 TRACE( "EM_GETTEXTRANGE min = %d max = %d textlength = %d\n", rng
->chrg
.cpMin
, rng
->chrg
.cpMax
, textlength
);
3695 if (nStart
< 0) return 0;
3696 if ((nStart
== 0 && nEnd
== -1) || nEnd
> textlength
)
3698 if (nStart
>= nEnd
) return 0;
3700 cursor_from_char_ofs( editor
, nStart
, &start
);
3701 return get_text_range( editor
, rng
->lpstrText
, &start
, nEnd
- nStart
);
3707 const unsigned int nMaxChars
= *(WORD
*) lParam
;
3708 unsigned int nCharsLeft
= nMaxChars
;
3709 char *dest
= (char *) lParam
;
3710 ME_Cursor start
, end
;
3712 TRACE( "EM_GETLINE: row=%d, nMaxChars=%d\n", (int)wParam
, nMaxChars
);
3714 row
= row_from_row_number( editor
, wParam
);
3715 if (row
== NULL
) return 0;
3717 row_first_cursor( row
, &start
);
3718 row_end_cursor( row
, &end
, TRUE
);
3724 int ofs
= (run
== start
.run
) ? start
.nOffset
: 0;
3725 int len
= (run
== end
.run
) ? end
.nOffset
: run
->len
;
3727 str
= get_text( run
, ofs
);
3728 nCopy
= min( nCharsLeft
, len
);
3730 memcpy(dest
, str
, nCopy
* sizeof(WCHAR
));
3731 dest
+= nCopy
* sizeof(WCHAR
);
3732 nCharsLeft
-= nCopy
;
3733 if (run
== end
.run
) break;
3734 run
= row_next_run( row
, run
);
3737 /* append line termination, space allowing */
3738 if (nCharsLeft
> 0) *((WCHAR
*)dest
) = '\0';
3740 TRACE("EM_GETLINE: got %u characters\n", nMaxChars
- nCharsLeft
);
3741 return nMaxChars
- nCharsLeft
;
3743 case EM_GETLINECOUNT
:
3745 int count
= editor
->total_rows
;
3746 ME_Run
*prev_run
, *last_run
;
3748 last_run
= para_end_run( para_prev( editor_end_para( editor
) ) );
3749 prev_run
= run_prev_all_paras( last_run
);
3751 if (editor
->bEmulateVersion10
&& prev_run
&& last_run
->nCharOfs
== 0 &&
3752 prev_run
->len
== 1 && *get_text( prev_run
, 0 ) == '\r')
3754 /* In 1.0 emulation, the last solitary \r at the very end of the text
3755 (if one exists) is NOT a line break.
3756 FIXME: this is an ugly hack. This should have a more regular model. */
3760 count
= max(1, count
);
3761 TRACE("EM_GETLINECOUNT: count==%d\n", count
);
3764 case EM_LINEFROMCHAR
:
3766 if (wParam
== -1) wParam
= ME_GetCursorOfs( editor
->pCursors
+ 1 );
3767 return row_number_from_char_ofs( editor
, wParam
);
3769 case EM_EXLINEFROMCHAR
:
3771 if (lParam
== -1) lParam
= ME_GetCursorOfs( editor
->pCursors
+ 1 );
3772 return row_number_from_char_ofs( editor
, lParam
);
3780 if (wParam
== -1) row
= row_from_cursor( editor
->pCursors
);
3781 else row
= row_from_row_number( editor
, wParam
);
3782 if (!row
) return -1;
3784 row_first_cursor( row
, &cursor
);
3785 ofs
= ME_GetCursorOfs( &cursor
);
3786 TRACE( "EM_LINEINDEX: nCharOfs==%d\n", ofs
);
3792 int start_ofs
, end_ofs
;
3795 if (wParam
> ME_GetTextLength(editor
))
3799 FIXME("EM_LINELENGTH: returning number of unselected characters on lines with selection unsupported.\n");
3802 cursor_from_char_ofs( editor
, wParam
, &cursor
);
3803 row
= row_from_cursor( &cursor
);
3804 row_first_cursor( row
, &cursor
);
3805 start_ofs
= ME_GetCursorOfs( &cursor
);
3806 row_end_cursor( row
, &cursor
, FALSE
);
3807 end_ofs
= ME_GetCursorOfs( &cursor
);
3808 TRACE( "EM_LINELENGTH(%ld)==%d\n", wParam
, end_ofs
- start_ofs
);
3809 return end_ofs
- start_ofs
;
3811 case EM_EXLIMITTEXT
:
3813 if ((int)lParam
< 0)
3816 editor
->nTextLimit
= 65536;
3818 editor
->nTextLimit
= (int) lParam
;
3824 editor
->nTextLimit
= 65536;
3826 editor
->nTextLimit
= (int) wParam
;
3829 case EM_GETLIMITTEXT
:
3831 return editor
->nTextLimit
;
3836 FINDTEXTW
*ft
= (FINDTEXTW
*)lParam
;
3837 return ME_FindText(editor
, wParam
, &ft
->chrg
, ft
->lpstrText
, NULL
);
3840 case EM_FINDTEXTEXW
:
3842 FINDTEXTEXW
*ex
= (FINDTEXTEXW
*)lParam
;
3843 return ME_FindText(editor
, wParam
, &ex
->chrg
, ex
->lpstrText
, &ex
->chrgText
);
3846 if (!wParam
|| !lParam
)
3848 *(int *)wParam
= editor
->nZoomNumerator
;
3849 *(int *)lParam
= editor
->nZoomDenominator
;
3852 return ME_SetZoom(editor
, wParam
, lParam
);
3853 case EM_CHARFROMPOS
:
3856 if (ME_CharFromPos(editor
, ((POINTL
*)lParam
)->x
, ((POINTL
*)lParam
)->y
,
3858 return ME_GetCursorOfs(&cursor
);
3862 case EM_POSFROMCHAR
:
3865 int nCharOfs
, nLength
;
3869 /* detect which API version we're dealing with */
3870 if (wParam
>= 0x40000)
3872 nLength
= ME_GetTextLength(editor
);
3873 nCharOfs
= min(nCharOfs
, nLength
);
3874 nCharOfs
= max(nCharOfs
, 0);
3876 cursor_from_char_ofs( editor
, nCharOfs
, &cursor
);
3877 pt
.y
= cursor
.run
->pt
.y
;
3878 pt
.x
= cursor
.run
->pt
.x
+
3879 ME_PointFromChar( editor
, cursor
.run
, cursor
.nOffset
, TRUE
);
3880 pt
.y
+= cursor
.para
->pt
.y
+ editor
->rcFormat
.top
;
3881 pt
.x
+= editor
->rcFormat
.left
;
3883 pt
.x
-= editor
->horz_si
.nPos
;
3884 pt
.y
-= editor
->vert_si
.nPos
;
3886 if (wParam
>= 0x40000) *(POINTL
*)wParam
= pt
;
3888 return (wParam
>= 0x40000) ? 0 : MAKELONG( pt
.x
, pt
.y
);
3890 case WM_LBUTTONDBLCLK
:
3891 case WM_LBUTTONDOWN
:
3893 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
3894 ITextHost_TxSetFocus(editor
->texthost
);
3895 ME_LButtonDown(editor
, (short)LOWORD(lParam
), (short)HIWORD(lParam
),
3896 ME_CalculateClickCount(editor
, msg
, wParam
, lParam
));
3897 ITextHost_TxSetCapture(editor
->texthost
, TRUE
);
3898 editor
->bMouseCaptured
= TRUE
;
3899 link_notify( editor
, msg
, wParam
, lParam
);
3903 if (editor
->bMouseCaptured
)
3904 ME_MouseMove(editor
, (short)LOWORD(lParam
), (short)HIWORD(lParam
));
3906 link_notify( editor
, msg
, wParam
, lParam
);
3909 if (editor
->bMouseCaptured
) {
3910 ITextHost_TxSetCapture(editor
->texthost
, FALSE
);
3911 editor
->bMouseCaptured
= FALSE
;
3913 if (editor
->nSelectionType
== stDocument
)
3914 editor
->nSelectionType
= stPosition
;
3917 link_notify( editor
, msg
, wParam
, lParam
);
3921 case WM_RBUTTONDOWN
:
3922 case WM_RBUTTONDBLCLK
:
3923 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
3924 link_notify( editor
, msg
, wParam
, lParam
);
3926 case WM_CONTEXTMENU
:
3927 if (!ME_ShowContextMenu(editor
, (short)LOWORD(lParam
), (short)HIWORD(lParam
)))
3931 editor
->bHaveFocus
= TRUE
;
3932 create_caret(editor
);
3933 update_caret(editor
);
3934 ITextHost_TxNotify( editor
->texthost
, EN_SETFOCUS
, NULL
);
3935 if (!editor
->bHideSelection
&& (editor
->props
& TXTBIT_HIDESELECTION
))
3936 ME_InvalidateSelection( editor
);
3939 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
3940 editor
->bHaveFocus
= FALSE
;
3941 editor
->wheel_remain
= 0;
3944 ITextHost_TxNotify( editor
->texthost
, EN_KILLFOCUS
, NULL
);
3945 if (!editor
->bHideSelection
&& (editor
->props
& TXTBIT_HIDESELECTION
))
3946 ME_InvalidateSelection( editor
);
3949 TRACE("editor wnd command = %d\n", LOWORD(wParam
));
3952 if (ME_KeyDown(editor
, LOWORD(wParam
)))
3956 return handle_wm_char( editor
, wParam
, lParam
);
3958 if (wParam
== UNICODE_NOCHAR
) return TRUE
;
3959 if (wParam
<= 0x000fffff)
3961 if (wParam
> 0xffff) /* convert to surrogates */
3964 handle_wm_char( editor
, (wParam
>> 10) + 0xd800, 0 );
3965 handle_wm_char( editor
, (wParam
& 0x03ff) + 0xdc00, 0 );
3968 handle_wm_char( editor
, wParam
, 0 );
3971 case EM_STOPGROUPTYPING
:
3972 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
3976 const int scrollUnit
= 7;
3978 switch(LOWORD(wParam
))
3981 scroll_abs( editor
, 0, 0, TRUE
);
3984 scroll_abs( editor
, editor
->horz_si
.nMax
- (int)editor
->horz_si
.nPage
,
3985 editor
->vert_si
.nMax
- (int)editor
->vert_si
.nPage
, TRUE
);
3988 ME_ScrollLeft(editor
, scrollUnit
);
3991 ME_ScrollRight(editor
, scrollUnit
);
3994 ME_ScrollLeft(editor
, editor
->sizeWindow
.cx
);
3997 ME_ScrollRight(editor
, editor
->sizeWindow
.cx
);
4000 case SB_THUMBPOSITION
:
4002 int pos
= HIWORD(wParam
);
4003 if (editor
->horz_si
.nMax
> 0xffff)
4004 pos
= MulDiv(pos
, editor
->horz_si
.nMax
, 0xffff);
4005 scroll_h_abs( editor
, pos
, FALSE
);
4011 case EM_SCROLL
: /* fall through */
4015 int lineHeight
= get_default_line_height( editor
);
4017 origNPos
= editor
->vert_si
.nPos
;
4019 switch(LOWORD(wParam
))
4022 scroll_abs( editor
, 0, 0, TRUE
);
4025 scroll_abs( editor
, editor
->horz_si
.nMax
- (int)editor
->horz_si
.nPage
,
4026 editor
->vert_si
.nMax
- (int)editor
->vert_si
.nPage
, TRUE
);
4029 ME_ScrollUp(editor
,lineHeight
);
4032 ME_ScrollDown(editor
,lineHeight
);
4035 ME_ScrollUp(editor
,editor
->sizeWindow
.cy
);
4038 ME_ScrollDown(editor
,editor
->sizeWindow
.cy
);
4041 case SB_THUMBPOSITION
:
4043 int pos
= HIWORD(wParam
);
4044 if (editor
->vert_si
.nMax
> 0xffff)
4045 pos
= MulDiv(pos
, editor
->vert_si
.nMax
, 0xffff);
4046 scroll_v_abs( editor
, pos
, FALSE
);
4050 if (msg
== EM_SCROLL
)
4051 return 0x00010000 | (((editor
->vert_si
.nPos
- origNPos
)/lineHeight
) & 0xffff);
4056 int delta
= GET_WHEEL_DELTA_WPARAM( wParam
);
4057 BOOL ctrl_is_down
= GetKeyState( VK_CONTROL
) & 0x8000;
4059 /* if scrolling changes direction, ignore left overs */
4060 if ((delta
< 0 && editor
->wheel_remain
< 0) ||
4061 (delta
> 0 && editor
->wheel_remain
> 0))
4062 editor
->wheel_remain
+= delta
;
4064 editor
->wheel_remain
= delta
;
4066 if (editor
->wheel_remain
)
4070 if (!editor
->nZoomNumerator
|| !editor
->nZoomDenominator
)
4074 numerator
= editor
->nZoomNumerator
* 100 / editor
->nZoomDenominator
;
4076 numerator
+= calc_wheel_change( &editor
->wheel_remain
, 10 );
4077 if (numerator
>= 10 && numerator
<= 500)
4078 ME_SetZoom(editor
, numerator
, 100);
4083 SystemParametersInfoW( SPI_GETWHEELSCROLLLINES
, 0, &max_lines
, 0 );
4085 lines
= calc_wheel_change( &editor
->wheel_remain
, (int)max_lines
);
4087 ME_ScrollDown( editor
, -lines
* get_default_line_height( editor
) );
4092 case EM_REQUESTRESIZE
:
4093 ME_SendRequestResize(editor
, TRUE
);
4095 /* IME messages to make richedit controls IME aware */
4096 case WM_IME_SETCONTEXT
:
4097 case WM_IME_CONTROL
:
4099 case WM_IME_COMPOSITIONFULL
:
4101 case WM_IME_STARTCOMPOSITION
:
4103 editor
->imeStartIndex
=ME_GetCursorOfs(&editor
->pCursors
[0]);
4104 ME_DeleteSelection(editor
);
4105 ME_CommitUndo(editor
);
4106 ME_UpdateRepaint(editor
, FALSE
);
4109 case WM_IME_COMPOSITION
:
4113 ME_Style
*style
= style_get_insert_style( editor
, editor
->pCursors
);
4114 hIMC
= ITextHost_TxImmGetContext(editor
->texthost
);
4115 ME_DeleteSelection(editor
);
4116 ME_SaveTempStyle(editor
, style
);
4117 if (lParam
& (GCS_RESULTSTR
|GCS_COMPSTR
))
4119 LPWSTR lpCompStr
= NULL
;
4121 DWORD dwIndex
= lParam
& GCS_RESULTSTR
;
4123 dwIndex
= GCS_COMPSTR
;
4125 dwBufLen
= ImmGetCompositionStringW(hIMC
, dwIndex
, NULL
, 0);
4126 lpCompStr
= HeapAlloc(GetProcessHeap(),0,dwBufLen
+ sizeof(WCHAR
));
4127 ImmGetCompositionStringW(hIMC
, dwIndex
, lpCompStr
, dwBufLen
);
4128 lpCompStr
[dwBufLen
/sizeof(WCHAR
)] = 0;
4129 ME_InsertTextFromCursor(editor
,0,lpCompStr
,dwBufLen
/sizeof(WCHAR
),style
);
4130 HeapFree(GetProcessHeap(), 0, lpCompStr
);
4132 if (dwIndex
== GCS_COMPSTR
)
4133 set_selection_cursors(editor
,editor
->imeStartIndex
,
4134 editor
->imeStartIndex
+ dwBufLen
/sizeof(WCHAR
));
4136 ME_ReleaseStyle(style
);
4137 ME_CommitUndo(editor
);
4138 ME_UpdateRepaint(editor
, FALSE
);
4141 case WM_IME_ENDCOMPOSITION
:
4143 ME_DeleteSelection(editor
);
4144 editor
->imeStartIndex
=-1;
4147 case EM_GETOLEINTERFACE
:
4148 IRichEditOle_AddRef( editor
->richole
);
4149 *(IRichEditOle
**)lParam
= editor
->richole
;
4152 case EM_SETOLECALLBACK
:
4153 if(editor
->lpOleCallback
)
4154 IRichEditOleCallback_Release(editor
->lpOleCallback
);
4155 editor
->lpOleCallback
= (IRichEditOleCallback
*)lParam
;
4156 if(editor
->lpOleCallback
)
4157 IRichEditOleCallback_AddRef(editor
->lpOleCallback
);
4159 case EM_GETWORDBREAKPROC
:
4160 return (LRESULT
)editor
->pfnWordBreak
;
4161 case EM_SETWORDBREAKPROC
:
4163 EDITWORDBREAKPROCW pfnOld
= editor
->pfnWordBreak
;
4165 editor
->pfnWordBreak
= (EDITWORDBREAKPROCW
)lParam
;
4166 return (LRESULT
)pfnOld
;
4168 case EM_GETTEXTMODE
:
4169 return editor
->mode
;
4170 case EM_SETTEXTMODE
:
4175 if (ME_GetTextLength(editor
) ||
4176 !list_empty( &editor
->undo_stack
) || !list_empty( &editor
->redo_stack
))
4177 return E_UNEXPECTED
;
4179 /* Check for mutually exclusive flags in adjacent bits of wParam */
4180 if ((wParam
& (TM_RICHTEXT
| TM_MULTILEVELUNDO
| TM_MULTICODEPAGE
)) &
4181 (wParam
& (TM_PLAINTEXT
| TM_SINGLELEVELUNDO
| TM_SINGLECODEPAGE
)) << 1)
4182 return E_INVALIDARG
;
4184 if (wParam
& (TM_RICHTEXT
| TM_PLAINTEXT
))
4186 mask
|= TM_RICHTEXT
| TM_PLAINTEXT
;
4187 changes
|= wParam
& (TM_RICHTEXT
| TM_PLAINTEXT
);
4188 if (wParam
& TM_PLAINTEXT
) {
4189 /* Clear selection since it should be possible to select the
4190 * end of text run for rich text */
4191 ME_InvalidateSelection(editor
);
4192 ME_SetCursorToStart(editor
, &editor
->pCursors
[0]);
4193 editor
->pCursors
[1] = editor
->pCursors
[0];
4194 /* plain text can only have the default style. */
4195 ME_ClearTempStyle(editor
);
4196 ME_AddRefStyle(editor
->pBuffer
->pDefaultStyle
);
4197 ME_ReleaseStyle( editor
->pCursors
[0].run
->style
);
4198 editor
->pCursors
[0].run
->style
= editor
->pBuffer
->pDefaultStyle
;
4201 /* FIXME: Currently no support for undo level and code page options */
4202 editor
->mode
= (editor
->mode
& ~mask
) | changes
;
4205 case EM_SETTARGETDEVICE
:
4208 BOOL
new = (lParam
== 0 && (editor
->props
& TXTBIT_MULTILINE
));
4209 if (editor
->nAvailWidth
|| editor
->bWordWrap
!= new)
4211 editor
->bWordWrap
= new;
4212 editor
->nAvailWidth
= 0; /* wrap to client area */
4213 ME_RewrapRepaint(editor
);
4216 int width
= max(0, lParam
);
4217 if ((editor
->props
& TXTBIT_MULTILINE
) &&
4218 (!editor
->bWordWrap
|| editor
->nAvailWidth
!= width
))
4220 editor
->nAvailWidth
= width
;
4221 editor
->bWordWrap
= TRUE
;
4222 ME_RewrapRepaint(editor
);
4224 FIXME("EM_SETTARGETDEVICE doesn't use non-NULL target devices\n");
4229 *phresult
= S_FALSE
;
4235 /* Fill buffer with srcChars unicode characters from the start cursor.
4237 * buffer: destination buffer
4238 * buflen: length of buffer in characters excluding the NULL terminator.
4239 * start: start of editor text to copy into buffer.
4240 * srcChars: Number of characters to use from the editor text.
4241 * bCRLF: if true, replaces all end of lines with \r\n pairs.
4243 * returns the number of characters written excluding the NULL terminator.
4245 * The written text is always NULL terminated.
4247 int ME_GetTextW(ME_TextEditor
*editor
, WCHAR
*buffer
, int buflen
,
4248 const ME_Cursor
*start
, int srcChars
, BOOL bCRLF
,
4251 ME_Run
*run
, *next_run
;
4252 const WCHAR
*pStart
= buffer
;
4256 /* bCRLF flag is only honored in 2.0 and up. 1.0 must always return text verbatim */
4257 if (editor
->bEmulateVersion10
) bCRLF
= FALSE
;
4260 next_run
= run_next_all_paras( run
);
4262 nLen
= run
->len
- start
->nOffset
;
4263 str
= get_text( run
, start
->nOffset
);
4265 while (srcChars
&& buflen
&& next_run
)
4267 if (bCRLF
&& run
->nFlags
& MERF_ENDPARA
&& ~run
->nFlags
& MERF_ENDCELL
)
4269 if (buflen
== 1) break;
4270 /* FIXME: native fails to reduce srcChars here for WM_GETTEXT or
4271 * EM_GETTEXTEX, however, this is done for copying text which
4272 * also uses this function. */
4273 srcChars
-= min(nLen
, srcChars
);
4279 nLen
= min(nLen
, srcChars
);
4283 nLen
= min(nLen
, buflen
);
4286 CopyMemory(buffer
, str
, sizeof(WCHAR
) * nLen
);
4291 next_run
= run_next_all_paras( run
);
4294 str
= get_text( run
, 0 );
4296 /* append '\r' to the last paragraph. */
4297 if (run
== para_end_run( para_prev( editor_end_para( editor
) ) ) && bEOP
)
4303 return buffer
- pStart
;
4306 static int __cdecl
wchar_comp( const void *key
, const void *elem
)
4308 return *(const WCHAR
*)key
- *(const WCHAR
*)elem
;
4311 /* neutral characters end the url if the next non-neutral character is a space character,
4312 otherwise they are included in the url. */
4313 static BOOL
isurlneutral( WCHAR c
)
4315 /* NB this list is sorted */
4316 static const WCHAR neutral_chars
[] = L
"!\"'(),-.:;<>?[]{}";
4318 /* Some shortcuts */
4319 if (isalnum( c
)) return FALSE
;
4320 if (c
> L
'}') return FALSE
;
4322 return !!bsearch( &c
, neutral_chars
, ARRAY_SIZE( neutral_chars
) - 1, sizeof(c
), wchar_comp
);
4326 * This proc takes a selection, and scans it forward in order to select the span
4327 * of a possible URL candidate. A possible URL candidate must start with isalnum
4328 * or one of the following special characters: *|/\+%#@ and must consist entirely
4329 * of the characters allowed to start the URL, plus : (colon) which may occur
4330 * at most once, and not at either end.
4332 static BOOL
ME_FindNextURLCandidate(ME_TextEditor
*editor
,
4333 const ME_Cursor
*start
,
4335 ME_Cursor
*candidate_min
,
4336 ME_Cursor
*candidate_max
)
4338 ME_Cursor cursor
= *start
, neutral_end
, space_end
;
4339 BOOL candidateStarted
= FALSE
, quoted
= FALSE
;
4344 WCHAR
*str
= get_text( cursor
.run
, 0 );
4345 int run_len
= cursor
.run
->len
;
4347 nChars
-= run_len
- cursor
.nOffset
;
4349 /* Find start of candidate */
4350 if (!candidateStarted
)
4352 while (cursor
.nOffset
< run_len
)
4354 c
= str
[cursor
.nOffset
];
4355 if (!iswspace( c
) && !isurlneutral( c
))
4357 *candidate_min
= cursor
;
4358 candidateStarted
= TRUE
;
4359 neutral_end
.para
= NULL
;
4360 space_end
.para
= NULL
;
4364 quoted
= (c
== '<');
4369 /* Find end of candidate */
4370 if (candidateStarted
)
4372 while (cursor
.nOffset
< run_len
)
4374 c
= str
[cursor
.nOffset
];
4377 if (quoted
&& c
!= '\r')
4379 if (!space_end
.para
)
4381 if (neutral_end
.para
)
4382 space_end
= neutral_end
;
4390 else if (isurlneutral( c
))
4392 if (quoted
&& c
== '>')
4394 neutral_end
.para
= NULL
;
4395 space_end
.para
= NULL
;
4398 if (!neutral_end
.para
)
4399 neutral_end
= cursor
;
4402 neutral_end
.para
= NULL
;
4409 if (!cursor_next_run( &cursor
, TRUE
))
4414 if (candidateStarted
)
4417 *candidate_max
= space_end
;
4418 else if (neutral_end
.para
)
4419 *candidate_max
= neutral_end
;
4421 *candidate_max
= cursor
;
4424 *candidate_max
= *candidate_min
= cursor
;
4429 * This proc evaluates the selection and returns TRUE if it can be considered an URL
4431 static BOOL
ME_IsCandidateAnURL(ME_TextEditor
*editor
, const ME_Cursor
*start
, int nChars
)
4433 #define MAX_PREFIX_LEN 9
4434 #define X(str) str, ARRAY_SIZE(str) - 1
4436 const WCHAR text
[MAX_PREFIX_LEN
];
4453 WCHAR bufferW
[MAX_PREFIX_LEN
+ 1];
4456 ME_GetTextW(editor
, bufferW
, MAX_PREFIX_LEN
, start
, nChars
, FALSE
, FALSE
);
4457 for (i
= 0; i
< ARRAY_SIZE(prefixes
); i
++)
4459 if (nChars
< prefixes
[i
].length
) continue;
4460 if (!memcmp(prefixes
[i
].text
, bufferW
, prefixes
[i
].length
* sizeof(WCHAR
)))
4464 #undef MAX_PREFIX_LEN
4468 * This proc walks through the indicated selection and evaluates whether each
4469 * section identified by ME_FindNextURLCandidate and in-between sections have
4470 * their proper CFE_LINK attributes set or unset. If the CFE_LINK attribute is
4471 * not what it is supposed to be, this proc sets or unsets it as appropriate.
4473 * Since this function can cause runs to be split, do not depend on the value
4474 * of the start cursor at the end of the function.
4476 * nChars may be set to INT_MAX to update to the end of the text.
4478 * Returns TRUE if at least one section was modified.
4480 static BOOL
ME_UpdateLinkAttribute(ME_TextEditor
*editor
, ME_Cursor
*start
, int nChars
)
4482 BOOL modified
= FALSE
;
4483 ME_Cursor startCur
= *start
;
4485 if (!editor
->AutoURLDetect_bEnable
) return FALSE
;
4490 ME_Cursor candidateStart
, candidateEnd
;
4492 if (ME_FindNextURLCandidate(editor
, &startCur
, nChars
,
4493 &candidateStart
, &candidateEnd
))
4495 /* Section before candidate is not an URL */
4496 int cMin
= ME_GetCursorOfs(&candidateStart
);
4497 int cMax
= ME_GetCursorOfs(&candidateEnd
);
4499 if (!ME_IsCandidateAnURL(editor
, &candidateStart
, cMax
- cMin
))
4500 candidateStart
= candidateEnd
;
4501 nChars
-= cMax
- ME_GetCursorOfs(&startCur
);
4505 /* No more candidates until end of selection */
4509 if (startCur
.run
!= candidateStart
.run
||
4510 startCur
.nOffset
!= candidateStart
.nOffset
)
4512 /* CFE_LINK effect should be consistently unset */
4513 link
.cbSize
= sizeof(link
);
4514 ME_GetCharFormat(editor
, &startCur
, &candidateStart
, &link
);
4515 if (!(link
.dwMask
& CFM_LINK
) || (link
.dwEffects
& CFE_LINK
))
4517 /* CFE_LINK must be unset from this range */
4518 memset(&link
, 0, sizeof(CHARFORMAT2W
));
4519 link
.cbSize
= sizeof(link
);
4520 link
.dwMask
= CFM_LINK
;
4522 ME_SetCharFormat(editor
, &startCur
, &candidateStart
, &link
);
4523 /* Update candidateEnd since setting character formats may split
4524 * runs, which can cause a cursor to be at an invalid offset within
4526 while (candidateEnd
.nOffset
>= candidateEnd
.run
->len
)
4528 candidateEnd
.nOffset
-= candidateEnd
.run
->len
;
4529 candidateEnd
.run
= run_next_all_paras( candidateEnd
.run
);
4534 if (candidateStart
.run
!= candidateEnd
.run
||
4535 candidateStart
.nOffset
!= candidateEnd
.nOffset
)
4537 /* CFE_LINK effect should be consistently set */
4538 link
.cbSize
= sizeof(link
);
4539 ME_GetCharFormat(editor
, &candidateStart
, &candidateEnd
, &link
);
4540 if (!(link
.dwMask
& CFM_LINK
) || !(link
.dwEffects
& CFE_LINK
))
4542 /* CFE_LINK must be set on this range */
4543 memset(&link
, 0, sizeof(CHARFORMAT2W
));
4544 link
.cbSize
= sizeof(link
);
4545 link
.dwMask
= CFM_LINK
;
4546 link
.dwEffects
= CFE_LINK
;
4547 ME_SetCharFormat(editor
, &candidateStart
, &candidateEnd
, &link
);
4551 startCur
= candidateEnd
;
4552 } while (nChars
> 0);