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
;
249 HANDLE me_heap
= NULL
;
251 static ME_TextBuffer
*ME_MakeText(void) {
252 ME_TextBuffer
*buf
= heap_alloc(sizeof(*buf
));
253 ME_DisplayItem
*p1
= ME_MakeDI(diTextStart
);
254 ME_DisplayItem
*p2
= ME_MakeDI(diTextEnd
);
260 p1
->member
.para
.next_para
= p2
;
261 p2
->member
.para
.prev_para
= p1
;
262 p2
->member
.para
.nCharOfs
= 0;
266 buf
->pCharStyle
= NULL
;
271 ME_Paragraph
*editor_first_para( ME_TextEditor
*editor
)
273 return para_next( &editor
->pBuffer
->pFirst
->member
.para
);
276 /* Note, returns the diTextEnd sentinel paragraph */
277 ME_Paragraph
*editor_end_para( ME_TextEditor
*editor
)
279 return &editor
->pBuffer
->pLast
->member
.para
;
282 static BOOL
editor_beep( ME_TextEditor
*editor
, UINT type
)
284 return editor
->props
& TXTBIT_ALLOWBEEP
&& MessageBeep( type
);
287 static LRESULT
ME_StreamInText(ME_TextEditor
*editor
, DWORD dwFormat
, ME_InStream
*stream
, ME_Style
*style
)
290 LRESULT total_bytes_read
= 0;
291 BOOL is_read
= FALSE
;
292 DWORD cp
= CP_ACP
, copy
= 0;
293 char conv_buf
[4 + STREAMIN_BUFFER_SIZE
]; /* up to 4 additional UTF-8 bytes */
295 static const char bom_utf8
[] = {0xEF, 0xBB, 0xBF};
297 TRACE("%08x %p\n", dwFormat
, stream
);
301 WCHAR wszText
[STREAMIN_BUFFER_SIZE
+1];
305 ME_StreamInFill(stream
);
306 if (stream
->editstream
->dwError
)
310 total_bytes_read
+= stream
->dwSize
;
313 if (!(dwFormat
& SF_UNICODE
))
315 char * buf
= stream
->buffer
;
316 DWORD size
= stream
->dwSize
, end
;
321 if (stream
->dwSize
>= 3 && !memcmp(stream
->buffer
, bom_utf8
, 3))
333 memcpy(conv_buf
+ copy
, buf
, size
);
338 while ((buf
[end
-1] & 0xC0) == 0x80)
341 --total_bytes_read
; /* strange, but seems to match windows */
343 if (buf
[end
-1] & 0x80)
346 if ((buf
[end
-1] & 0xE0) == 0xC0)
348 if ((buf
[end
-1] & 0xF0) == 0xE0)
350 if ((buf
[end
-1] & 0xF8) == 0xF0)
353 if (size
- end
>= need
)
355 /* we have enough bytes for this sequence */
360 /* need more bytes, so don't transcode this sequence */
368 nWideChars
= MultiByteToWideChar(cp
, 0, buf
, end
, wszText
, STREAMIN_BUFFER_SIZE
);
375 memcpy(conv_buf
, buf
+ end
, size
- end
);
382 nWideChars
= stream
->dwSize
>> 1;
383 pText
= (WCHAR
*)stream
->buffer
;
386 ME_InsertTextFromCursor(editor
, 0, pText
, nWideChars
, style
);
387 if (stream
->dwSize
== 0)
391 return total_bytes_read
;
394 static void ME_ApplyBorderProperties(RTF_Info
*info
,
395 ME_BorderRect
*borderRect
,
396 RTFBorder
*borderDef
)
399 ME_Border
*pBorders
[] = {&borderRect
->top
,
403 for (i
= 0; i
< 4; i
++)
405 RTFColor
*colorDef
= info
->colorList
;
406 pBorders
[i
]->width
= borderDef
[i
].width
;
407 colorNum
= borderDef
[i
].color
;
408 while (colorDef
&& colorDef
->rtfCNum
!= colorNum
)
409 colorDef
= colorDef
->rtfNextColor
;
411 pBorders
[i
]->colorRef
= RGB(
412 colorDef
->rtfCRed
>= 0 ? colorDef
->rtfCRed
: 0,
413 colorDef
->rtfCGreen
>= 0 ? colorDef
->rtfCGreen
: 0,
414 colorDef
->rtfCBlue
>= 0 ? colorDef
->rtfCBlue
: 0);
416 pBorders
[i
]->colorRef
= RGB(0, 0, 0);
420 void ME_RTFCharAttrHook(RTF_Info
*info
)
423 fmt
.cbSize
= sizeof(fmt
);
427 switch(info
->rtfMinor
)
430 /* FIXME add more flags once they're implemented */
431 fmt
.dwMask
= CFM_BOLD
| CFM_ITALIC
| CFM_UNDERLINE
| CFM_UNDERLINETYPE
| CFM_STRIKEOUT
|
432 CFM_COLOR
| CFM_BACKCOLOR
| CFM_SIZE
| CFM_WEIGHT
;
433 fmt
.dwEffects
= CFE_AUTOCOLOR
| CFE_AUTOBACKCOLOR
;
434 fmt
.yHeight
= 12*20; /* 12pt */
435 fmt
.wWeight
= FW_NORMAL
;
436 fmt
.bUnderlineType
= CFU_UNDERLINE
;
439 fmt
.dwMask
= CFM_BOLD
| CFM_WEIGHT
;
440 fmt
.dwEffects
= info
->rtfParam
? CFE_BOLD
: 0;
441 fmt
.wWeight
= info
->rtfParam
? FW_BOLD
: FW_NORMAL
;
444 fmt
.dwMask
= CFM_ITALIC
;
445 fmt
.dwEffects
= info
->rtfParam
? fmt
.dwMask
: 0;
448 fmt
.dwMask
= CFM_UNDERLINETYPE
| CFM_UNDERLINE
;
449 fmt
.bUnderlineType
= CFU_UNDERLINE
;
450 fmt
.dwEffects
= info
->rtfParam
? CFE_UNDERLINE
: 0;
452 case rtfDotUnderline
:
453 fmt
.dwMask
= CFM_UNDERLINETYPE
| CFM_UNDERLINE
;
454 fmt
.bUnderlineType
= CFU_UNDERLINEDOTTED
;
455 fmt
.dwEffects
= info
->rtfParam
? CFE_UNDERLINE
: 0;
458 fmt
.dwMask
= CFM_UNDERLINETYPE
| CFM_UNDERLINE
;
459 fmt
.bUnderlineType
= CFU_UNDERLINEDOUBLE
;
460 fmt
.dwEffects
= info
->rtfParam
? CFE_UNDERLINE
: 0;
462 case rtfWordUnderline
:
463 fmt
.dwMask
= CFM_UNDERLINETYPE
| CFM_UNDERLINE
;
464 fmt
.bUnderlineType
= CFU_UNDERLINEWORD
;
465 fmt
.dwEffects
= info
->rtfParam
? CFE_UNDERLINE
: 0;
468 fmt
.dwMask
= CFM_UNDERLINE
;
472 fmt
.dwMask
= CFM_STRIKEOUT
;
473 fmt
.dwEffects
= info
->rtfParam
? fmt
.dwMask
: 0;
477 case rtfSubScrShrink
:
478 case rtfSuperScrShrink
:
480 fmt
.dwMask
= CFM_SUBSCRIPT
|CFM_SUPERSCRIPT
;
481 if (info
->rtfMinor
== rtfSubScrShrink
) fmt
.dwEffects
= CFE_SUBSCRIPT
;
482 if (info
->rtfMinor
== rtfSuperScrShrink
) fmt
.dwEffects
= CFE_SUPERSCRIPT
;
483 if (info
->rtfMinor
== rtfNoSuperSub
) fmt
.dwEffects
= 0;
486 fmt
.dwMask
= CFM_HIDDEN
;
487 fmt
.dwEffects
= info
->rtfParam
? fmt
.dwMask
: 0;
490 fmt
.dwMask
= CFM_BACKCOLOR
;
492 if (info
->rtfParam
== 0)
493 fmt
.dwEffects
= CFE_AUTOBACKCOLOR
;
494 else if (info
->rtfParam
!= rtfNoParam
)
496 RTFColor
*c
= RTFGetColor(info
, info
->rtfParam
);
497 if (c
&& c
->rtfCBlue
>= 0)
498 fmt
.crBackColor
= (c
->rtfCBlue
<<16)|(c
->rtfCGreen
<<8)|(c
->rtfCRed
);
500 fmt
.dwEffects
= CFE_AUTOBACKCOLOR
;
504 fmt
.dwMask
= CFM_COLOR
;
506 if (info
->rtfParam
== 0)
507 fmt
.dwEffects
= CFE_AUTOCOLOR
;
508 else if (info
->rtfParam
!= rtfNoParam
)
510 RTFColor
*c
= RTFGetColor(info
, info
->rtfParam
);
511 if (c
&& c
->rtfCBlue
>= 0)
512 fmt
.crTextColor
= (c
->rtfCBlue
<<16)|(c
->rtfCGreen
<<8)|(c
->rtfCRed
);
514 fmt
.dwEffects
= CFE_AUTOCOLOR
;
519 if (info
->rtfParam
!= rtfNoParam
)
521 RTFFont
*f
= RTFGetFont(info
, info
->rtfParam
);
524 MultiByteToWideChar(CP_ACP
, 0, f
->rtfFName
, -1, fmt
.szFaceName
, ARRAY_SIZE(fmt
.szFaceName
));
525 fmt
.szFaceName
[ARRAY_SIZE(fmt
.szFaceName
)-1] = '\0';
526 fmt
.bCharSet
= f
->rtfFCharSet
;
527 fmt
.dwMask
= CFM_FACE
| CFM_CHARSET
;
528 fmt
.bPitchAndFamily
= f
->rtfFPitch
| (f
->rtfFFamily
<< 4);
533 fmt
.dwMask
= CFM_SIZE
;
534 if (info
->rtfParam
!= rtfNoParam
)
535 fmt
.yHeight
= info
->rtfParam
*10;
540 RTFFlushOutputBuffer(info
);
541 /* FIXME too slow ? how come ? */
542 style2
= ME_ApplyStyle(info
->editor
, info
->style
, &fmt
);
543 ME_ReleaseStyle(info
->style
);
544 info
->style
= style2
;
545 info
->styleChanged
= TRUE
;
549 /* FIXME this function doesn't get any information about context of the RTF tag, which is very bad,
550 the same tags mean different things in different contexts */
551 void ME_RTFParAttrHook(RTF_Info
*info
)
553 switch(info
->rtfMinor
)
555 case rtfParDef
: /* restores default paragraph attributes */
556 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
557 info
->borderType
= RTFBorderParaLeft
;
558 else /* v1.0 - 3.0 */
559 info
->borderType
= RTFBorderParaTop
;
560 info
->fmt
.dwMask
= PFM_ALIGNMENT
| PFM_BORDER
| PFM_LINESPACING
| PFM_TABSTOPS
|
561 PFM_OFFSET
| PFM_RIGHTINDENT
| PFM_SPACEAFTER
| PFM_SPACEBEFORE
|
562 PFM_STARTINDENT
| PFM_RTLPARA
| PFM_NUMBERING
| PFM_NUMBERINGSTART
|
563 PFM_NUMBERINGSTYLE
| PFM_NUMBERINGTAB
;
565 info
->fmt
.wAlignment
= PFA_LEFT
;
566 info
->fmt
.cTabCount
= 0;
567 info
->fmt
.dxOffset
= info
->fmt
.dxStartIndent
= info
->fmt
.dxRightIndent
= 0;
568 info
->fmt
.wBorderWidth
= info
->fmt
.wBorders
= 0;
569 info
->fmt
.wBorderSpace
= 0;
570 info
->fmt
.bLineSpacingRule
= 0;
571 info
->fmt
.dySpaceBefore
= info
->fmt
.dySpaceAfter
= 0;
572 info
->fmt
.dyLineSpacing
= 0;
573 info
->fmt
.wEffects
&= ~PFE_RTLPARA
;
574 info
->fmt
.wNumbering
= 0;
575 info
->fmt
.wNumberingStart
= 0;
576 info
->fmt
.wNumberingStyle
= 0;
577 info
->fmt
.wNumberingTab
= 0;
579 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
581 if (info
->tableDef
&& info
->tableDef
->row_start
&&
582 info
->tableDef
->row_start
->nFlags
& MEPF_ROWEND
)
586 /* We are just after a table row. */
587 RTFFlushOutputBuffer(info
);
588 cursor
= info
->editor
->pCursors
[0];
590 if (para
== para_next( info
->tableDef
->row_start
)
591 && !cursor
.nOffset
&& !cursor
.run
->nCharOfs
)
593 /* Since the table row end, no text has been inserted, and the \intbl
594 * control word has not be used. We can confirm that we are not in a
597 info
->tableDef
->row_start
= NULL
;
598 info
->canInheritInTbl
= FALSE
;
602 else /* v1.0 - v3.0 */
604 info
->fmt
.dwMask
|= PFM_TABLE
;
605 info
->fmt
.wEffects
&= ~PFE_TABLE
;
609 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
611 while (info
->rtfParam
> info
->nestingLevel
)
613 RTFTable
*tableDef
= heap_alloc_zero(sizeof(*tableDef
));
614 tableDef
->parent
= info
->tableDef
;
615 info
->tableDef
= tableDef
;
617 RTFFlushOutputBuffer(info
);
618 if (tableDef
->row_start
&& tableDef
->row_start
->nFlags
& MEPF_ROWEND
)
620 ME_Paragraph
*para
= para_next( tableDef
->row_start
);
621 tableDef
->row_start
= table_insert_row_start_at_para( info
->editor
, para
);
626 cursor
= info
->editor
->pCursors
[0];
627 if (cursor
.nOffset
|| cursor
.run
->nCharOfs
)
628 ME_InsertTextFromCursor(info
->editor
, 0, L
"\r", 1, info
->style
);
629 tableDef
->row_start
= table_insert_row_start( info
->editor
, info
->editor
->pCursors
);
632 info
->nestingLevel
++;
634 info
->canInheritInTbl
= FALSE
;
639 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
641 if (info
->nestingLevel
< 1)
647 info
->tableDef
= heap_alloc_zero(sizeof(*info
->tableDef
));
648 tableDef
= info
->tableDef
;
649 RTFFlushOutputBuffer(info
);
650 if (tableDef
->row_start
&& tableDef
->row_start
->nFlags
& MEPF_ROWEND
)
651 para
= para_next( tableDef
->row_start
);
653 para
= info
->editor
->pCursors
[0].para
;
655 tableDef
->row_start
= table_insert_row_start_at_para( info
->editor
, para
);
657 info
->nestingLevel
= 1;
658 info
->canInheritInTbl
= TRUE
;
661 } else { /* v1.0 - v3.0 */
662 info
->fmt
.dwMask
|= PFM_TABLE
;
663 info
->fmt
.wEffects
|= PFE_TABLE
;
669 if ((info
->fmt
.dwMask
& (PFM_STARTINDENT
| PFM_OFFSET
)) != (PFM_STARTINDENT
| PFM_OFFSET
))
672 fmt
.cbSize
= sizeof(fmt
);
673 editor_get_selection_para_fmt( info
->editor
, &fmt
);
674 info
->fmt
.dwMask
|= PFM_STARTINDENT
| PFM_OFFSET
;
675 info
->fmt
.dxStartIndent
= fmt
.dxStartIndent
;
676 info
->fmt
.dxOffset
= fmt
.dxOffset
;
678 if (info
->rtfMinor
== rtfFirstIndent
)
680 info
->fmt
.dxStartIndent
+= info
->fmt
.dxOffset
+ info
->rtfParam
;
681 info
->fmt
.dxOffset
= -info
->rtfParam
;
684 info
->fmt
.dxStartIndent
= info
->rtfParam
- info
->fmt
.dxOffset
;
687 info
->fmt
.dwMask
|= PFM_RIGHTINDENT
;
688 info
->fmt
.dxRightIndent
= info
->rtfParam
;
692 info
->fmt
.dwMask
|= PFM_ALIGNMENT
;
693 info
->fmt
.wAlignment
= PFA_LEFT
;
696 info
->fmt
.dwMask
|= PFM_ALIGNMENT
;
697 info
->fmt
.wAlignment
= PFA_RIGHT
;
700 info
->fmt
.dwMask
|= PFM_ALIGNMENT
;
701 info
->fmt
.wAlignment
= PFA_CENTER
;
704 if (!(info
->fmt
.dwMask
& PFM_TABSTOPS
))
707 fmt
.cbSize
= sizeof(fmt
);
708 editor_get_selection_para_fmt( info
->editor
, &fmt
);
709 memcpy(info
->fmt
.rgxTabs
, fmt
.rgxTabs
,
710 fmt
.cTabCount
* sizeof(fmt
.rgxTabs
[0]));
711 info
->fmt
.cTabCount
= fmt
.cTabCount
;
712 info
->fmt
.dwMask
|= PFM_TABSTOPS
;
714 if (info
->fmt
.cTabCount
< MAX_TAB_STOPS
&& info
->rtfParam
< 0x1000000)
715 info
->fmt
.rgxTabs
[info
->fmt
.cTabCount
++] = info
->rtfParam
;
718 info
->fmt
.dwMask
|= PFM_KEEP
;
719 info
->fmt
.wEffects
|= PFE_KEEP
;
721 case rtfNoWidowControl
:
722 info
->fmt
.dwMask
|= PFM_NOWIDOWCONTROL
;
723 info
->fmt
.wEffects
|= PFE_NOWIDOWCONTROL
;
726 info
->fmt
.dwMask
|= PFM_KEEPNEXT
;
727 info
->fmt
.wEffects
|= PFE_KEEPNEXT
;
730 info
->fmt
.dwMask
|= PFM_SPACEAFTER
;
731 info
->fmt
.dySpaceAfter
= info
->rtfParam
;
734 info
->fmt
.dwMask
|= PFM_SPACEBEFORE
;
735 info
->fmt
.dySpaceBefore
= info
->rtfParam
;
737 case rtfSpaceBetween
:
738 info
->fmt
.dwMask
|= PFM_LINESPACING
;
739 if ((int)info
->rtfParam
> 0)
741 info
->fmt
.dyLineSpacing
= info
->rtfParam
;
742 info
->fmt
.bLineSpacingRule
= 3;
746 info
->fmt
.dyLineSpacing
= info
->rtfParam
;
747 info
->fmt
.bLineSpacingRule
= 4;
750 case rtfSpaceMultiply
:
751 info
->fmt
.dwMask
|= PFM_LINESPACING
;
752 info
->fmt
.dyLineSpacing
= info
->rtfParam
* 20;
753 info
->fmt
.bLineSpacingRule
= 5;
756 info
->fmt
.dwMask
|= PFM_NUMBERING
;
757 info
->fmt
.wNumbering
= PFN_BULLET
;
760 info
->fmt
.dwMask
|= PFM_NUMBERING
;
761 info
->fmt
.wNumbering
= 2; /* FIXME: MSDN says it's not used ?? */
764 info
->borderType
= RTFBorderParaLeft
;
765 info
->fmt
.wBorders
|= 1;
766 info
->fmt
.dwMask
|= PFM_BORDER
;
769 info
->borderType
= RTFBorderParaRight
;
770 info
->fmt
.wBorders
|= 2;
771 info
->fmt
.dwMask
|= PFM_BORDER
;
774 info
->borderType
= RTFBorderParaTop
;
775 info
->fmt
.wBorders
|= 4;
776 info
->fmt
.dwMask
|= PFM_BORDER
;
778 case rtfBorderBottom
:
779 info
->borderType
= RTFBorderParaBottom
;
780 info
->fmt
.wBorders
|= 8;
781 info
->fmt
.dwMask
|= PFM_BORDER
;
783 case rtfBorderSingle
:
784 info
->fmt
.wBorders
&= ~0x700;
785 info
->fmt
.wBorders
|= 1 << 8;
786 info
->fmt
.dwMask
|= PFM_BORDER
;
789 info
->fmt
.wBorders
&= ~0x700;
790 info
->fmt
.wBorders
|= 2 << 8;
791 info
->fmt
.dwMask
|= PFM_BORDER
;
793 case rtfBorderShadow
:
794 info
->fmt
.wBorders
&= ~0x700;
795 info
->fmt
.wBorders
|= 10 << 8;
796 info
->fmt
.dwMask
|= PFM_BORDER
;
798 case rtfBorderDouble
:
799 info
->fmt
.wBorders
&= ~0x700;
800 info
->fmt
.wBorders
|= 7 << 8;
801 info
->fmt
.dwMask
|= PFM_BORDER
;
804 info
->fmt
.wBorders
&= ~0x700;
805 info
->fmt
.wBorders
|= 11 << 8;
806 info
->fmt
.dwMask
|= PFM_BORDER
;
810 int borderSide
= info
->borderType
& RTFBorderSideMask
;
811 RTFTable
*tableDef
= info
->tableDef
;
812 if ((info
->borderType
& RTFBorderTypeMask
) == RTFBorderTypeCell
)
815 if (!tableDef
|| tableDef
->numCellsDefined
>= MAX_TABLE_CELLS
)
817 border
= &tableDef
->cells
[tableDef
->numCellsDefined
].border
[borderSide
];
818 border
->width
= info
->rtfParam
;
821 info
->fmt
.wBorderWidth
= info
->rtfParam
;
822 info
->fmt
.dwMask
|= PFM_BORDER
;
826 info
->fmt
.wBorderSpace
= info
->rtfParam
;
827 info
->fmt
.dwMask
|= PFM_BORDER
;
831 RTFTable
*tableDef
= info
->tableDef
;
832 int borderSide
= info
->borderType
& RTFBorderSideMask
;
833 int borderType
= info
->borderType
& RTFBorderTypeMask
;
836 case RTFBorderTypePara
:
837 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
839 /* v1.0 - 3.0 treat paragraph and row borders the same. */
840 case RTFBorderTypeRow
:
842 tableDef
->border
[borderSide
].color
= info
->rtfParam
;
845 case RTFBorderTypeCell
:
846 if (tableDef
&& tableDef
->numCellsDefined
< MAX_TABLE_CELLS
) {
847 tableDef
->cells
[tableDef
->numCellsDefined
].border
[borderSide
].color
= info
->rtfParam
;
854 info
->fmt
.dwMask
|= PFM_RTLPARA
;
855 info
->fmt
.wEffects
|= PFE_RTLPARA
;
858 info
->fmt
.dwMask
|= PFM_RTLPARA
;
859 info
->fmt
.wEffects
&= ~PFE_RTLPARA
;
864 void ME_RTFTblAttrHook(RTF_Info
*info
)
866 switch (info
->rtfMinor
)
870 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
871 info
->borderType
= 0; /* Not sure */
872 else /* v1.0 - 3.0 */
873 info
->borderType
= RTFBorderRowTop
;
874 if (!info
->tableDef
) {
875 info
->tableDef
= ME_MakeTableDef(info
->editor
);
877 ME_InitTableDef(info
->editor
, info
->tableDef
);
886 info
->tableDef
= ME_MakeTableDef(info
->editor
);
888 cellNum
= info
->tableDef
->numCellsDefined
;
889 if (cellNum
>= MAX_TABLE_CELLS
)
891 info
->tableDef
->cells
[cellNum
].rightBoundary
= info
->rtfParam
;
892 if (cellNum
< MAX_TAB_STOPS
)
894 /* Tab stops were used to store cell positions before v4.1 but v4.1
895 * still seems to set the tabstops without using them. */
896 PARAFORMAT2
*fmt
= &info
->editor
->pCursors
[0].para
->fmt
;
897 fmt
->rgxTabs
[cellNum
] &= ~0x00FFFFFF;
898 fmt
->rgxTabs
[cellNum
] |= 0x00FFFFFF & info
->rtfParam
;
900 info
->tableDef
->numCellsDefined
++;
904 info
->borderType
= RTFBorderRowTop
;
907 info
->borderType
= RTFBorderRowLeft
;
909 case rtfRowBordBottom
:
910 info
->borderType
= RTFBorderRowBottom
;
912 case rtfRowBordRight
:
913 info
->borderType
= RTFBorderRowRight
;
916 info
->borderType
= RTFBorderCellTop
;
918 case rtfCellBordLeft
:
919 info
->borderType
= RTFBorderCellLeft
;
921 case rtfCellBordBottom
:
922 info
->borderType
= RTFBorderCellBottom
;
924 case rtfCellBordRight
:
925 info
->borderType
= RTFBorderCellRight
;
929 info
->tableDef
->gapH
= info
->rtfParam
;
933 info
->tableDef
->leftEdge
= info
->rtfParam
;
938 void ME_RTFSpecialCharHook(RTF_Info
*info
)
940 RTFTable
*tableDef
= info
->tableDef
;
941 switch (info
->rtfMinor
)
944 if (info
->editor
->bEmulateVersion10
) /* v1.0 - v3.0 */
946 /* else fall through since v4.1 treats rtfNestCell and rtfCell the same */
950 RTFFlushOutputBuffer(info
);
951 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
953 if (tableDef
->row_start
)
955 if (!info
->nestingLevel
&& tableDef
->row_start
->nFlags
& MEPF_ROWEND
)
957 ME_Paragraph
*para
= para_next( tableDef
->row_start
);
958 tableDef
->row_start
= table_insert_row_start_at_para( info
->editor
, para
);
959 info
->nestingLevel
= 1;
961 table_insert_cell( info
->editor
, info
->editor
->pCursors
);
964 else /* v1.0 - v3.0 */
966 ME_Paragraph
*para
= info
->editor
->pCursors
[0].para
;
968 if (para_in_table( para
) && tableDef
->numCellsInserted
< tableDef
->numCellsDefined
)
971 ME_InsertTextFromCursor(info
->editor
, 0, &tab
, 1, info
->style
);
972 tableDef
->numCellsInserted
++;
977 if (info
->editor
->bEmulateVersion10
) /* v1.0 - v3.0 */
979 /* else fall through since v4.1 treats rtfNestRow and rtfRow the same */
989 RTFFlushOutputBuffer(info
);
990 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
992 if (!tableDef
->row_start
) break;
993 if (!info
->nestingLevel
&& tableDef
->row_start
->nFlags
& MEPF_ROWEND
)
995 para
= para_next( tableDef
->row_start
);
996 tableDef
->row_start
= table_insert_row_start_at_para( info
->editor
, para
);
997 info
->nestingLevel
++;
999 para
= tableDef
->row_start
;
1000 cell
= table_row_first_cell( para
);
1001 assert( cell
&& !cell_prev( cell
) );
1002 if (tableDef
->numCellsDefined
< 1)
1004 /* 2000 twips appears to be the cell size that native richedit uses
1005 * when no cell sizes are specified. */
1006 const int default_size
= 2000;
1007 int right_boundary
= default_size
;
1008 cell
->nRightBoundary
= right_boundary
;
1009 while (cell_next( cell
))
1011 cell
= cell_next( cell
);
1012 right_boundary
+= default_size
;
1013 cell
->nRightBoundary
= right_boundary
;
1015 para
= table_insert_cell( info
->editor
, info
->editor
->pCursors
);
1016 cell
= para_cell( para
);
1017 cell
->nRightBoundary
= right_boundary
;
1021 for (i
= 0; i
< tableDef
->numCellsDefined
; i
++)
1023 RTFCell
*cellDef
= &tableDef
->cells
[i
];
1024 cell
->nRightBoundary
= cellDef
->rightBoundary
;
1025 ME_ApplyBorderProperties( info
, &cell
->border
, cellDef
->border
);
1026 cell
= cell_next( cell
);
1029 para
= table_insert_cell( info
->editor
, info
->editor
->pCursors
);
1030 cell
= para_cell( para
);
1033 /* Cell for table row delimiter is empty */
1034 cell
->nRightBoundary
= tableDef
->cells
[i
- 1].rightBoundary
;
1037 run
= para_first_run( cell_first_para( cell
) );
1038 if (info
->editor
->pCursors
[0].run
!= run
|| info
->editor
->pCursors
[0].nOffset
)
1041 /* Delete inserted cells that aren't defined. */
1042 info
->editor
->pCursors
[1].run
= run
;
1043 info
->editor
->pCursors
[1].para
= run
->para
;
1044 info
->editor
->pCursors
[1].nOffset
= 0;
1045 nOfs
= ME_GetCursorOfs(&info
->editor
->pCursors
[1]);
1046 nChars
= ME_GetCursorOfs(&info
->editor
->pCursors
[0]) - nOfs
;
1047 ME_InternalDeleteText(info
->editor
, &info
->editor
->pCursors
[1],
1051 para
= table_insert_row_end( info
->editor
, info
->editor
->pCursors
);
1052 para
->fmt
.dxOffset
= abs(info
->tableDef
->gapH
);
1053 para
->fmt
.dxStartIndent
= info
->tableDef
->leftEdge
;
1054 ME_ApplyBorderProperties( info
, ¶
->border
, tableDef
->border
);
1055 info
->nestingLevel
--;
1056 if (!info
->nestingLevel
)
1058 if (info
->canInheritInTbl
) tableDef
->row_start
= para
;
1061 while (info
->tableDef
)
1063 tableDef
= info
->tableDef
;
1064 info
->tableDef
= tableDef
->parent
;
1065 heap_free(tableDef
);
1071 info
->tableDef
= tableDef
->parent
;
1072 heap_free(tableDef
);
1075 else /* v1.0 - v3.0 */
1077 para
= info
->editor
->pCursors
[0].para
;
1078 para
->fmt
.dxOffset
= info
->tableDef
->gapH
;
1079 para
->fmt
.dxStartIndent
= info
->tableDef
->leftEdge
;
1081 ME_ApplyBorderProperties( info
, ¶
->border
, tableDef
->border
);
1082 while (tableDef
->numCellsInserted
< tableDef
->numCellsDefined
)
1085 ME_InsertTextFromCursor(info
->editor
, 0, &tab
, 1, info
->style
);
1086 tableDef
->numCellsInserted
++;
1088 para
->fmt
.cTabCount
= min(tableDef
->numCellsDefined
, MAX_TAB_STOPS
);
1089 if (!tableDef
->numCellsDefined
) para
->fmt
.wEffects
&= ~PFE_TABLE
;
1090 ME_InsertTextFromCursor(info
->editor
, 0, L
"\r", 1, info
->style
);
1091 tableDef
->numCellsInserted
= 0;
1097 if (info
->editor
->bEmulateVersion10
) /* v1.0 - 3.0 */
1101 RTFFlushOutputBuffer(info
);
1102 para
= info
->editor
->pCursors
[0].para
;
1103 if (para_in_table( para
))
1105 /* rtfPar is treated like a space within a table. */
1106 info
->rtfClass
= rtfText
;
1107 info
->rtfMajor
= ' ';
1109 else if (info
->rtfMinor
== rtfPar
&& tableDef
)
1110 tableDef
->numCellsInserted
= 0;
1116 static HRESULT
insert_static_object(ME_TextEditor
*editor
, HENHMETAFILE hemf
, HBITMAP hbmp
,
1119 LPOLEOBJECT lpObject
= NULL
;
1120 LPSTORAGE lpStorage
= NULL
;
1121 LPOLECLIENTSITE lpClientSite
= NULL
;
1122 LPDATAOBJECT lpDataObject
= NULL
;
1123 LPOLECACHE lpOleCache
= NULL
;
1127 HRESULT hr
= E_FAIL
;
1132 stgm
.tymed
= TYMED_ENHMF
;
1133 stgm
.u
.hEnhMetaFile
= hemf
;
1134 fm
.cfFormat
= CF_ENHMETAFILE
;
1138 stgm
.tymed
= TYMED_GDI
;
1139 stgm
.u
.hBitmap
= hbmp
;
1140 fm
.cfFormat
= CF_BITMAP
;
1144 stgm
.pUnkForRelease
= NULL
;
1147 fm
.dwAspect
= DVASPECT_CONTENT
;
1149 fm
.tymed
= stgm
.tymed
;
1151 if (OleCreateDefaultHandler(&CLSID_NULL
, NULL
, &IID_IOleObject
, (void**)&lpObject
) == S_OK
&&
1152 IRichEditOle_GetClientSite(editor
->richole
, &lpClientSite
) == S_OK
&&
1153 IOleObject_SetClientSite(lpObject
, lpClientSite
) == S_OK
&&
1154 IOleObject_GetUserClassID(lpObject
, &clsid
) == S_OK
&&
1155 IOleObject_QueryInterface(lpObject
, &IID_IOleCache
, (void**)&lpOleCache
) == S_OK
&&
1156 IOleCache_Cache(lpOleCache
, &fm
, 0, &conn
) == S_OK
&&
1157 IOleObject_QueryInterface(lpObject
, &IID_IDataObject
, (void**)&lpDataObject
) == S_OK
&&
1158 IDataObject_SetData(lpDataObject
, &fm
, &stgm
, TRUE
) == S_OK
)
1162 reobject
.cbStruct
= sizeof(reobject
);
1163 reobject
.cp
= REO_CP_SELECTION
;
1164 reobject
.clsid
= clsid
;
1165 reobject
.poleobj
= lpObject
;
1166 reobject
.pstg
= lpStorage
;
1167 reobject
.polesite
= lpClientSite
;
1168 /* convert from twips to .01 mm */
1169 reobject
.sizel
.cx
= MulDiv(sz
->cx
, 254, 144);
1170 reobject
.sizel
.cy
= MulDiv(sz
->cy
, 254, 144);
1171 reobject
.dvaspect
= DVASPECT_CONTENT
;
1172 reobject
.dwFlags
= 0; /* FIXME */
1173 reobject
.dwUser
= 0;
1175 editor_insert_oleobj(editor
, &reobject
);
1179 if (lpObject
) IOleObject_Release(lpObject
);
1180 if (lpClientSite
) IOleClientSite_Release(lpClientSite
);
1181 if (lpStorage
) IStorage_Release(lpStorage
);
1182 if (lpDataObject
) IDataObject_Release(lpDataObject
);
1183 if (lpOleCache
) IOleCache_Release(lpOleCache
);
1188 static void ME_RTFReadShpPictGroup( RTF_Info
*info
)
1196 if (info
->rtfClass
== rtfEOF
) return;
1197 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1199 if (--level
== 0) break;
1201 else if (RTFCheckCM( info
, rtfGroup
, rtfBeginGroup
))
1207 RTFRouteToken( info
);
1208 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1213 RTFRouteToken( info
); /* feed "}" back to router */
1217 static DWORD
read_hex_data( RTF_Info
*info
, BYTE
**out
)
1219 DWORD read
= 0, size
= 1024;
1225 if (info
->rtfClass
!= rtfText
)
1227 ERR("Called with incorrect token\n");
1231 buf
= HeapAlloc( GetProcessHeap(), 0, size
);
1234 val
= info
->rtfMajor
;
1235 for (flip
= TRUE
;; flip
= !flip
)
1237 RTFGetToken( info
);
1238 if (info
->rtfClass
== rtfEOF
)
1240 HeapFree( GetProcessHeap(), 0, buf
);
1243 if (info
->rtfClass
!= rtfText
) break;
1249 buf
= HeapReAlloc( GetProcessHeap(), 0, buf
, size
);
1252 buf
[read
++] = RTFCharToHex(val
) * 16 + RTFCharToHex(info
->rtfMajor
);
1255 val
= info
->rtfMajor
;
1257 if (flip
) FIXME("wrong hex string\n");
1263 static void ME_RTFReadPictGroup(RTF_Info
*info
)
1266 BYTE
*buffer
= NULL
;
1271 enum gfxkind
{gfx_unknown
= 0, gfx_enhmetafile
, gfx_metafile
, gfx_dib
} gfx
= gfx_unknown
;
1279 RTFGetToken( info
);
1281 if (info
->rtfClass
== rtfText
)
1286 size
= read_hex_data( info
, &buffer
);
1290 RTFSkipGroup( info
);
1292 } /* We potentially have a new token so fall through. */
1294 if (info
->rtfClass
== rtfEOF
) return;
1296 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1298 if (--level
== 0) break;
1301 if (RTFCheckCM( info
, rtfGroup
, rtfBeginGroup
))
1306 if (!RTFCheckCM( info
, rtfControl
, rtfPictAttr
))
1308 RTFRouteToken( info
);
1309 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1314 if (RTFCheckMM( info
, rtfPictAttr
, rtfWinMetafile
))
1316 mfp
.mm
= info
->rtfParam
;
1319 else if (RTFCheckMM( info
, rtfPictAttr
, rtfDevIndBitmap
))
1321 if (info
->rtfParam
!= 0) FIXME("dibitmap should be 0 (%d)\n", info
->rtfParam
);
1324 else if (RTFCheckMM( info
, rtfPictAttr
, rtfEmfBlip
))
1325 gfx
= gfx_enhmetafile
;
1326 else if (RTFCheckMM( info
, rtfPictAttr
, rtfPicWid
))
1327 mfp
.xExt
= info
->rtfParam
;
1328 else if (RTFCheckMM( info
, rtfPictAttr
, rtfPicHt
))
1329 mfp
.yExt
= info
->rtfParam
;
1330 else if (RTFCheckMM( info
, rtfPictAttr
, rtfPicGoalWid
))
1331 sz
.cx
= info
->rtfParam
;
1332 else if (RTFCheckMM( info
, rtfPictAttr
, rtfPicGoalHt
))
1333 sz
.cy
= info
->rtfParam
;
1335 FIXME("Non supported attribute: %d %d %d\n", info
->rtfClass
, info
->rtfMajor
, info
->rtfMinor
);
1342 case gfx_enhmetafile
:
1343 if ((hemf
= SetEnhMetaFileBits( size
, buffer
)))
1344 insert_static_object( info
->editor
, hemf
, NULL
, &sz
);
1347 if ((hemf
= SetWinMetaFileBits( size
, buffer
, NULL
, &mfp
)))
1348 insert_static_object( info
->editor
, hemf
, NULL
, &sz
);
1352 BITMAPINFO
*bi
= (BITMAPINFO
*)buffer
;
1354 unsigned nc
= bi
->bmiHeader
.biClrUsed
;
1356 /* not quite right, especially for bitfields type of compression */
1357 if (!nc
&& bi
->bmiHeader
.biBitCount
<= 8)
1358 nc
= 1 << bi
->bmiHeader
.biBitCount
;
1359 if ((hbmp
= CreateDIBitmap( hdc
, &bi
->bmiHeader
,
1360 CBM_INIT
, (char*)(bi
+ 1) + nc
* sizeof(RGBQUAD
),
1361 bi
, DIB_RGB_COLORS
)) )
1362 insert_static_object( info
->editor
, NULL
, hbmp
, &sz
);
1363 ReleaseDC( 0, hdc
);
1370 HeapFree( GetProcessHeap(), 0, buffer
);
1371 RTFRouteToken( info
); /* feed "}" back to router */
1375 /* for now, lookup the \result part and use it, whatever the object */
1376 static void ME_RTFReadObjectGroup(RTF_Info
*info
)
1381 if (info
->rtfClass
== rtfEOF
)
1383 if (RTFCheckCM(info
, rtfGroup
, rtfEndGroup
))
1385 if (RTFCheckCM(info
, rtfGroup
, rtfBeginGroup
))
1388 if (info
->rtfClass
== rtfEOF
)
1390 if (RTFCheckCMM(info
, rtfControl
, rtfDestination
, rtfObjResult
))
1394 while (RTFGetToken (info
) != rtfEOF
)
1396 if (info
->rtfClass
== rtfGroup
)
1398 if (info
->rtfMajor
== rtfBeginGroup
) level
++;
1399 else if (info
->rtfMajor
== rtfEndGroup
&& --level
< 0) break;
1401 RTFRouteToken(info
);
1404 else RTFSkipGroup(info
);
1407 if (!RTFCheckCM (info
, rtfControl
, rtfObjAttr
))
1409 FIXME("Non supported attribute: %d %d %d\n", info
->rtfClass
, info
->rtfMajor
, info
->rtfMinor
);
1413 RTFRouteToken(info
); /* feed "}" back to router */
1416 static void ME_RTFReadParnumGroup( RTF_Info
*info
)
1418 int level
= 1, type
= -1;
1419 WORD indent
= 0, start
= 1;
1420 WCHAR txt_before
= 0, txt_after
= 0;
1424 RTFGetToken( info
);
1426 if (RTFCheckCMM( info
, rtfControl
, rtfDestination
, rtfParNumTextBefore
) ||
1427 RTFCheckCMM( info
, rtfControl
, rtfDestination
, rtfParNumTextAfter
))
1429 int loc
= info
->rtfMinor
;
1431 RTFGetToken( info
);
1432 if (info
->rtfClass
== rtfText
)
1434 if (loc
== rtfParNumTextBefore
)
1435 txt_before
= info
->rtfMajor
;
1437 txt_after
= info
->rtfMajor
;
1440 /* falling through to catch EOFs and group level changes */
1443 if (info
->rtfClass
== rtfEOF
)
1446 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1448 if (--level
== 0) break;
1452 if (RTFCheckCM( info
, rtfGroup
, rtfBeginGroup
))
1458 /* Ignore non para-attr */
1459 if (!RTFCheckCM( info
, rtfControl
, rtfParAttr
))
1462 switch (info
->rtfMinor
)
1464 case rtfParLevel
: /* Para level is ignored */
1471 case rtfParNumDecimal
:
1474 case rtfParNumULetter
:
1475 type
= PFN_UCLETTER
;
1477 case rtfParNumURoman
:
1480 case rtfParNumLLetter
:
1481 type
= PFN_LCLETTER
;
1483 case rtfParNumLRoman
:
1487 case rtfParNumIndent
:
1488 indent
= info
->rtfParam
;
1490 case rtfParNumStartAt
:
1491 start
= info
->rtfParam
;
1498 info
->fmt
.dwMask
|= (PFM_NUMBERING
| PFM_NUMBERINGSTART
| PFM_NUMBERINGSTYLE
| PFM_NUMBERINGTAB
);
1499 info
->fmt
.wNumbering
= type
;
1500 info
->fmt
.wNumberingStart
= start
;
1501 info
->fmt
.wNumberingStyle
= PFNS_PAREN
;
1502 if (type
!= PFN_BULLET
)
1504 if (txt_before
== 0 && txt_after
== 0)
1505 info
->fmt
.wNumberingStyle
= PFNS_PLAIN
;
1506 else if (txt_after
== '.')
1507 info
->fmt
.wNumberingStyle
= PFNS_PERIOD
;
1508 else if (txt_before
== '(' && txt_after
== ')')
1509 info
->fmt
.wNumberingStyle
= PFNS_PARENS
;
1511 info
->fmt
.wNumberingTab
= indent
;
1514 TRACE("type %d indent %d start %d txt before %04x txt after %04x\n",
1515 type
, indent
, start
, txt_before
, txt_after
);
1517 RTFRouteToken( info
); /* feed "}" back to router */
1520 static void ME_RTFReadHook(RTF_Info
*info
)
1522 switch(info
->rtfClass
)
1525 switch(info
->rtfMajor
)
1528 if (info
->stackTop
< maxStack
) {
1529 info
->stack
[info
->stackTop
].style
= info
->style
;
1530 ME_AddRefStyle(info
->style
);
1531 info
->stack
[info
->stackTop
].codePage
= info
->codePage
;
1532 info
->stack
[info
->stackTop
].unicodeLength
= info
->unicodeLength
;
1535 info
->styleChanged
= FALSE
;
1539 RTFFlushOutputBuffer(info
);
1541 if (info
->stackTop
<= 0)
1542 info
->rtfClass
= rtfEOF
;
1543 if (info
->stackTop
< 0)
1546 ME_ReleaseStyle(info
->style
);
1547 info
->style
= info
->stack
[info
->stackTop
].style
;
1548 info
->codePage
= info
->stack
[info
->stackTop
].codePage
;
1549 info
->unicodeLength
= info
->stack
[info
->stackTop
].unicodeLength
;
1558 ME_StreamInFill(ME_InStream
*stream
)
1560 stream
->editstream
->dwError
= stream
->editstream
->pfnCallback(stream
->editstream
->dwCookie
,
1561 (BYTE
*)stream
->buffer
,
1562 sizeof(stream
->buffer
),
1563 (LONG
*)&stream
->dwSize
);
1567 static LRESULT
ME_StreamIn(ME_TextEditor
*editor
, DWORD format
, EDITSTREAM
*stream
, BOOL stripLastCR
)
1571 int from
, to
, nUndoMode
;
1572 int nEventMask
= editor
->nEventMask
;
1573 ME_InStream inStream
;
1574 BOOL invalidRTF
= FALSE
;
1575 ME_Cursor
*selStart
, *selEnd
;
1576 LRESULT num_read
= 0; /* bytes read for SF_TEXT, non-control chars inserted for SF_RTF */
1578 TRACE("stream==%p editor==%p format==0x%X\n", stream
, editor
, format
);
1579 editor
->nEventMask
= 0;
1581 ME_GetSelectionOfs(editor
, &from
, &to
);
1582 if (format
& SFF_SELECTION
&& editor
->mode
& TM_RICHTEXT
)
1584 ME_GetSelection(editor
, &selStart
, &selEnd
);
1585 style
= ME_GetSelectionInsertStyle(editor
);
1587 ME_InternalDeleteText(editor
, selStart
, to
- from
, FALSE
);
1589 /* Don't insert text at the end of the table row */
1590 if (!editor
->bEmulateVersion10
) /* v4.1 */
1592 ME_Paragraph
*para
= editor
->pCursors
->para
;
1593 if (para
->nFlags
& (MEPF_ROWSTART
| MEPF_ROWEND
))
1595 para
= para_next( para
);
1596 editor
->pCursors
[0].para
= para
;
1597 editor
->pCursors
[0].run
= para_first_run( para
);
1598 editor
->pCursors
[0].nOffset
= 0;
1600 editor
->pCursors
[1] = editor
->pCursors
[0];
1602 else /* v1.0 - 3.0 */
1604 if (editor
->pCursors
[0].run
->nFlags
& MERF_ENDPARA
&&
1605 para_in_table( editor
->pCursors
[0].para
))
1611 style
= editor
->pBuffer
->pDefaultStyle
;
1612 ME_AddRefStyle(style
);
1613 set_selection_cursors(editor
, 0, 0);
1614 ME_InternalDeleteText(editor
, &editor
->pCursors
[1],
1615 ME_GetTextLength(editor
), FALSE
);
1617 ME_ClearTempStyle(editor
);
1618 editor_set_default_para_fmt( editor
, &editor
->pCursors
[0].para
->fmt
);
1622 /* Back up undo mode to a local variable */
1623 nUndoMode
= editor
->nUndoMode
;
1625 /* Only create an undo if SFF_SELECTION is set */
1626 if (!(format
& SFF_SELECTION
))
1627 editor
->nUndoMode
= umIgnore
;
1629 inStream
.editstream
= stream
;
1630 inStream
.editstream
->dwError
= 0;
1631 inStream
.dwSize
= 0;
1632 inStream
.dwUsed
= 0;
1634 if (format
& SF_RTF
)
1636 /* Check if it's really RTF, and if it is not, use plain text */
1637 ME_StreamInFill(&inStream
);
1638 if (!inStream
.editstream
->dwError
)
1640 if ((!editor
->bEmulateVersion10
&& strncmp(inStream
.buffer
, "{\\rtf", 5) && strncmp(inStream
.buffer
, "{\\urtf", 6))
1641 || (editor
->bEmulateVersion10
&& *inStream
.buffer
!= '{'))
1644 inStream
.editstream
->dwError
= -16;
1649 if (!invalidRTF
&& !inStream
.editstream
->dwError
)
1652 from
= ME_GetCursorOfs(&editor
->pCursors
[0]);
1653 if (format
& SF_RTF
) {
1655 /* setup the RTF parser */
1656 memset(&parser
, 0, sizeof parser
);
1657 RTFSetEditStream(&parser
, &inStream
);
1658 parser
.rtfFormat
= format
&(SF_TEXT
|SF_RTF
);
1659 parser
.editor
= editor
;
1660 parser
.style
= style
;
1661 WriterInit(&parser
);
1663 RTFSetReadHook(&parser
, ME_RTFReadHook
);
1664 RTFSetDestinationCallback(&parser
, rtfShpPict
, ME_RTFReadShpPictGroup
);
1665 RTFSetDestinationCallback(&parser
, rtfPict
, ME_RTFReadPictGroup
);
1666 RTFSetDestinationCallback(&parser
, rtfObject
, ME_RTFReadObjectGroup
);
1667 RTFSetDestinationCallback(&parser
, rtfParNumbering
, ME_RTFReadParnumGroup
);
1668 if (!parser
.editor
->bEmulateVersion10
) /* v4.1 */
1670 RTFSetDestinationCallback(&parser
, rtfNoNestTables
, RTFSkipGroup
);
1671 RTFSetDestinationCallback(&parser
, rtfNestTableProps
, RTFReadGroup
);
1675 /* do the parsing */
1677 RTFFlushOutputBuffer(&parser
);
1678 if (!editor
->bEmulateVersion10
) /* v4.1 */
1680 if (parser
.tableDef
&& parser
.tableDef
->row_start
&&
1681 (parser
.nestingLevel
> 0 || parser
.canInheritInTbl
))
1683 /* Delete any incomplete table row at the end of the rich text. */
1687 parser
.rtfMinor
= rtfRow
;
1688 /* Complete the table row before deleting it.
1689 * By doing it this way we will have the current paragraph format set
1690 * properly to reflect that is not in the complete table, and undo items
1691 * will be added for this change to the current paragraph format. */
1692 if (parser
.nestingLevel
> 0)
1694 while (parser
.nestingLevel
> 1)
1695 ME_RTFSpecialCharHook(&parser
); /* Decrements nestingLevel */
1696 para
= parser
.tableDef
->row_start
;
1697 ME_RTFSpecialCharHook(&parser
);
1701 para
= parser
.tableDef
->row_start
;
1702 ME_RTFSpecialCharHook(&parser
);
1703 assert( para
->nFlags
& MEPF_ROWEND
);
1704 para
= para_next( para
);
1707 editor
->pCursors
[1].para
= para
;
1708 editor
->pCursors
[1].run
= para_first_run( para
);
1709 editor
->pCursors
[1].nOffset
= 0;
1710 nOfs
= ME_GetCursorOfs(&editor
->pCursors
[1]);
1711 nChars
= ME_GetCursorOfs(&editor
->pCursors
[0]) - nOfs
;
1712 ME_InternalDeleteText(editor
, &editor
->pCursors
[1], nChars
, TRUE
);
1713 if (parser
.tableDef
) parser
.tableDef
->row_start
= NULL
;
1716 RTFDestroy(&parser
);
1718 if (parser
.stackTop
> 0)
1720 while (--parser
.stackTop
>= 0)
1722 ME_ReleaseStyle(parser
.style
);
1723 parser
.style
= parser
.stack
[parser
.stackTop
].style
;
1725 if (!inStream
.editstream
->dwError
)
1726 inStream
.editstream
->dwError
= HRESULT_FROM_WIN32(ERROR_HANDLE_EOF
);
1729 /* Remove last line break, as mandated by tests. This is not affected by
1730 CR/LF counters, since RTF streaming presents only \para tokens, which
1731 are converted according to the standard rules: \r for 2.0, \r\n for 1.0
1733 if (stripLastCR
&& !(format
& SFF_SELECTION
)) {
1735 ME_GetSelection(editor
, &selStart
, &selEnd
);
1736 newto
= ME_GetCursorOfs(selEnd
);
1737 if (newto
> to
+ (editor
->bEmulateVersion10
? 1 : 0)) {
1738 WCHAR lastchar
[3] = {'\0', '\0'};
1739 int linebreakSize
= editor
->bEmulateVersion10
? 2 : 1;
1740 ME_Cursor linebreakCursor
= *selEnd
, lastcharCursor
= *selEnd
;
1743 /* Set the final eop to the char fmt of the last char */
1744 cf
.cbSize
= sizeof(cf
);
1745 cf
.dwMask
= CFM_ALL2
;
1746 ME_MoveCursorChars(editor
, &lastcharCursor
, -1, FALSE
);
1747 ME_GetCharFormat(editor
, &lastcharCursor
, &linebreakCursor
, &cf
);
1748 set_selection_cursors(editor
, newto
, -1);
1749 ME_SetSelectionCharFormat(editor
, &cf
);
1750 set_selection_cursors(editor
, newto
, newto
);
1752 ME_MoveCursorChars(editor
, &linebreakCursor
, -linebreakSize
, FALSE
);
1753 ME_GetTextW(editor
, lastchar
, 2, &linebreakCursor
, linebreakSize
, FALSE
, FALSE
);
1754 if (lastchar
[0] == '\r' && (lastchar
[1] == '\n' || lastchar
[1] == '\0')) {
1755 ME_InternalDeleteText(editor
, &linebreakCursor
, linebreakSize
, FALSE
);
1759 to
= ME_GetCursorOfs(&editor
->pCursors
[0]);
1760 num_read
= to
- from
;
1762 style
= parser
.style
;
1764 else if (format
& SF_TEXT
)
1766 num_read
= ME_StreamInText(editor
, format
, &inStream
, style
);
1767 to
= ME_GetCursorOfs(&editor
->pCursors
[0]);
1770 ERR("EM_STREAMIN without SF_TEXT or SF_RTF\n");
1771 /* put the cursor at the top */
1772 if (!(format
& SFF_SELECTION
))
1773 set_selection_cursors(editor
, 0, 0);
1774 cursor_from_char_ofs( editor
, from
, &start
);
1775 ME_UpdateLinkAttribute(editor
, &start
, to
- from
);
1778 /* Restore saved undo mode */
1779 editor
->nUndoMode
= nUndoMode
;
1781 /* even if we didn't add an undo, we need to commit anything on the stack */
1782 ME_CommitUndo(editor
);
1784 /* If SFF_SELECTION isn't set, delete any undos from before we started too */
1785 if (!(format
& SFF_SELECTION
))
1786 ME_EmptyUndoStack(editor
);
1788 ME_ReleaseStyle(style
);
1789 editor
->nEventMask
= nEventMask
;
1790 ME_UpdateRepaint(editor
, FALSE
);
1791 if (!(format
& SFF_SELECTION
)) {
1792 ME_ClearTempStyle(editor
);
1794 ME_SendSelChange(editor
);
1795 ME_SendRequestResize(editor
, FALSE
);
1801 typedef struct tagME_RTFStringStreamStruct
1806 } ME_RTFStringStreamStruct
;
1808 static DWORD CALLBACK
ME_ReadFromRTFString(DWORD_PTR dwCookie
, LPBYTE lpBuff
, LONG cb
, LONG
*pcb
)
1810 ME_RTFStringStreamStruct
*pStruct
= (ME_RTFStringStreamStruct
*)dwCookie
;
1813 count
= min(cb
, pStruct
->length
- pStruct
->pos
);
1814 memmove(lpBuff
, pStruct
->string
+ pStruct
->pos
, count
);
1815 pStruct
->pos
+= count
;
1821 ME_StreamInRTFString(ME_TextEditor
*editor
, BOOL selection
, char *string
)
1824 ME_RTFStringStreamStruct data
;
1826 data
.string
= string
;
1827 data
.length
= strlen(string
);
1829 es
.dwCookie
= (DWORD_PTR
)&data
;
1830 es
.pfnCallback
= ME_ReadFromRTFString
;
1831 ME_StreamIn(editor
, SF_RTF
| (selection
? SFF_SELECTION
: 0), &es
, TRUE
);
1836 ME_FindText(ME_TextEditor
*editor
, DWORD flags
, const CHARRANGE
*chrg
, const WCHAR
*text
, CHARRANGE
*chrgText
)
1838 const int nLen
= lstrlenW(text
);
1839 const int nTextLen
= ME_GetTextLength(editor
);
1842 WCHAR wLastChar
= ' ';
1844 TRACE("flags==0x%08x, chrg->cpMin==%d, chrg->cpMax==%d text==%s\n",
1845 flags
, chrg
->cpMin
, chrg
->cpMax
, debugstr_w(text
));
1847 if (flags
& ~(FR_DOWN
| FR_MATCHCASE
| FR_WHOLEWORD
))
1848 FIXME("Flags 0x%08x not implemented\n",
1849 flags
& ~(FR_DOWN
| FR_MATCHCASE
| FR_WHOLEWORD
));
1852 if (chrg
->cpMax
== -1)
1855 nMax
= chrg
->cpMax
> nTextLen
? nTextLen
: chrg
->cpMax
;
1857 /* In 1.0 emulation, if cpMax reaches end of text, add the FR_DOWN flag */
1858 if (editor
->bEmulateVersion10
&& nMax
== nTextLen
)
1863 /* In 1.0 emulation, cpMin must always be no greater than cpMax */
1864 if (editor
->bEmulateVersion10
&& nMax
< nMin
)
1868 chrgText
->cpMin
= -1;
1869 chrgText
->cpMax
= -1;
1874 /* when searching up, if cpMin < cpMax, then instead of searching
1875 * on [cpMin,cpMax], we search on [0,cpMin], otherwise, search on
1876 * [cpMax, cpMin]. The exception is when cpMax is -1, in which
1877 * case, it is always bigger than cpMin.
1879 if (!editor
->bEmulateVersion10
&& !(flags
& FR_DOWN
))
1883 nMax
= nMin
> nTextLen
? nTextLen
: nMin
;
1884 if (nMin
< nSwap
|| chrg
->cpMax
== -1)
1890 if (!nLen
|| nMin
< 0 || nMax
< 0 || nMax
< nMin
)
1893 chrgText
->cpMin
= chrgText
->cpMax
= -1;
1897 if (flags
& FR_DOWN
) /* Forward search */
1899 /* If possible, find the character before where the search starts */
1900 if ((flags
& FR_WHOLEWORD
) && nMin
)
1902 cursor_from_char_ofs( editor
, nMin
- 1, &cursor
);
1903 wLastChar
= *get_text( cursor
.run
, cursor
.nOffset
);
1904 ME_MoveCursorChars(editor
, &cursor
, 1, FALSE
);
1906 else cursor_from_char_ofs( editor
, nMin
, &cursor
);
1908 while (cursor
.run
&& ME_GetCursorOfs(&cursor
) + nLen
<= nMax
)
1910 ME_Run
*run
= cursor
.run
;
1911 int nCurStart
= cursor
.nOffset
;
1914 while (run
&& ME_CharCompare( *get_text( run
, nCurStart
+ nMatched
), text
[nMatched
], (flags
& FR_MATCHCASE
)))
1916 if ((flags
& FR_WHOLEWORD
) && iswalnum(wLastChar
))
1920 if (nMatched
== nLen
)
1922 ME_Run
*next_run
= run
;
1923 int nNextStart
= nCurStart
;
1926 /* Check to see if next character is a whitespace */
1927 if (flags
& FR_WHOLEWORD
)
1929 if (nCurStart
+ nMatched
== run
->len
)
1931 next_run
= run_next_all_paras( run
);
1932 nNextStart
= -nMatched
;
1936 wNextChar
= *get_text( next_run
, nNextStart
+ nMatched
);
1940 if (iswalnum(wNextChar
))
1944 cursor
.nOffset
+= cursor
.para
->nCharOfs
+ cursor
.run
->nCharOfs
;
1947 chrgText
->cpMin
= cursor
.nOffset
;
1948 chrgText
->cpMax
= cursor
.nOffset
+ nLen
;
1950 TRACE("found at %d-%d\n", cursor
.nOffset
, cursor
.nOffset
+ nLen
);
1951 return cursor
.nOffset
;
1953 if (nCurStart
+ nMatched
== run
->len
)
1955 run
= run_next_all_paras( run
);
1956 nCurStart
= -nMatched
;
1960 wLastChar
= *get_text( run
, nCurStart
+ nMatched
);
1965 if (cursor
.nOffset
== cursor
.run
->len
)
1967 if (run_next_all_paras( cursor
.run
))
1969 cursor
.run
= run_next_all_paras( cursor
.run
);
1970 cursor
.para
= cursor
.run
->para
;
1978 else /* Backward search */
1980 /* If possible, find the character after where the search ends */
1981 if ((flags
& FR_WHOLEWORD
) && nMax
< nTextLen
- 1)
1983 cursor_from_char_ofs( editor
, nMax
+ 1, &cursor
);
1984 wLastChar
= *get_text( cursor
.run
, cursor
.nOffset
);
1985 ME_MoveCursorChars(editor
, &cursor
, -1, FALSE
);
1987 else cursor_from_char_ofs( editor
, nMax
, &cursor
);
1989 while (cursor
.run
&& ME_GetCursorOfs(&cursor
) - nLen
>= nMin
)
1991 ME_Run
*run
= cursor
.run
;
1992 ME_Paragraph
*para
= cursor
.para
;
1993 int nCurEnd
= cursor
.nOffset
;
1996 if (nCurEnd
== 0 && run_prev_all_paras( run
))
1998 run
= run_prev_all_paras( run
);
2003 while (run
&& ME_CharCompare( *get_text( run
, nCurEnd
- nMatched
- 1 ),
2004 text
[nLen
- nMatched
- 1], (flags
& FR_MATCHCASE
) ))
2006 if ((flags
& FR_WHOLEWORD
) && iswalnum(wLastChar
))
2010 if (nMatched
== nLen
)
2012 ME_Run
*prev_run
= run
;
2013 int nPrevEnd
= nCurEnd
;
2017 /* Check to see if previous character is a whitespace */
2018 if (flags
& FR_WHOLEWORD
)
2020 if (nPrevEnd
- nMatched
== 0)
2022 prev_run
= run_prev_all_paras( run
);
2023 if (prev_run
) nPrevEnd
= prev_run
->len
+ nMatched
;
2026 if (prev_run
) wPrevChar
= *get_text( prev_run
, nPrevEnd
- nMatched
- 1 );
2027 else wPrevChar
= ' ';
2029 if (iswalnum(wPrevChar
))
2033 nStart
= para
->nCharOfs
+ run
->nCharOfs
+ nCurEnd
- nMatched
;
2036 chrgText
->cpMin
= nStart
;
2037 chrgText
->cpMax
= nStart
+ nLen
;
2039 TRACE("found at %d-%d\n", nStart
, nStart
+ nLen
);
2042 if (nCurEnd
- nMatched
== 0)
2044 if (run_prev_all_paras( run
))
2046 run
= run_prev_all_paras( run
);
2049 /* Don't care about pCurItem becoming NULL here; it's already taken
2050 * care of in the exterior loop condition */
2051 nCurEnd
= run
->len
+ nMatched
;
2055 wLastChar
= *get_text( run
, nCurEnd
- nMatched
- 1 );
2060 if (cursor
.nOffset
< 0)
2062 if (run_prev_all_paras( cursor
.run
) )
2064 cursor
.run
= run_prev_all_paras( cursor
.run
);
2065 cursor
.para
= cursor
.run
->para
;
2066 cursor
.nOffset
= cursor
.run
->len
;
2073 TRACE("not found\n");
2075 chrgText
->cpMin
= chrgText
->cpMax
= -1;
2079 static int ME_GetTextEx(ME_TextEditor
*editor
, GETTEXTEX
*ex
, LPARAM pText
)
2084 if (!ex
->cb
|| !pText
) return 0;
2086 if (ex
->flags
& ~(GT_SELECTION
| GT_USECRLF
))
2087 FIXME("GETTEXTEX flags 0x%08x not supported\n", ex
->flags
& ~(GT_SELECTION
| GT_USECRLF
));
2089 if (ex
->flags
& GT_SELECTION
)
2092 int nStartCur
= ME_GetSelectionOfs(editor
, &from
, &to
);
2093 start
= editor
->pCursors
[nStartCur
];
2098 ME_SetCursorToStart(editor
, &start
);
2101 if (ex
->codepage
== CP_UNICODE
)
2103 return ME_GetTextW(editor
, (LPWSTR
)pText
, ex
->cb
/ sizeof(WCHAR
) - 1,
2104 &start
, nChars
, ex
->flags
& GT_USECRLF
, FALSE
);
2108 /* potentially each char may be a CR, why calculate the exact value with O(N) when
2109 we can just take a bigger buffer? :)
2110 The above assumption still holds with CR/LF counters, since CR->CRLF expansion
2111 occurs only in richedit 2.0 mode, in which line breaks have only one CR
2113 int crlfmul
= (ex
->flags
& GT_USECRLF
) ? 2 : 1;
2118 buflen
= min(crlfmul
* nChars
, ex
->cb
- 1);
2119 buffer
= heap_alloc((buflen
+ 1) * sizeof(WCHAR
));
2121 nChars
= ME_GetTextW(editor
, buffer
, buflen
, &start
, nChars
, ex
->flags
& GT_USECRLF
, FALSE
);
2122 rc
= WideCharToMultiByte(ex
->codepage
, 0, buffer
, nChars
+ 1,
2123 (LPSTR
)pText
, ex
->cb
, ex
->lpDefaultChar
, ex
->lpUsedDefChar
);
2124 if (rc
) rc
--; /* do not count 0 terminator */
2131 static int get_text_range( ME_TextEditor
*editor
, WCHAR
*buffer
,
2132 const ME_Cursor
*start
, int len
)
2134 if (!buffer
) return 0;
2135 return ME_GetTextW( editor
, buffer
, INT_MAX
, start
, len
, FALSE
, FALSE
);
2138 int set_selection( ME_TextEditor
*editor
, int to
, int from
)
2142 TRACE("%d - %d\n", to
, from
);
2144 if (!editor
->bHideSelection
) ME_InvalidateSelection( editor
);
2145 end
= set_selection_cursors( editor
, to
, from
);
2146 if (!editor
->bHideSelection
) ME_InvalidateSelection( editor
);
2147 update_caret( editor
);
2148 ME_SendSelChange( editor
);
2153 typedef struct tagME_GlobalDestStruct
2157 } ME_GlobalDestStruct
;
2159 static DWORD CALLBACK
ME_ReadFromHGLOBALUnicode(DWORD_PTR dwCookie
, LPBYTE lpBuff
, LONG cb
, LONG
*pcb
)
2161 ME_GlobalDestStruct
*pData
= (ME_GlobalDestStruct
*)dwCookie
;
2166 pDest
= (WORD
*)lpBuff
;
2167 pSrc
= GlobalLock(pData
->hData
);
2168 for (i
= 0; i
<cb
&& pSrc
[pData
->nLength
+i
]; i
++) {
2169 pDest
[i
] = pSrc
[pData
->nLength
+i
];
2171 pData
->nLength
+= i
;
2173 GlobalUnlock(pData
->hData
);
2177 static DWORD CALLBACK
ME_ReadFromHGLOBALRTF(DWORD_PTR dwCookie
, LPBYTE lpBuff
, LONG cb
, LONG
*pcb
)
2179 ME_GlobalDestStruct
*pData
= (ME_GlobalDestStruct
*)dwCookie
;
2184 pSrc
= GlobalLock(pData
->hData
);
2185 for (i
= 0; i
<cb
&& pSrc
[pData
->nLength
+i
]; i
++) {
2186 pDest
[i
] = pSrc
[pData
->nLength
+i
];
2188 pData
->nLength
+= i
;
2190 GlobalUnlock(pData
->hData
);
2194 static HRESULT
paste_rtf(ME_TextEditor
*editor
, FORMATETC
*fmt
, STGMEDIUM
*med
)
2197 ME_GlobalDestStruct gds
;
2200 gds
.hData
= med
->u
.hGlobal
;
2202 es
.dwCookie
= (DWORD_PTR
)&gds
;
2203 es
.pfnCallback
= ME_ReadFromHGLOBALRTF
;
2204 hr
= ME_StreamIn( editor
, SF_RTF
| SFF_SELECTION
, &es
, FALSE
) == 0 ? E_FAIL
: S_OK
;
2205 ReleaseStgMedium( med
);
2209 static HRESULT
paste_text(ME_TextEditor
*editor
, FORMATETC
*fmt
, STGMEDIUM
*med
)
2212 ME_GlobalDestStruct gds
;
2215 gds
.hData
= med
->u
.hGlobal
;
2217 es
.dwCookie
= (DWORD_PTR
)&gds
;
2218 es
.pfnCallback
= ME_ReadFromHGLOBALUnicode
;
2219 hr
= ME_StreamIn( editor
, SF_TEXT
| SF_UNICODE
| SFF_SELECTION
, &es
, FALSE
) == 0 ? E_FAIL
: S_OK
;
2220 ReleaseStgMedium( med
);
2224 static HRESULT
paste_emf(ME_TextEditor
*editor
, FORMATETC
*fmt
, STGMEDIUM
*med
)
2229 hr
= insert_static_object( editor
, med
->u
.hEnhMetaFile
, NULL
, &sz
);
2232 ME_CommitUndo( editor
);
2233 ME_UpdateRepaint( editor
, FALSE
);
2236 ReleaseStgMedium( med
);
2241 static struct paste_format
2244 HRESULT (*paste
)(ME_TextEditor
*, FORMATETC
*, STGMEDIUM
*);
2248 {{ -1, NULL
, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
}, paste_rtf
, L
"Rich Text Format" },
2249 {{ CF_UNICODETEXT
, NULL
, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
}, paste_text
},
2250 {{ CF_ENHMETAFILE
, NULL
, DVASPECT_CONTENT
, -1, TYMED_ENHMF
}, paste_emf
},
2254 static void init_paste_formats(void)
2256 struct paste_format
*format
;
2261 for (format
= paste_formats
; format
->fmt
.cfFormat
; format
++)
2264 format
->fmt
.cfFormat
= RegisterClipboardFormatW( format
->name
);
2270 static BOOL
paste_special(ME_TextEditor
*editor
, UINT cf
, REPASTESPECIAL
*ps
, BOOL check_only
)
2274 struct paste_format
*format
;
2277 /* Protect read-only edit control from modification */
2278 if (editor
->props
& TXTBIT_READONLY
)
2280 if (!check_only
) editor_beep( editor
, MB_ICONERROR
);
2284 init_paste_formats();
2286 if (ps
&& ps
->dwAspect
!= DVASPECT_CONTENT
)
2287 FIXME("Ignoring aspect %x\n", ps
->dwAspect
);
2289 hr
= OleGetClipboard( &data
);
2290 if (hr
!= S_OK
) return FALSE
;
2292 if (cf
== CF_TEXT
) cf
= CF_UNICODETEXT
;
2295 for (format
= paste_formats
; format
->fmt
.cfFormat
; format
++)
2297 if (cf
&& cf
!= format
->fmt
.cfFormat
) continue;
2298 hr
= IDataObject_QueryGetData( data
, &format
->fmt
);
2303 hr
= IDataObject_GetData( data
, &format
->fmt
, &med
);
2304 if (hr
!= S_OK
) goto done
;
2305 hr
= format
->paste( editor
, &format
->fmt
, &med
);
2312 IDataObject_Release( data
);
2317 static HRESULT
editor_copy( ME_TextEditor
*editor
, ME_Cursor
*start
, int chars
, IDataObject
**data_out
)
2319 IDataObject
*data
= NULL
;
2322 if (editor
->lpOleCallback
)
2325 range
.cpMin
= ME_GetCursorOfs( start
);
2326 range
.cpMax
= range
.cpMin
+ chars
;
2327 hr
= IRichEditOleCallback_GetClipboardData( editor
->lpOleCallback
, &range
, RECO_COPY
, &data
);
2330 if (FAILED( hr
) || !data
)
2331 hr
= ME_GetDataObject( editor
, start
, chars
, &data
);
2333 if (SUCCEEDED( hr
))
2339 hr
= OleSetClipboard( data
);
2340 IDataObject_Release( data
);
2347 HRESULT
editor_copy_or_cut( ME_TextEditor
*editor
, BOOL cut
, ME_Cursor
*start
, int count
,
2348 IDataObject
**data_out
)
2352 if (cut
&& (editor
->props
& TXTBIT_READONLY
))
2354 return E_ACCESSDENIED
;
2357 hr
= editor_copy( editor
, start
, count
, data_out
);
2358 if (SUCCEEDED(hr
) && cut
)
2360 ME_InternalDeleteText( editor
, start
, count
, FALSE
);
2361 ME_CommitUndo( editor
);
2362 ME_UpdateRepaint( editor
, TRUE
);
2367 static BOOL
copy_or_cut( ME_TextEditor
*editor
, BOOL cut
)
2371 int start_cursor
= ME_GetSelectionOfs( editor
, &offs
, &count
);
2372 ME_Cursor
*sel_start
= &editor
->pCursors
[start_cursor
];
2374 if (editor
->password_char
) return FALSE
;
2377 hr
= editor_copy_or_cut( editor
, cut
, sel_start
, count
, NULL
);
2378 if (FAILED( hr
)) editor_beep( editor
, MB_ICONERROR
);
2380 return SUCCEEDED( hr
);
2383 static void ME_UpdateSelectionLinkAttribute(ME_TextEditor
*editor
)
2385 ME_Paragraph
*start_para
, *end_para
;
2386 ME_Cursor
*from
, *to
, start
;
2389 if (!editor
->AutoURLDetect_bEnable
) return;
2391 ME_GetSelection(editor
, &from
, &to
);
2393 /* Find paragraph previous to the one that contains start cursor */
2394 start_para
= from
->para
;
2395 if (para_prev( start_para
)) start_para
= para_prev( start_para
);
2397 /* Find paragraph that contains end cursor */
2398 end_para
= para_next( to
->para
);
2400 start
.para
= start_para
;
2401 start
.run
= para_first_run( start_para
);
2403 num_chars
= end_para
->nCharOfs
- start_para
->nCharOfs
;
2405 ME_UpdateLinkAttribute( editor
, &start
, num_chars
);
2408 static BOOL
handle_enter(ME_TextEditor
*editor
)
2410 BOOL shift_is_down
= GetKeyState(VK_SHIFT
) & 0x8000;
2412 if (editor
->props
& TXTBIT_MULTILINE
)
2414 ME_Cursor cursor
= editor
->pCursors
[0];
2415 ME_Paragraph
*para
= cursor
.para
;
2417 ME_Style
*style
, *eop_style
;
2419 if (editor
->props
& TXTBIT_READONLY
)
2421 editor_beep( editor
, MB_ICONERROR
);
2425 ME_GetSelectionOfs(editor
, &from
, &to
);
2426 if (editor
->nTextLimit
> ME_GetTextLength(editor
) - (to
-from
))
2428 if (!editor
->bEmulateVersion10
) /* v4.1 */
2430 if (para
->nFlags
& MEPF_ROWEND
)
2432 /* Add a new table row after this row. */
2433 para
= table_append_row( editor
, para
);
2434 para
= para_next( para
);
2435 editor
->pCursors
[0].para
= para
;
2436 editor
->pCursors
[0].run
= para_first_run( para
);
2437 editor
->pCursors
[0].nOffset
= 0;
2438 editor
->pCursors
[1] = editor
->pCursors
[0];
2439 ME_CommitUndo(editor
);
2440 ME_UpdateRepaint(editor
, FALSE
);
2443 else if (para
== editor
->pCursors
[1].para
&&
2444 cursor
.nOffset
+ cursor
.run
->nCharOfs
== 0 &&
2445 para_prev( para
) && para_prev( para
)->nFlags
& MEPF_ROWSTART
&&
2446 !para_prev( para
)->nCharOfs
)
2448 /* Insert a newline before the table. */
2449 para
= para_prev( para
);
2450 para
->nFlags
&= ~MEPF_ROWSTART
;
2451 editor
->pCursors
[0].para
= para
;
2452 editor
->pCursors
[0].run
= para_first_run( para
);
2453 editor
->pCursors
[1] = editor
->pCursors
[0];
2454 ME_InsertTextFromCursor( editor
, 0, L
"\r", 1, editor
->pCursors
[0].run
->style
);
2455 para
= editor_first_para( editor
);
2456 editor_set_default_para_fmt( editor
, ¶
->fmt
);
2458 para_mark_rewrap( editor
, para
);
2459 editor
->pCursors
[0].para
= para
;
2460 editor
->pCursors
[0].run
= para_first_run( para
);
2461 editor
->pCursors
[1] = editor
->pCursors
[0];
2462 para_next( para
)->nFlags
|= MEPF_ROWSTART
;
2463 ME_CommitCoalescingUndo(editor
);
2464 ME_UpdateRepaint(editor
, FALSE
);
2468 else /* v1.0 - 3.0 */
2470 ME_Paragraph
*para
= cursor
.para
;
2471 if (para_in_table( para
))
2473 if (cursor
.run
->nFlags
& MERF_ENDPARA
)
2477 ME_ContinueCoalescingTransaction(editor
);
2478 para
= table_append_row( editor
, para
);
2479 editor
->pCursors
[0].para
= para
;
2480 editor
->pCursors
[0].run
= para_first_run( para
);
2481 editor
->pCursors
[0].nOffset
= 0;
2482 editor
->pCursors
[1] = editor
->pCursors
[0];
2483 ME_CommitCoalescingUndo(editor
);
2484 ME_UpdateRepaint(editor
, FALSE
);
2490 ME_ContinueCoalescingTransaction(editor
);
2491 if (cursor
.run
->nCharOfs
+ cursor
.nOffset
== 0 &&
2492 para_prev( para
) && !para_in_table( para_prev( para
) ))
2494 /* Insert newline before table */
2495 cursor
.run
= para_end_run( para_prev( para
) );
2498 editor
->pCursors
[0].run
= cursor
.run
;
2499 editor
->pCursors
[0].para
= para_prev( para
);
2501 editor
->pCursors
[0].nOffset
= 0;
2502 editor
->pCursors
[1] = editor
->pCursors
[0];
2503 ME_InsertTextFromCursor( editor
, 0, L
"\r", 1, editor
->pCursors
[0].run
->style
);
2507 editor
->pCursors
[1] = editor
->pCursors
[0];
2508 para
= table_append_row( editor
, para
);
2509 editor
->pCursors
[0].para
= para
;
2510 editor
->pCursors
[0].run
= para_first_run( para
);
2511 editor
->pCursors
[0].nOffset
= 0;
2512 editor
->pCursors
[1] = editor
->pCursors
[0];
2514 ME_CommitCoalescingUndo(editor
);
2515 ME_UpdateRepaint(editor
, FALSE
);
2521 style
= style_get_insert_style( editor
, editor
->pCursors
);
2523 /* Normally the new eop style is the insert style, however in a list it is copied from the existing
2524 eop style (this prevents the list label style changing when the new eop is inserted).
2525 No extra ref is taken here on eop_style. */
2526 if (para
->fmt
.wNumbering
)
2527 eop_style
= para
->eop_run
->style
;
2530 ME_ContinueCoalescingTransaction(editor
);
2532 ME_InsertEndRowFromCursor(editor
, 0);
2534 if (!editor
->bEmulateVersion10
)
2535 ME_InsertTextFromCursor(editor
, 0, L
"\r", 1, eop_style
);
2537 ME_InsertTextFromCursor(editor
, 0, L
"\r\n", 2, eop_style
);
2538 ME_CommitCoalescingUndo(editor
);
2541 ME_UpdateSelectionLinkAttribute(editor
);
2542 ME_UpdateRepaint(editor
, FALSE
);
2543 ME_SaveTempStyle(editor
, style
); /* set the temp insert style for the new para */
2544 ME_ReleaseStyle(style
);
2552 ME_KeyDown(ME_TextEditor
*editor
, WORD nKey
)
2554 BOOL ctrl_is_down
= GetKeyState(VK_CONTROL
) & 0x8000;
2555 BOOL shift_is_down
= GetKeyState(VK_SHIFT
) & 0x8000;
2557 if (editor
->bMouseCaptured
)
2559 if (nKey
!= VK_SHIFT
&& nKey
!= VK_CONTROL
&& nKey
!= VK_MENU
)
2560 editor
->nSelectionType
= stPosition
;
2568 editor
->nUDArrowX
= -1;
2574 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
2575 ME_ArrowKey(editor
, nKey
, shift_is_down
, ctrl_is_down
);
2579 editor
->nUDArrowX
= -1;
2580 /* FIXME backspace and delete aren't the same, they act different wrt paragraph style of the merged paragraph */
2581 if (editor
->props
& TXTBIT_READONLY
)
2583 if (ME_IsSelection(editor
))
2585 ME_DeleteSelection(editor
);
2586 ME_CommitUndo(editor
);
2588 else if (nKey
== VK_DELETE
)
2590 /* Delete stops group typing.
2591 * (See MSDN remarks on EM_STOPGROUPTYPING message) */
2592 ME_DeleteTextAtCursor(editor
, 1, 1);
2593 ME_CommitUndo(editor
);
2595 else if (ME_ArrowKey(editor
, VK_LEFT
, FALSE
, FALSE
))
2597 BOOL bDeletionSucceeded
;
2598 /* Backspace can be grouped for a single undo */
2599 ME_ContinueCoalescingTransaction(editor
);
2600 bDeletionSucceeded
= ME_DeleteTextAtCursor(editor
, 1, 1);
2601 if (!bDeletionSucceeded
&& !editor
->bEmulateVersion10
) { /* v4.1 */
2602 /* Deletion was prevented so the cursor is moved back to where it was.
2603 * (e.g. this happens when trying to delete cell boundaries)
2605 ME_ArrowKey(editor
, VK_RIGHT
, FALSE
, FALSE
);
2607 ME_CommitCoalescingUndo(editor
);
2611 table_move_from_row_start( editor
);
2612 ME_UpdateSelectionLinkAttribute(editor
);
2613 ME_UpdateRepaint(editor
, FALSE
);
2614 ME_SendRequestResize(editor
, FALSE
);
2617 if (!editor
->bEmulateVersion10
)
2618 return handle_enter(editor
);
2623 set_selection( editor
, 0, -1 );
2629 return paste_special( editor
, 0, NULL
, FALSE
);
2634 return copy_or_cut(editor
, nKey
== 'X');
2652 if (nKey
!= VK_SHIFT
&& nKey
!= VK_CONTROL
&& nKey
&& nKey
!= VK_MENU
)
2653 editor
->nUDArrowX
= -1;
2660 chf
.cbSize
= sizeof(chf
);
2662 ME_GetSelectionCharFormat(editor
, &chf
);
2663 ME_DumpStyleToBuf(&chf
, buf
);
2664 MessageBoxA(NULL
, buf
, "Style dump", MB_OK
);
2668 ME_CheckCharOffsets(editor
);
2675 static LRESULT
handle_wm_char( ME_TextEditor
*editor
, WCHAR wstr
, LPARAM flags
)
2677 if (editor
->bMouseCaptured
)
2680 if (editor
->props
& TXTBIT_READONLY
)
2682 editor_beep( editor
, MB_ICONERROR
);
2683 return 0; /* FIXME really 0 ? */
2686 if (editor
->bEmulateVersion10
&& wstr
== '\r')
2687 handle_enter(editor
);
2689 if ((unsigned)wstr
>= ' ' || wstr
== '\t')
2691 ME_Cursor cursor
= editor
->pCursors
[0];
2692 ME_Paragraph
*para
= cursor
.para
;
2694 BOOL ctrl_is_down
= GetKeyState(VK_CONTROL
) & 0x8000;
2695 ME_GetSelectionOfs(editor
, &from
, &to
);
2697 /* v4.1 allows tabs to be inserted with ctrl key down */
2698 !(ctrl_is_down
&& !editor
->bEmulateVersion10
))
2700 BOOL selected_row
= FALSE
;
2702 if (ME_IsSelection(editor
) &&
2703 cursor
.run
->nCharOfs
+ cursor
.nOffset
== 0 &&
2704 to
== ME_GetCursorOfs(&editor
->pCursors
[0]) && para_prev( para
))
2706 para
= para_prev( para
);
2707 selected_row
= TRUE
;
2709 if (para_in_table( para
))
2711 table_handle_tab( editor
, selected_row
);
2712 ME_CommitUndo(editor
);
2716 else if (!editor
->bEmulateVersion10
) /* v4.1 */
2718 if (para
->nFlags
& MEPF_ROWEND
)
2722 para
= para_next( para
);
2723 if (para
->nFlags
& MEPF_ROWSTART
) para
= para_next( para
);
2724 editor
->pCursors
[0].para
= para
;
2725 editor
->pCursors
[0].run
= para_first_run( para
);
2726 editor
->pCursors
[0].nOffset
= 0;
2727 editor
->pCursors
[1] = editor
->pCursors
[0];
2731 else /* v1.0 - 3.0 */
2733 if (para_in_table( para
) && cursor
.run
->nFlags
& MERF_ENDPARA
&& from
== to
)
2735 /* Text should not be inserted at the end of the table. */
2736 editor_beep( editor
, -1 );
2740 /* FIXME maybe it would make sense to call EM_REPLACESEL instead ? */
2741 /* WM_CHAR is restricted to nTextLimit */
2742 if(editor
->nTextLimit
> ME_GetTextLength(editor
) - (to
-from
))
2744 ME_Style
*style
= style_get_insert_style( editor
, editor
->pCursors
);
2745 ME_ContinueCoalescingTransaction(editor
);
2746 ME_InsertTextFromCursor(editor
, 0, &wstr
, 1, style
);
2747 ME_ReleaseStyle(style
);
2748 ME_CommitCoalescingUndo(editor
);
2749 ITextHost_TxSetCursor(editor
->texthost
, NULL
, FALSE
);
2752 ME_UpdateSelectionLinkAttribute(editor
);
2753 ME_UpdateRepaint(editor
, FALSE
);
2758 /* Process the message and calculate the new click count.
2760 * returns: The click count if it is mouse down event, else returns 0. */
2761 static int ME_CalculateClickCount(ME_TextEditor
*editor
, UINT msg
, WPARAM wParam
,
2764 static int clickNum
= 0;
2765 if (msg
< WM_MOUSEFIRST
|| msg
> WM_MOUSELAST
)
2768 if ((msg
== WM_LBUTTONDBLCLK
) ||
2769 (msg
== WM_RBUTTONDBLCLK
) ||
2770 (msg
== WM_MBUTTONDBLCLK
) ||
2771 (msg
== WM_XBUTTONDBLCLK
))
2773 msg
-= (WM_LBUTTONDBLCLK
- WM_LBUTTONDOWN
);
2776 if ((msg
== WM_LBUTTONDOWN
) ||
2777 (msg
== WM_RBUTTONDOWN
) ||
2778 (msg
== WM_MBUTTONDOWN
) ||
2779 (msg
== WM_XBUTTONDOWN
))
2781 static MSG prevClickMsg
;
2783 /* Compare the editor instead of the hwnd so that the this
2784 * can still be done for windowless richedit controls. */
2785 clickMsg
.hwnd
= (HWND
)editor
;
2786 clickMsg
.message
= msg
;
2787 clickMsg
.wParam
= wParam
;
2788 clickMsg
.lParam
= lParam
;
2789 clickMsg
.time
= GetMessageTime();
2790 clickMsg
.pt
.x
= (short)LOWORD(lParam
);
2791 clickMsg
.pt
.y
= (short)HIWORD(lParam
);
2792 if ((clickNum
!= 0) &&
2793 (clickMsg
.message
== prevClickMsg
.message
) &&
2794 (clickMsg
.hwnd
== prevClickMsg
.hwnd
) &&
2795 (clickMsg
.wParam
== prevClickMsg
.wParam
) &&
2796 (clickMsg
.time
- prevClickMsg
.time
< GetDoubleClickTime()) &&
2797 (abs(clickMsg
.pt
.x
- prevClickMsg
.pt
.x
) < GetSystemMetrics(SM_CXDOUBLECLK
)/2) &&
2798 (abs(clickMsg
.pt
.y
- prevClickMsg
.pt
.y
) < GetSystemMetrics(SM_CYDOUBLECLK
)/2))
2804 prevClickMsg
= clickMsg
;
2811 static BOOL
is_link( ME_Run
*run
)
2813 return (run
->style
->fmt
.dwMask
& CFM_LINK
) && (run
->style
->fmt
.dwEffects
& CFE_LINK
);
2816 void editor_set_cursor( ME_TextEditor
*editor
, int x
, int y
)
2820 static HCURSOR cursor_arrow
, cursor_hand
, cursor_ibeam
, cursor_reverse
;
2825 cursor_arrow
= LoadCursorW( NULL
, MAKEINTRESOURCEW( IDC_ARROW
) );
2826 cursor_hand
= LoadCursorW( NULL
, MAKEINTRESOURCEW( IDC_HAND
) );
2827 cursor_ibeam
= LoadCursorW( NULL
, MAKEINTRESOURCEW( IDC_IBEAM
) );
2828 cursor_reverse
= LoadCursorW( dll_instance
, MAKEINTRESOURCEW( OCR_REVERSE
) );
2831 cursor
= cursor_ibeam
;
2833 if ((editor
->nSelectionType
== stLine
&& editor
->bMouseCaptured
) ||
2834 (!editor
->bEmulateVersion10
&& y
< editor
->rcFormat
.top
&& x
< editor
->rcFormat
.left
))
2835 cursor
= cursor_reverse
;
2836 else if (y
< editor
->rcFormat
.top
|| y
> editor
->rcFormat
.bottom
)
2838 if (editor
->bEmulateVersion10
) cursor
= cursor_arrow
;
2839 else cursor
= cursor_ibeam
;
2841 else if (x
< editor
->rcFormat
.left
) cursor
= cursor_reverse
;
2844 ME_CharFromPos( editor
, x
, y
, &pos
, &is_exact
);
2847 ME_Run
*run
= pos
.run
;
2849 if (is_link( run
)) cursor
= cursor_hand
;
2851 else if (ME_IsSelection( editor
))
2853 int start
, end
, offset
= ME_GetCursorOfs( &pos
);
2855 ME_GetSelectionOfs( editor
, &start
, &end
);
2856 if (start
<= offset
&& end
>= offset
) cursor
= cursor_arrow
;
2861 ITextHost_TxSetCursor( editor
->texthost
, cursor
, cursor
== cursor_ibeam
);
2864 static LONG
ME_GetSelectionType(ME_TextEditor
*editor
)
2866 LONG sel_type
= SEL_EMPTY
;
2869 ME_GetSelectionOfs(editor
, &start
, &end
);
2871 sel_type
= SEL_EMPTY
;
2874 LONG object_count
= 0, character_count
= 0;
2877 for (i
= 0; i
< end
- start
; i
++)
2881 cursor_from_char_ofs( editor
, start
+ i
, &cursor
);
2882 if (cursor
.run
->reobj
) object_count
++;
2883 else character_count
++;
2884 if (character_count
>= 2 && object_count
>= 2)
2885 return (SEL_TEXT
| SEL_MULTICHAR
| SEL_OBJECT
| SEL_MULTIOBJECT
);
2887 if (character_count
)
2889 sel_type
|= SEL_TEXT
;
2890 if (character_count
>= 2)
2891 sel_type
|= SEL_MULTICHAR
;
2895 sel_type
|= SEL_OBJECT
;
2896 if (object_count
>= 2)
2897 sel_type
|= SEL_MULTIOBJECT
;
2903 static BOOL
ME_ShowContextMenu(ME_TextEditor
*editor
, int x
, int y
)
2910 if (!editor
->lpOleCallback
|| !editor
->have_texthost2
) return FALSE
;
2911 if (FAILED( ITextHost2_TxGetWindow( editor
->texthost
, &hwnd
))) return FALSE
;
2912 parent
= GetParent( hwnd
);
2913 if (!parent
) parent
= hwnd
;
2915 ME_GetSelectionOfs( editor
, &selrange
.cpMin
, &selrange
.cpMax
);
2916 seltype
= ME_GetSelectionType( editor
);
2917 if (SUCCEEDED( IRichEditOleCallback_GetContextMenu( editor
->lpOleCallback
, seltype
, NULL
, &selrange
, &menu
) ))
2919 TrackPopupMenu( menu
, TPM_LEFTALIGN
| TPM_RIGHTBUTTON
, x
, y
, 0, parent
, NULL
);
2920 DestroyMenu( menu
);
2925 ME_TextEditor
*ME_MakeEditor(ITextHost
*texthost
, BOOL bEmulateVersion10
)
2927 ME_TextEditor
*ed
= heap_alloc(sizeof(*ed
));
2932 ed
->sizeWindow
.cx
= ed
->sizeWindow
.cy
= 0;
2933 if (ITextHost_QueryInterface( texthost
, &IID_ITextHost2
, (void **)&ed
->texthost
) == S_OK
)
2935 ITextHost_Release( texthost
);
2936 ed
->have_texthost2
= TRUE
;
2940 ed
->texthost
= (ITextHost2
*)texthost
;
2941 ed
->have_texthost2
= FALSE
;
2944 ed
->bEmulateVersion10
= bEmulateVersion10
;
2945 ed
->in_place_active
= FALSE
;
2947 ITextHost_TxGetPropertyBits( ed
->texthost
, TXTBIT_RICHTEXT
| TXTBIT_MULTILINE
| TXTBIT_READONLY
|
2948 TXTBIT_USEPASSWORD
| TXTBIT_HIDESELECTION
| TXTBIT_SAVESELECTION
|
2949 TXTBIT_AUTOWORDSEL
| TXTBIT_VERTICAL
| TXTBIT_WORDWRAP
| TXTBIT_ALLOWBEEP
|
2952 ITextHost_TxGetScrollBars( ed
->texthost
, &ed
->scrollbars
);
2953 ed
->pBuffer
= ME_MakeText();
2954 ed
->nZoomNumerator
= ed
->nZoomDenominator
= 0;
2955 ed
->nAvailWidth
= 0; /* wrap to client area */
2956 list_init( &ed
->style_list
);
2957 ME_MakeFirstParagraph(ed
);
2958 /* The four cursors are for:
2959 * 0 - The position where the caret is shown
2960 * 1 - The anchored end of the selection (for normal selection)
2961 * 2 & 3 - The anchored start and end respectively for word, line,
2962 * or paragraph selection.
2965 ed
->pCursors
= heap_alloc(ed
->nCursors
* sizeof(*ed
->pCursors
));
2966 ME_SetCursorToStart(ed
, &ed
->pCursors
[0]);
2967 ed
->pCursors
[1] = ed
->pCursors
[0];
2968 ed
->pCursors
[2] = ed
->pCursors
[0];
2969 ed
->pCursors
[3] = ed
->pCursors
[1];
2970 ed
->nLastTotalLength
= ed
->nTotalLength
= 0;
2971 ed
->nLastTotalWidth
= ed
->nTotalWidth
= 0;
2974 ed
->nModifyStep
= 0;
2975 ed
->nTextLimit
= TEXT_LIMIT_DEFAULT
;
2976 list_init( &ed
->undo_stack
);
2977 list_init( &ed
->redo_stack
);
2978 ed
->nUndoStackSize
= 0;
2979 ed
->nUndoLimit
= STACK_SIZE_DEFAULT
;
2980 ed
->nUndoMode
= umAddToUndo
;
2981 ed
->nParagraphs
= 1;
2982 ed
->nLastSelStart
= ed
->nLastSelEnd
= 0;
2983 ed
->last_sel_start_para
= ed
->last_sel_end_para
= ed
->pCursors
[0].para
;
2984 ed
->bHideSelection
= FALSE
;
2985 ed
->pfnWordBreak
= NULL
;
2987 ed
->lpOleCallback
= NULL
;
2988 ed
->mode
= TM_MULTILEVELUNDO
| TM_MULTICODEPAGE
;
2989 ed
->mode
|= (ed
->props
& TXTBIT_RICHTEXT
) ? TM_RICHTEXT
: TM_PLAINTEXT
;
2990 ed
->AutoURLDetect_bEnable
= FALSE
;
2991 ed
->bHaveFocus
= FALSE
;
2992 ed
->bMouseCaptured
= FALSE
;
2993 ed
->caret_hidden
= FALSE
;
2994 ed
->caret_height
= 0;
2995 for (i
=0; i
<HFONT_CACHE_SIZE
; i
++)
2997 ed
->pFontCache
[i
].nRefs
= 0;
2998 ed
->pFontCache
[i
].nAge
= 0;
2999 ed
->pFontCache
[i
].hFont
= NULL
;
3002 ME_CheckCharOffsets(ed
);
3003 SetRectEmpty(&ed
->rcFormat
);
3004 hr
= ITextHost_TxGetSelectionBarWidth( ed
->texthost
, &selbarwidth
);
3005 /* FIXME: Convert selbarwidth from HIMETRIC to pixels */
3006 if (hr
== S_OK
&& selbarwidth
) ed
->selofs
= SELECTIONBAR_WIDTH
;
3007 else ed
->selofs
= 0;
3008 ed
->nSelectionType
= stPosition
;
3010 ed
->password_char
= 0;
3011 if (ed
->props
& TXTBIT_USEPASSWORD
)
3012 ITextHost_TxGetPasswordChar( ed
->texthost
, &ed
->password_char
);
3014 ed
->bWordWrap
= (ed
->props
& TXTBIT_WORDWRAP
) && (ed
->props
& TXTBIT_MULTILINE
);
3016 ed
->notified_cr
.cpMin
= ed
->notified_cr
.cpMax
= 0;
3018 /* Default scrollbar information */
3019 ed
->vert_si
.cbSize
= sizeof(SCROLLINFO
);
3020 ed
->vert_si
.nMin
= 0;
3021 ed
->vert_si
.nMax
= 0;
3022 ed
->vert_si
.nPage
= 0;
3023 ed
->vert_si
.nPos
= 0;
3024 ed
->vert_sb_enabled
= 0;
3026 ed
->horz_si
.cbSize
= sizeof(SCROLLINFO
);
3027 ed
->horz_si
.nMin
= 0;
3028 ed
->horz_si
.nMax
= 0;
3029 ed
->horz_si
.nPage
= 0;
3030 ed
->horz_si
.nPos
= 0;
3031 ed
->horz_sb_enabled
= 0;
3033 if (ed
->scrollbars
& ES_DISABLENOSCROLL
)
3035 if (ed
->scrollbars
& WS_VSCROLL
)
3037 ITextHost_TxSetScrollRange( ed
->texthost
, SB_VERT
, 0, 1, TRUE
);
3038 ITextHost_TxEnableScrollBar( ed
->texthost
, SB_VERT
, ESB_DISABLE_BOTH
);
3040 if (ed
->scrollbars
& WS_HSCROLL
)
3042 ITextHost_TxSetScrollRange( ed
->texthost
, SB_HORZ
, 0, 1, TRUE
);
3043 ITextHost_TxEnableScrollBar( ed
->texthost
, SB_HORZ
, ESB_DISABLE_BOTH
);
3047 ed
->wheel_remain
= 0;
3049 ed
->back_style
= TXTBACK_OPAQUE
;
3050 ITextHost_TxGetBackStyle( ed
->texthost
, &ed
->back_style
);
3052 list_init( &ed
->reobj_list
);
3053 OleInitialize(NULL
);
3058 void ME_DestroyEditor(ME_TextEditor
*editor
)
3060 ME_DisplayItem
*p
= editor
->pBuffer
->pFirst
, *pNext
= NULL
;
3061 ME_Style
*s
, *cursor2
;
3064 ME_ClearTempStyle(editor
);
3065 ME_EmptyUndoStack(editor
);
3066 editor
->pBuffer
->pFirst
= NULL
;
3070 if (p
->type
== diParagraph
)
3071 para_destroy( editor
, &p
->member
.para
);
3073 ME_DestroyDisplayItem(p
);
3077 LIST_FOR_EACH_ENTRY_SAFE( s
, cursor2
, &editor
->style_list
, ME_Style
, entry
)
3078 ME_DestroyStyle( s
);
3080 ME_ReleaseStyle(editor
->pBuffer
->pDefaultStyle
);
3081 for (i
=0; i
<HFONT_CACHE_SIZE
; i
++)
3083 if (editor
->pFontCache
[i
].hFont
)
3084 DeleteObject(editor
->pFontCache
[i
].hFont
);
3086 if(editor
->lpOleCallback
)
3087 IRichEditOleCallback_Release(editor
->lpOleCallback
);
3091 heap_free(editor
->pBuffer
);
3092 heap_free(editor
->pCursors
);
3096 static inline int get_default_line_height( ME_TextEditor
*editor
)
3100 if (editor
->pBuffer
&& editor
->pBuffer
->pDefaultStyle
)
3101 height
= editor
->pBuffer
->pDefaultStyle
->tm
.tmHeight
;
3102 if (height
<= 0) height
= 24;
3107 static inline int calc_wheel_change( int *remain
, int amount_per_click
)
3109 int change
= amount_per_click
* (float)*remain
/ WHEEL_DELTA
;
3110 *remain
-= WHEEL_DELTA
* change
/ amount_per_click
;
3114 void link_notify(ME_TextEditor
*editor
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
3118 ME_Cursor cursor
; /* The start of the clicked text. */
3122 x
= (short)LOWORD(lParam
);
3123 y
= (short)HIWORD(lParam
);
3124 ME_CharFromPos(editor
, x
, y
, &cursor
, &isExact
);
3125 if (!isExact
) return;
3127 if (is_link( cursor
.run
))
3128 { /* The clicked run has CFE_LINK set */
3129 info
.nmhdr
.hwndFrom
= NULL
;
3130 info
.nmhdr
.idFrom
= 0;
3131 info
.nmhdr
.code
= EN_LINK
;
3133 info
.wParam
= wParam
;
3134 info
.lParam
= lParam
;
3137 /* find the first contiguous run with CFE_LINK set */
3138 info
.chrg
.cpMin
= ME_GetCursorOfs(&cursor
);
3140 while ((run
= run_prev( run
)) && is_link( run
))
3141 info
.chrg
.cpMin
-= run
->len
;
3143 /* find the last contiguous run with CFE_LINK set */
3144 info
.chrg
.cpMax
= ME_GetCursorOfs(&cursor
) + cursor
.run
->len
;
3146 while ((run
= run_next( run
)) && is_link( run
))
3147 info
.chrg
.cpMax
+= run
->len
;
3149 ITextHost_TxNotify(editor
->texthost
, info
.nmhdr
.code
, &info
);
3153 void ME_ReplaceSel(ME_TextEditor
*editor
, BOOL can_undo
, const WCHAR
*str
, int len
)
3155 int from
, to
, nStartCursor
;
3158 nStartCursor
= ME_GetSelectionOfs(editor
, &from
, &to
);
3159 style
= ME_GetSelectionInsertStyle(editor
);
3160 ME_InternalDeleteText(editor
, &editor
->pCursors
[nStartCursor
], to
-from
, FALSE
);
3161 ME_InsertTextFromCursor(editor
, 0, str
, len
, style
);
3162 ME_ReleaseStyle(style
);
3163 /* drop temporary style if line end */
3165 * FIXME question: does abc\n mean: put abc,
3166 * clear temp style, put \n? (would require a change)
3168 if (len
>0 && str
[len
-1] == '\n')
3169 ME_ClearTempStyle(editor
);
3170 ME_CommitUndo(editor
);
3171 ME_UpdateSelectionLinkAttribute(editor
);
3173 ME_EmptyUndoStack(editor
);
3174 ME_UpdateRepaint(editor
, FALSE
);
3177 static void ME_SetText(ME_TextEditor
*editor
, void *text
, BOOL unicode
)
3179 LONG codepage
= unicode
? CP_UNICODE
: CP_ACP
;
3182 LPWSTR wszText
= ME_ToUnicode(codepage
, text
, &textLen
);
3183 ME_InsertTextFromCursor(editor
, 0, wszText
, textLen
, editor
->pBuffer
->pDefaultStyle
);
3184 ME_EndToUnicode(codepage
, wszText
);
3187 static LRESULT
handle_EM_SETCHARFORMAT( ME_TextEditor
*editor
, WPARAM flags
, const CHARFORMAT2W
*fmt_in
)
3190 BOOL changed
= TRUE
;
3191 ME_Cursor start
, end
;
3193 if (!cfany_to_cf2w( &fmt
, fmt_in
)) return 0;
3195 if (flags
& SCF_ALL
)
3197 if (editor
->mode
& TM_PLAINTEXT
)
3199 ME_SetDefaultCharFormat( editor
, &fmt
);
3203 ME_SetCursorToStart( editor
, &start
);
3204 ME_SetCharFormat( editor
, &start
, NULL
, &fmt
);
3205 editor
->nModifyStep
= 1;
3208 else if (flags
& SCF_SELECTION
)
3210 if (editor
->mode
& TM_PLAINTEXT
) return 0;
3211 if (flags
& SCF_WORD
)
3213 end
= editor
->pCursors
[0];
3214 ME_MoveCursorWords( editor
, &end
, +1 );
3216 ME_MoveCursorWords( editor
, &start
, -1 );
3217 ME_SetCharFormat( editor
, &start
, &end
, &fmt
);
3219 changed
= ME_IsSelection( editor
);
3220 ME_SetSelectionCharFormat( editor
, &fmt
);
3221 if (changed
) editor
->nModifyStep
= 1;
3223 else /* SCF_DEFAULT */
3225 ME_SetDefaultCharFormat( editor
, &fmt
);
3228 ME_CommitUndo( editor
);
3231 ME_WrapMarkedParagraphs( editor
);
3232 ME_UpdateScrollBar( editor
);
3237 #define UNSUPPORTED_MSG(e) \
3239 FIXME(#e ": stub\n"); \
3240 *phresult = S_FALSE; \
3243 /* Handle messages for windowless and windowed richedit controls.
3245 * The LRESULT that is returned is a return value for window procs,
3246 * and the phresult parameter is the COM return code needed by the
3247 * text services interface. */
3248 LRESULT
editor_handle_message( ME_TextEditor
*editor
, UINT msg
, WPARAM wParam
,
3249 LPARAM lParam
, HRESULT
* phresult
)
3255 UNSUPPORTED_MSG(EM_DISPLAYBAND
)
3256 UNSUPPORTED_MSG(EM_FINDWORDBREAK
)
3257 UNSUPPORTED_MSG(EM_FMTLINES
)
3258 UNSUPPORTED_MSG(EM_FORMATRANGE
)
3259 UNSUPPORTED_MSG(EM_GETBIDIOPTIONS
)
3260 UNSUPPORTED_MSG(EM_GETEDITSTYLE
)
3261 UNSUPPORTED_MSG(EM_GETIMECOMPMODE
)
3262 UNSUPPORTED_MSG(EM_GETIMESTATUS
)
3263 UNSUPPORTED_MSG(EM_SETIMESTATUS
)
3264 UNSUPPORTED_MSG(EM_GETLANGOPTIONS
)
3265 UNSUPPORTED_MSG(EM_GETREDONAME
)
3266 UNSUPPORTED_MSG(EM_GETTYPOGRAPHYOPTIONS
)
3267 UNSUPPORTED_MSG(EM_GETUNDONAME
)
3268 UNSUPPORTED_MSG(EM_GETWORDBREAKPROCEX
)
3269 UNSUPPORTED_MSG(EM_SETBIDIOPTIONS
)
3270 UNSUPPORTED_MSG(EM_SETEDITSTYLE
)
3271 UNSUPPORTED_MSG(EM_SETLANGOPTIONS
)
3272 UNSUPPORTED_MSG(EM_SETMARGINS
)
3273 UNSUPPORTED_MSG(EM_SETPALETTE
)
3274 UNSUPPORTED_MSG(EM_SETTABSTOPS
)
3275 UNSUPPORTED_MSG(EM_SETTYPOGRAPHYOPTIONS
)
3276 UNSUPPORTED_MSG(EM_SETWORDBREAKPROCEX
)
3278 /* Messages specific to Richedit controls */
3281 return ME_StreamIn(editor
, wParam
, (EDITSTREAM
*)lParam
, TRUE
);
3283 return ME_StreamOut(editor
, wParam
, (EDITSTREAM
*)lParam
);
3284 case EM_EMPTYUNDOBUFFER
:
3285 ME_EmptyUndoStack(editor
);
3289 /* Note: wParam/lParam can be NULL */
3291 PUINT pfrom
= wParam
? (PUINT
)wParam
: &from
;
3292 PUINT pto
= lParam
? (PUINT
)lParam
: &to
;
3293 ME_GetSelectionOfs(editor
, (int *)pfrom
, (int *)pto
);
3294 if ((*pfrom
|*pto
) & 0xFFFF0000)
3296 return MAKELONG(*pfrom
,*pto
);
3300 CHARRANGE
*pRange
= (CHARRANGE
*)lParam
;
3301 ME_GetSelectionOfs(editor
, &pRange
->cpMin
, &pRange
->cpMax
);
3302 TRACE("EM_EXGETSEL = (%d,%d)\n", pRange
->cpMin
, pRange
->cpMax
);
3305 case EM_SETUNDOLIMIT
:
3307 if ((int)wParam
< 0)
3308 editor
->nUndoLimit
= STACK_SIZE_DEFAULT
;
3310 editor
->nUndoLimit
= min(wParam
, STACK_SIZE_MAX
);
3311 /* Setting a max stack size keeps wine from getting killed
3312 for hogging memory. Windows allocates all this memory at once, so
3313 no program would realistically set a value above our maximum. */
3314 return editor
->nUndoLimit
;
3317 return !list_empty( &editor
->undo_stack
);
3319 return !list_empty( &editor
->redo_stack
);
3320 case WM_UNDO
: /* FIXME: actually not the same */
3322 return ME_Undo(editor
);
3324 return ME_Redo(editor
);
3325 case EM_SETFONTSIZE
:
3328 LONG tmp_size
, size
;
3329 BOOL is_increase
= ((LONG
)wParam
> 0);
3331 if (editor
->mode
& TM_PLAINTEXT
)
3334 cf
.cbSize
= sizeof(cf
);
3335 cf
.dwMask
= CFM_SIZE
;
3336 ME_GetSelectionCharFormat(editor
, &cf
);
3337 tmp_size
= (cf
.yHeight
/ 20) + wParam
;
3341 else if (tmp_size
> 12 && tmp_size
< 28 && tmp_size
% 2)
3342 size
= tmp_size
+ (is_increase
? 1 : -1);
3343 else if (tmp_size
> 28 && tmp_size
< 36)
3344 size
= is_increase
? 36 : 28;
3345 else if (tmp_size
> 36 && tmp_size
< 48)
3346 size
= is_increase
? 48 : 36;
3347 else if (tmp_size
> 48 && tmp_size
< 72)
3348 size
= is_increase
? 72 : 48;
3349 else if (tmp_size
> 72 && tmp_size
< 80)
3350 size
= is_increase
? 80 : 72;
3351 else if (tmp_size
> 80 && tmp_size
< 1638)
3352 size
= 10 * (is_increase
? (tmp_size
/ 10 + 1) : (tmp_size
/ 10));
3353 else if (tmp_size
>= 1638)
3358 cf
.yHeight
= size
* 20; /* convert twips to points */
3359 ME_SetSelectionCharFormat(editor
, &cf
);
3360 ME_CommitUndo(editor
);
3361 ME_WrapMarkedParagraphs(editor
);
3362 ME_UpdateScrollBar(editor
);
3368 return set_selection( editor
, wParam
, lParam
);
3370 case EM_SETSCROLLPOS
:
3372 POINT
*point
= (POINT
*)lParam
;
3373 scroll_abs( editor
, point
->x
, point
->y
, TRUE
);
3376 case EM_AUTOURLDETECT
:
3378 if (wParam
==1 || wParam
==0)
3380 editor
->AutoURLDetect_bEnable
= (BOOL
)wParam
;
3383 return E_INVALIDARG
;
3385 case EM_GETAUTOURLDETECT
:
3387 return editor
->AutoURLDetect_bEnable
;
3391 CHARRANGE range
= *(CHARRANGE
*)lParam
;
3393 return set_selection( editor
, range
.cpMin
, range
.cpMax
);
3398 SETTEXTEX
*pStruct
= (SETTEXTEX
*)wParam
;
3401 BOOL bRtf
, bUnicode
, bSelection
, bUTF8
;
3402 int oldModify
= editor
->nModifyStep
;
3403 static const char utf8_bom
[] = {0xef, 0xbb, 0xbf};
3405 if (!pStruct
) return 0;
3407 /* If we detect ascii rtf at the start of the string,
3408 * we know it isn't unicode. */
3409 bRtf
= (lParam
&& (!strncmp((char *)lParam
, "{\\rtf", 5) ||
3410 !strncmp((char *)lParam
, "{\\urtf", 6)));
3411 bUnicode
= !bRtf
&& pStruct
->codepage
== CP_UNICODE
;
3412 bUTF8
= (lParam
&& (!strncmp((char *)lParam
, utf8_bom
, 3)));
3414 TRACE("EM_SETTEXTEX - %s, flags %d, cp %d\n",
3415 bUnicode
? debugstr_w((LPCWSTR
)lParam
) : debugstr_a((LPCSTR
)lParam
),
3416 pStruct
->flags
, pStruct
->codepage
);
3418 bSelection
= (pStruct
->flags
& ST_SELECTION
) != 0;
3420 int nStartCursor
= ME_GetSelectionOfs(editor
, &from
, &to
);
3421 style
= ME_GetSelectionInsertStyle(editor
);
3422 ME_InternalDeleteText(editor
, &editor
->pCursors
[nStartCursor
], to
- from
, FALSE
);
3425 ME_SetCursorToStart(editor
, &start
);
3426 ME_InternalDeleteText(editor
, &start
, ME_GetTextLength(editor
), FALSE
);
3427 style
= editor
->pBuffer
->pDefaultStyle
;
3431 ME_StreamInRTFString(editor
, bSelection
, (char *)lParam
);
3433 /* FIXME: The length returned doesn't include the rtf control
3434 * characters, only the actual text. */
3435 len
= lParam
? strlen((char *)lParam
) : 0;
3438 if (bUTF8
&& !bUnicode
) {
3439 wszText
= ME_ToUnicode(CP_UTF8
, (void *)(lParam
+3), &len
);
3440 ME_InsertTextFromCursor(editor
, 0, wszText
, len
, style
);
3441 ME_EndToUnicode(CP_UTF8
, wszText
);
3443 wszText
= ME_ToUnicode(pStruct
->codepage
, (void *)lParam
, &len
);
3444 ME_InsertTextFromCursor(editor
, 0, wszText
, len
, style
);
3445 ME_EndToUnicode(pStruct
->codepage
, wszText
);
3450 ME_ReleaseStyle(style
);
3451 ME_UpdateSelectionLinkAttribute(editor
);
3455 ME_SetCursorToStart(editor
, &cursor
);
3456 ME_UpdateLinkAttribute(editor
, &cursor
, INT_MAX
);
3458 ME_CommitUndo(editor
);
3459 if (!(pStruct
->flags
& ST_KEEPUNDO
))
3461 editor
->nModifyStep
= oldModify
;
3462 ME_EmptyUndoStack(editor
);
3464 ME_UpdateRepaint(editor
, FALSE
);
3467 case EM_SELECTIONTYPE
:
3468 return ME_GetSelectionType(editor
);
3470 return editor
->nModifyStep
== 0 ? 0 : -1;
3474 editor
->nModifyStep
= 1;
3476 editor
->nModifyStep
= 0;
3480 case EM_SETEVENTMASK
:
3482 DWORD nOldMask
= editor
->nEventMask
;
3484 editor
->nEventMask
= lParam
;
3487 case EM_GETEVENTMASK
:
3488 return editor
->nEventMask
;
3489 case EM_SETCHARFORMAT
:
3490 return handle_EM_SETCHARFORMAT( editor
, wParam
, (CHARFORMAT2W
*)lParam
);
3491 case EM_GETCHARFORMAT
:
3493 CHARFORMAT2W tmp
, *dst
= (CHARFORMAT2W
*)lParam
;
3494 if (dst
->cbSize
!= sizeof(CHARFORMATA
) &&
3495 dst
->cbSize
!= sizeof(CHARFORMATW
) &&
3496 dst
->cbSize
!= sizeof(CHARFORMAT2A
) &&
3497 dst
->cbSize
!= sizeof(CHARFORMAT2W
))
3499 tmp
.cbSize
= sizeof(tmp
);
3501 ME_GetDefaultCharFormat(editor
, &tmp
);
3503 ME_GetSelectionCharFormat(editor
, &tmp
);
3504 cf2w_to_cfany(dst
, &tmp
);
3507 case EM_SETPARAFORMAT
:
3509 BOOL result
= editor_set_selection_para_fmt( editor
, (PARAFORMAT2
*)lParam
);
3510 ME_WrapMarkedParagraphs(editor
);
3511 ME_UpdateScrollBar(editor
);
3512 ME_CommitUndo(editor
);
3515 case EM_GETPARAFORMAT
:
3516 editor_get_selection_para_fmt( editor
, (PARAFORMAT2
*)lParam
);
3517 return ((PARAFORMAT2
*)lParam
)->dwMask
;
3518 case EM_GETFIRSTVISIBLELINE
:
3520 ME_Paragraph
*para
= editor_first_para( editor
);
3522 int y
= editor
->vert_si
.nPos
;
3525 while (para_next( para
))
3527 if (y
< para
->pt
.y
+ para
->nHeight
) break;
3528 count
+= para
->nRows
;
3529 para
= para_next( para
);
3532 row
= para_first_row( para
);
3535 if (y
< para
->pt
.y
+ row
->pt
.y
+ row
->nHeight
) break;
3537 row
= row_next( row
);
3541 case EM_HIDESELECTION
:
3543 editor
->bHideSelection
= (wParam
!= 0);
3544 ME_InvalidateSelection(editor
);
3549 if (!(editor
->props
& TXTBIT_MULTILINE
))
3551 ME_ScrollDown( editor
, lParam
* get_default_line_height( editor
) );
3557 int nStartCursor
= ME_GetSelectionOfs(editor
, &from
, &to
);
3558 ME_InternalDeleteText(editor
, &editor
->pCursors
[nStartCursor
], to
-from
, FALSE
);
3559 ME_CommitUndo(editor
);
3560 ME_UpdateRepaint(editor
, TRUE
);
3565 WCHAR
*text
= (WCHAR
*)lParam
;
3566 int len
= text
? lstrlenW( text
) : 0;
3568 TRACE( "EM_REPLACESEL - %s\n", debugstr_w( text
) );
3569 ME_ReplaceSel( editor
, !!wParam
, text
, len
);
3572 case EM_SCROLLCARET
:
3573 editor_ensure_visible( editor
, &editor
->pCursors
[0] );
3580 BOOL bRepaint
= LOWORD(lParam
);
3583 wParam
= (WPARAM
)GetStockObject(SYSTEM_FONT
);
3585 if (!GetObjectW((HGDIOBJ
)wParam
, sizeof(LOGFONTW
), &lf
))
3588 hDC
= ITextHost_TxGetDC(editor
->texthost
);
3589 ME_CharFormatFromLogFont(hDC
, &lf
, &fmt
);
3590 ITextHost_TxReleaseDC(editor
->texthost
, hDC
);
3591 if (editor
->mode
& TM_RICHTEXT
) {
3593 ME_SetCursorToStart(editor
, &start
);
3594 ME_SetCharFormat(editor
, &start
, NULL
, &fmt
);
3596 ME_SetDefaultCharFormat(editor
, &fmt
);
3598 ME_CommitUndo(editor
);
3599 editor_mark_rewrap_all( editor
);
3600 ME_WrapMarkedParagraphs(editor
);
3601 ME_UpdateScrollBar(editor
);
3609 ME_SetCursorToStart(editor
, &cursor
);
3610 ME_InternalDeleteText(editor
, &cursor
, ME_GetTextLength(editor
), FALSE
);
3613 TRACE("WM_SETTEXT lParam==%lx\n",lParam
);
3614 if (!strncmp((char *)lParam
, "{\\rtf", 5) ||
3615 !strncmp((char *)lParam
, "{\\urtf", 6))
3617 /* Undocumented: WM_SETTEXT supports RTF text */
3618 ME_StreamInRTFString(editor
, 0, (char *)lParam
);
3621 ME_SetText( editor
, (void*)lParam
, TRUE
);
3624 TRACE("WM_SETTEXT - NULL\n");
3625 ME_SetCursorToStart(editor
, &cursor
);
3626 ME_UpdateLinkAttribute(editor
, &cursor
, INT_MAX
);
3627 set_selection_cursors(editor
, 0, 0);
3628 editor
->nModifyStep
= 0;
3629 ME_CommitUndo(editor
);
3630 ME_EmptyUndoStack(editor
);
3631 ME_UpdateRepaint(editor
, FALSE
);
3635 return paste_special( editor
, 0, NULL
, TRUE
);
3637 case WM_MBUTTONDOWN
:
3641 case EM_PASTESPECIAL
:
3642 paste_special( editor
, wParam
, (REPASTESPECIAL
*)lParam
, FALSE
);
3646 copy_or_cut(editor
, msg
== WM_CUT
);
3648 case WM_GETTEXTLENGTH
:
3650 GETTEXTLENGTHEX how
;
3651 how
.flags
= GTL_CLOSE
| (editor
->bEmulateVersion10
? 0 : GTL_USECRLF
) | GTL_NUMCHARS
;
3652 how
.codepage
= CP_UNICODE
;
3653 return ME_GetTextLengthEx(editor
, &how
);
3655 case EM_GETTEXTLENGTHEX
:
3656 return ME_GetTextLengthEx(editor
, (GETTEXTLENGTHEX
*)wParam
);
3660 ex
.cb
= wParam
* sizeof(WCHAR
);
3661 ex
.flags
= GT_USECRLF
;
3662 ex
.codepage
= CP_UNICODE
;
3663 ex
.lpDefaultChar
= NULL
;
3664 ex
.lpUsedDefChar
= NULL
;
3665 return ME_GetTextEx(editor
, &ex
, lParam
);
3668 return ME_GetTextEx(editor
, (GETTEXTEX
*)wParam
, lParam
);
3671 int nFrom
, nTo
, nStartCur
= ME_GetSelectionOfs(editor
, &nFrom
, &nTo
);
3672 ME_Cursor
*from
= &editor
->pCursors
[nStartCur
];
3673 return get_text_range( editor
, (WCHAR
*)lParam
, from
, nTo
- nFrom
);
3675 case EM_GETSCROLLPOS
:
3677 POINT
*point
= (POINT
*)lParam
;
3678 point
->x
= editor
->horz_si
.nPos
;
3679 point
->y
= editor
->vert_si
.nPos
;
3680 /* 16-bit scaled value is returned as stored in scrollinfo */
3681 if (editor
->horz_si
.nMax
> 0xffff)
3682 point
->x
= MulDiv(point
->x
, 0xffff, editor
->horz_si
.nMax
);
3683 if (editor
->vert_si
.nMax
> 0xffff)
3684 point
->y
= MulDiv(point
->y
, 0xffff, editor
->vert_si
.nMax
);
3687 case EM_GETTEXTRANGE
:
3689 TEXTRANGEW
*rng
= (TEXTRANGEW
*)lParam
;
3691 int nStart
= rng
->chrg
.cpMin
;
3692 int nEnd
= rng
->chrg
.cpMax
;
3693 int textlength
= ME_GetTextLength(editor
);
3695 TRACE( "EM_GETTEXTRANGE min = %d max = %d textlength = %d\n", rng
->chrg
.cpMin
, rng
->chrg
.cpMax
, textlength
);
3696 if (nStart
< 0) return 0;
3697 if ((nStart
== 0 && nEnd
== -1) || nEnd
> textlength
)
3699 if (nStart
>= nEnd
) return 0;
3701 cursor_from_char_ofs( editor
, nStart
, &start
);
3702 return get_text_range( editor
, rng
->lpstrText
, &start
, nEnd
- nStart
);
3708 const unsigned int nMaxChars
= *(WORD
*) lParam
;
3709 unsigned int nCharsLeft
= nMaxChars
;
3710 char *dest
= (char *) lParam
;
3711 ME_Cursor start
, end
;
3713 TRACE( "EM_GETLINE: row=%d, nMaxChars=%d\n", (int)wParam
, nMaxChars
);
3715 row
= row_from_row_number( editor
, wParam
);
3716 if (row
== NULL
) return 0;
3718 row_first_cursor( row
, &start
);
3719 row_end_cursor( row
, &end
, TRUE
);
3725 int ofs
= (run
== start
.run
) ? start
.nOffset
: 0;
3726 int len
= (run
== end
.run
) ? end
.nOffset
: run
->len
;
3728 str
= get_text( run
, ofs
);
3729 nCopy
= min( nCharsLeft
, len
);
3731 memcpy(dest
, str
, nCopy
* sizeof(WCHAR
));
3732 dest
+= nCopy
* sizeof(WCHAR
);
3733 nCharsLeft
-= nCopy
;
3734 if (run
== end
.run
) break;
3735 run
= row_next_run( row
, run
);
3738 /* append line termination, space allowing */
3739 if (nCharsLeft
> 0) *((WCHAR
*)dest
) = '\0';
3741 TRACE("EM_GETLINE: got %u characters\n", nMaxChars
- nCharsLeft
);
3742 return nMaxChars
- nCharsLeft
;
3744 case EM_GETLINECOUNT
:
3746 int count
= editor
->total_rows
;
3747 ME_Run
*prev_run
, *last_run
;
3749 last_run
= para_end_run( para_prev( editor_end_para( editor
) ) );
3750 prev_run
= run_prev_all_paras( last_run
);
3752 if (editor
->bEmulateVersion10
&& prev_run
&& last_run
->nCharOfs
== 0 &&
3753 prev_run
->len
== 1 && *get_text( prev_run
, 0 ) == '\r')
3755 /* In 1.0 emulation, the last solitary \r at the very end of the text
3756 (if one exists) is NOT a line break.
3757 FIXME: this is an ugly hack. This should have a more regular model. */
3761 count
= max(1, count
);
3762 TRACE("EM_GETLINECOUNT: count==%d\n", count
);
3765 case EM_LINEFROMCHAR
:
3767 if (wParam
== -1) wParam
= ME_GetCursorOfs( editor
->pCursors
+ 1 );
3768 return row_number_from_char_ofs( editor
, wParam
);
3770 case EM_EXLINEFROMCHAR
:
3772 if (lParam
== -1) lParam
= ME_GetCursorOfs( editor
->pCursors
+ 1 );
3773 return row_number_from_char_ofs( editor
, lParam
);
3781 if (wParam
== -1) row
= row_from_cursor( editor
->pCursors
);
3782 else row
= row_from_row_number( editor
, wParam
);
3783 if (!row
) return -1;
3785 row_first_cursor( row
, &cursor
);
3786 ofs
= ME_GetCursorOfs( &cursor
);
3787 TRACE( "EM_LINEINDEX: nCharOfs==%d\n", ofs
);
3793 int start_ofs
, end_ofs
;
3796 if (wParam
> ME_GetTextLength(editor
))
3800 FIXME("EM_LINELENGTH: returning number of unselected characters on lines with selection unsupported.\n");
3803 cursor_from_char_ofs( editor
, wParam
, &cursor
);
3804 row
= row_from_cursor( &cursor
);
3805 row_first_cursor( row
, &cursor
);
3806 start_ofs
= ME_GetCursorOfs( &cursor
);
3807 row_end_cursor( row
, &cursor
, FALSE
);
3808 end_ofs
= ME_GetCursorOfs( &cursor
);
3809 TRACE( "EM_LINELENGTH(%ld)==%d\n", wParam
, end_ofs
- start_ofs
);
3810 return end_ofs
- start_ofs
;
3812 case EM_EXLIMITTEXT
:
3814 if ((int)lParam
< 0)
3817 editor
->nTextLimit
= 65536;
3819 editor
->nTextLimit
= (int) lParam
;
3825 editor
->nTextLimit
= 65536;
3827 editor
->nTextLimit
= (int) wParam
;
3830 case EM_GETLIMITTEXT
:
3832 return editor
->nTextLimit
;
3837 FINDTEXTW
*ft
= (FINDTEXTW
*)lParam
;
3838 return ME_FindText(editor
, wParam
, &ft
->chrg
, ft
->lpstrText
, NULL
);
3841 case EM_FINDTEXTEXW
:
3843 FINDTEXTEXW
*ex
= (FINDTEXTEXW
*)lParam
;
3844 return ME_FindText(editor
, wParam
, &ex
->chrg
, ex
->lpstrText
, &ex
->chrgText
);
3847 if (!wParam
|| !lParam
)
3849 *(int *)wParam
= editor
->nZoomNumerator
;
3850 *(int *)lParam
= editor
->nZoomDenominator
;
3853 return ME_SetZoom(editor
, wParam
, lParam
);
3854 case EM_CHARFROMPOS
:
3857 if (ME_CharFromPos(editor
, ((POINTL
*)lParam
)->x
, ((POINTL
*)lParam
)->y
,
3859 return ME_GetCursorOfs(&cursor
);
3863 case EM_POSFROMCHAR
:
3866 int nCharOfs
, nLength
;
3870 /* detect which API version we're dealing with */
3871 if (wParam
>= 0x40000)
3873 nLength
= ME_GetTextLength(editor
);
3874 nCharOfs
= min(nCharOfs
, nLength
);
3875 nCharOfs
= max(nCharOfs
, 0);
3877 cursor_from_char_ofs( editor
, nCharOfs
, &cursor
);
3878 pt
.y
= cursor
.run
->pt
.y
;
3879 pt
.x
= cursor
.run
->pt
.x
+
3880 ME_PointFromChar( editor
, cursor
.run
, cursor
.nOffset
, TRUE
);
3881 pt
.y
+= cursor
.para
->pt
.y
+ editor
->rcFormat
.top
;
3882 pt
.x
+= editor
->rcFormat
.left
;
3884 pt
.x
-= editor
->horz_si
.nPos
;
3885 pt
.y
-= editor
->vert_si
.nPos
;
3887 if (wParam
>= 0x40000) *(POINTL
*)wParam
= pt
;
3889 return (wParam
>= 0x40000) ? 0 : MAKELONG( pt
.x
, pt
.y
);
3891 case WM_LBUTTONDBLCLK
:
3892 case WM_LBUTTONDOWN
:
3894 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
3895 ITextHost_TxSetFocus(editor
->texthost
);
3896 ME_LButtonDown(editor
, (short)LOWORD(lParam
), (short)HIWORD(lParam
),
3897 ME_CalculateClickCount(editor
, msg
, wParam
, lParam
));
3898 ITextHost_TxSetCapture(editor
->texthost
, TRUE
);
3899 editor
->bMouseCaptured
= TRUE
;
3900 link_notify( editor
, msg
, wParam
, lParam
);
3904 if (editor
->bMouseCaptured
)
3905 ME_MouseMove(editor
, (short)LOWORD(lParam
), (short)HIWORD(lParam
));
3907 link_notify( editor
, msg
, wParam
, lParam
);
3910 if (editor
->bMouseCaptured
) {
3911 ITextHost_TxSetCapture(editor
->texthost
, FALSE
);
3912 editor
->bMouseCaptured
= FALSE
;
3914 if (editor
->nSelectionType
== stDocument
)
3915 editor
->nSelectionType
= stPosition
;
3918 link_notify( editor
, msg
, wParam
, lParam
);
3922 case WM_RBUTTONDOWN
:
3923 case WM_RBUTTONDBLCLK
:
3924 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
3925 link_notify( editor
, msg
, wParam
, lParam
);
3927 case WM_CONTEXTMENU
:
3928 if (!ME_ShowContextMenu(editor
, (short)LOWORD(lParam
), (short)HIWORD(lParam
)))
3932 editor
->bHaveFocus
= TRUE
;
3933 create_caret(editor
);
3934 update_caret(editor
);
3935 ITextHost_TxNotify( editor
->texthost
, EN_SETFOCUS
, NULL
);
3936 if (!editor
->bHideSelection
&& (editor
->props
& TXTBIT_HIDESELECTION
))
3937 ME_InvalidateSelection( editor
);
3940 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
3941 editor
->bHaveFocus
= FALSE
;
3942 editor
->wheel_remain
= 0;
3945 ITextHost_TxNotify( editor
->texthost
, EN_KILLFOCUS
, NULL
);
3946 if (!editor
->bHideSelection
&& (editor
->props
& TXTBIT_HIDESELECTION
))
3947 ME_InvalidateSelection( editor
);
3950 TRACE("editor wnd command = %d\n", LOWORD(wParam
));
3953 if (ME_KeyDown(editor
, LOWORD(wParam
)))
3957 return handle_wm_char( editor
, wParam
, lParam
);
3959 if (wParam
== UNICODE_NOCHAR
) return TRUE
;
3960 if (wParam
<= 0x000fffff)
3962 if (wParam
> 0xffff) /* convert to surrogates */
3965 handle_wm_char( editor
, (wParam
>> 10) + 0xd800, 0 );
3966 handle_wm_char( editor
, (wParam
& 0x03ff) + 0xdc00, 0 );
3969 handle_wm_char( editor
, wParam
, 0 );
3972 case EM_STOPGROUPTYPING
:
3973 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
3977 const int scrollUnit
= 7;
3979 switch(LOWORD(wParam
))
3982 scroll_abs( editor
, 0, 0, TRUE
);
3985 scroll_abs( editor
, editor
->horz_si
.nMax
- (int)editor
->horz_si
.nPage
,
3986 editor
->vert_si
.nMax
- (int)editor
->vert_si
.nPage
, TRUE
);
3989 ME_ScrollLeft(editor
, scrollUnit
);
3992 ME_ScrollRight(editor
, scrollUnit
);
3995 ME_ScrollLeft(editor
, editor
->sizeWindow
.cx
);
3998 ME_ScrollRight(editor
, editor
->sizeWindow
.cx
);
4001 case SB_THUMBPOSITION
:
4003 int pos
= HIWORD(wParam
);
4004 if (editor
->horz_si
.nMax
> 0xffff)
4005 pos
= MulDiv(pos
, editor
->horz_si
.nMax
, 0xffff);
4006 scroll_h_abs( editor
, pos
, FALSE
);
4012 case EM_SCROLL
: /* fall through */
4016 int lineHeight
= get_default_line_height( editor
);
4018 origNPos
= editor
->vert_si
.nPos
;
4020 switch(LOWORD(wParam
))
4023 scroll_abs( editor
, 0, 0, TRUE
);
4026 scroll_abs( editor
, editor
->horz_si
.nMax
- (int)editor
->horz_si
.nPage
,
4027 editor
->vert_si
.nMax
- (int)editor
->vert_si
.nPage
, TRUE
);
4030 ME_ScrollUp(editor
,lineHeight
);
4033 ME_ScrollDown(editor
,lineHeight
);
4036 ME_ScrollUp(editor
,editor
->sizeWindow
.cy
);
4039 ME_ScrollDown(editor
,editor
->sizeWindow
.cy
);
4042 case SB_THUMBPOSITION
:
4044 int pos
= HIWORD(wParam
);
4045 if (editor
->vert_si
.nMax
> 0xffff)
4046 pos
= MulDiv(pos
, editor
->vert_si
.nMax
, 0xffff);
4047 scroll_v_abs( editor
, pos
, FALSE
);
4051 if (msg
== EM_SCROLL
)
4052 return 0x00010000 | (((editor
->vert_si
.nPos
- origNPos
)/lineHeight
) & 0xffff);
4057 int delta
= GET_WHEEL_DELTA_WPARAM( wParam
);
4058 BOOL ctrl_is_down
= GetKeyState( VK_CONTROL
) & 0x8000;
4060 /* if scrolling changes direction, ignore left overs */
4061 if ((delta
< 0 && editor
->wheel_remain
< 0) ||
4062 (delta
> 0 && editor
->wheel_remain
> 0))
4063 editor
->wheel_remain
+= delta
;
4065 editor
->wheel_remain
= delta
;
4067 if (editor
->wheel_remain
)
4071 if (!editor
->nZoomNumerator
|| !editor
->nZoomDenominator
)
4075 numerator
= editor
->nZoomNumerator
* 100 / editor
->nZoomDenominator
;
4077 numerator
+= calc_wheel_change( &editor
->wheel_remain
, 10 );
4078 if (numerator
>= 10 && numerator
<= 500)
4079 ME_SetZoom(editor
, numerator
, 100);
4084 SystemParametersInfoW( SPI_GETWHEELSCROLLLINES
, 0, &max_lines
, 0 );
4086 lines
= calc_wheel_change( &editor
->wheel_remain
, (int)max_lines
);
4088 ME_ScrollDown( editor
, -lines
* get_default_line_height( editor
) );
4093 case EM_REQUESTRESIZE
:
4094 ME_SendRequestResize(editor
, TRUE
);
4096 /* IME messages to make richedit controls IME aware */
4097 case WM_IME_SETCONTEXT
:
4098 case WM_IME_CONTROL
:
4100 case WM_IME_COMPOSITIONFULL
:
4102 case WM_IME_STARTCOMPOSITION
:
4104 editor
->imeStartIndex
=ME_GetCursorOfs(&editor
->pCursors
[0]);
4105 ME_DeleteSelection(editor
);
4106 ME_CommitUndo(editor
);
4107 ME_UpdateRepaint(editor
, FALSE
);
4110 case WM_IME_COMPOSITION
:
4114 ME_Style
*style
= style_get_insert_style( editor
, editor
->pCursors
);
4115 hIMC
= ITextHost_TxImmGetContext(editor
->texthost
);
4116 ME_DeleteSelection(editor
);
4117 ME_SaveTempStyle(editor
, style
);
4118 if (lParam
& (GCS_RESULTSTR
|GCS_COMPSTR
))
4120 LPWSTR lpCompStr
= NULL
;
4122 DWORD dwIndex
= lParam
& GCS_RESULTSTR
;
4124 dwIndex
= GCS_COMPSTR
;
4126 dwBufLen
= ImmGetCompositionStringW(hIMC
, dwIndex
, NULL
, 0);
4127 lpCompStr
= HeapAlloc(GetProcessHeap(),0,dwBufLen
+ sizeof(WCHAR
));
4128 ImmGetCompositionStringW(hIMC
, dwIndex
, lpCompStr
, dwBufLen
);
4129 lpCompStr
[dwBufLen
/sizeof(WCHAR
)] = 0;
4130 ME_InsertTextFromCursor(editor
,0,lpCompStr
,dwBufLen
/sizeof(WCHAR
),style
);
4131 HeapFree(GetProcessHeap(), 0, lpCompStr
);
4133 if (dwIndex
== GCS_COMPSTR
)
4134 set_selection_cursors(editor
,editor
->imeStartIndex
,
4135 editor
->imeStartIndex
+ dwBufLen
/sizeof(WCHAR
));
4137 ME_ReleaseStyle(style
);
4138 ME_CommitUndo(editor
);
4139 ME_UpdateRepaint(editor
, FALSE
);
4142 case WM_IME_ENDCOMPOSITION
:
4144 ME_DeleteSelection(editor
);
4145 editor
->imeStartIndex
=-1;
4148 case EM_GETOLEINTERFACE
:
4149 IRichEditOle_AddRef( editor
->richole
);
4150 *(IRichEditOle
**)lParam
= editor
->richole
;
4153 case EM_SETOLECALLBACK
:
4154 if(editor
->lpOleCallback
)
4155 IRichEditOleCallback_Release(editor
->lpOleCallback
);
4156 editor
->lpOleCallback
= (IRichEditOleCallback
*)lParam
;
4157 if(editor
->lpOleCallback
)
4158 IRichEditOleCallback_AddRef(editor
->lpOleCallback
);
4160 case EM_GETWORDBREAKPROC
:
4161 return (LRESULT
)editor
->pfnWordBreak
;
4162 case EM_SETWORDBREAKPROC
:
4164 EDITWORDBREAKPROCW pfnOld
= editor
->pfnWordBreak
;
4166 editor
->pfnWordBreak
= (EDITWORDBREAKPROCW
)lParam
;
4167 return (LRESULT
)pfnOld
;
4169 case EM_GETTEXTMODE
:
4170 return editor
->mode
;
4171 case EM_SETTEXTMODE
:
4176 if (ME_GetTextLength(editor
) ||
4177 !list_empty( &editor
->undo_stack
) || !list_empty( &editor
->redo_stack
))
4178 return E_UNEXPECTED
;
4180 /* Check for mutually exclusive flags in adjacent bits of wParam */
4181 if ((wParam
& (TM_RICHTEXT
| TM_MULTILEVELUNDO
| TM_MULTICODEPAGE
)) &
4182 (wParam
& (TM_PLAINTEXT
| TM_SINGLELEVELUNDO
| TM_SINGLECODEPAGE
)) << 1)
4183 return E_INVALIDARG
;
4185 if (wParam
& (TM_RICHTEXT
| TM_PLAINTEXT
))
4187 mask
|= TM_RICHTEXT
| TM_PLAINTEXT
;
4188 changes
|= wParam
& (TM_RICHTEXT
| TM_PLAINTEXT
);
4189 if (wParam
& TM_PLAINTEXT
) {
4190 /* Clear selection since it should be possible to select the
4191 * end of text run for rich text */
4192 ME_InvalidateSelection(editor
);
4193 ME_SetCursorToStart(editor
, &editor
->pCursors
[0]);
4194 editor
->pCursors
[1] = editor
->pCursors
[0];
4195 /* plain text can only have the default style. */
4196 ME_ClearTempStyle(editor
);
4197 ME_AddRefStyle(editor
->pBuffer
->pDefaultStyle
);
4198 ME_ReleaseStyle( editor
->pCursors
[0].run
->style
);
4199 editor
->pCursors
[0].run
->style
= editor
->pBuffer
->pDefaultStyle
;
4202 /* FIXME: Currently no support for undo level and code page options */
4203 editor
->mode
= (editor
->mode
& ~mask
) | changes
;
4206 case EM_SETTARGETDEVICE
:
4209 BOOL
new = (lParam
== 0 && (editor
->props
& TXTBIT_MULTILINE
));
4210 if (editor
->nAvailWidth
|| editor
->bWordWrap
!= new)
4212 editor
->bWordWrap
= new;
4213 editor
->nAvailWidth
= 0; /* wrap to client area */
4214 ME_RewrapRepaint(editor
);
4217 int width
= max(0, lParam
);
4218 if ((editor
->props
& TXTBIT_MULTILINE
) &&
4219 (!editor
->bWordWrap
|| editor
->nAvailWidth
!= width
))
4221 editor
->nAvailWidth
= width
;
4222 editor
->bWordWrap
= TRUE
;
4223 ME_RewrapRepaint(editor
);
4225 FIXME("EM_SETTARGETDEVICE doesn't use non-NULL target devices\n");
4230 *phresult
= S_FALSE
;
4236 /* Fill buffer with srcChars unicode characters from the start cursor.
4238 * buffer: destination buffer
4239 * buflen: length of buffer in characters excluding the NULL terminator.
4240 * start: start of editor text to copy into buffer.
4241 * srcChars: Number of characters to use from the editor text.
4242 * bCRLF: if true, replaces all end of lines with \r\n pairs.
4244 * returns the number of characters written excluding the NULL terminator.
4246 * The written text is always NULL terminated.
4248 int ME_GetTextW(ME_TextEditor
*editor
, WCHAR
*buffer
, int buflen
,
4249 const ME_Cursor
*start
, int srcChars
, BOOL bCRLF
,
4252 ME_Run
*run
, *next_run
;
4253 const WCHAR
*pStart
= buffer
;
4257 /* bCRLF flag is only honored in 2.0 and up. 1.0 must always return text verbatim */
4258 if (editor
->bEmulateVersion10
) bCRLF
= FALSE
;
4261 next_run
= run_next_all_paras( run
);
4263 nLen
= run
->len
- start
->nOffset
;
4264 str
= get_text( run
, start
->nOffset
);
4266 while (srcChars
&& buflen
&& next_run
)
4268 if (bCRLF
&& run
->nFlags
& MERF_ENDPARA
&& ~run
->nFlags
& MERF_ENDCELL
)
4270 if (buflen
== 1) break;
4271 /* FIXME: native fails to reduce srcChars here for WM_GETTEXT or
4272 * EM_GETTEXTEX, however, this is done for copying text which
4273 * also uses this function. */
4274 srcChars
-= min(nLen
, srcChars
);
4280 nLen
= min(nLen
, srcChars
);
4284 nLen
= min(nLen
, buflen
);
4287 CopyMemory(buffer
, str
, sizeof(WCHAR
) * nLen
);
4292 next_run
= run_next_all_paras( run
);
4295 str
= get_text( run
, 0 );
4297 /* append '\r' to the last paragraph. */
4298 if (run
== para_end_run( para_prev( editor_end_para( editor
) ) ) && bEOP
)
4304 return buffer
- pStart
;
4307 static int __cdecl
wchar_comp( const void *key
, const void *elem
)
4309 return *(const WCHAR
*)key
- *(const WCHAR
*)elem
;
4312 /* neutral characters end the url if the next non-neutral character is a space character,
4313 otherwise they are included in the url. */
4314 static BOOL
isurlneutral( WCHAR c
)
4316 /* NB this list is sorted */
4317 static const WCHAR neutral_chars
[] = L
"!\"'(),-.:;<>?[]{}";
4319 /* Some shortcuts */
4320 if (isalnum( c
)) return FALSE
;
4321 if (c
> L
'}') return FALSE
;
4323 return !!bsearch( &c
, neutral_chars
, ARRAY_SIZE( neutral_chars
) - 1, sizeof(c
), wchar_comp
);
4327 * This proc takes a selection, and scans it forward in order to select the span
4328 * of a possible URL candidate. A possible URL candidate must start with isalnum
4329 * or one of the following special characters: *|/\+%#@ and must consist entirely
4330 * of the characters allowed to start the URL, plus : (colon) which may occur
4331 * at most once, and not at either end.
4333 static BOOL
ME_FindNextURLCandidate(ME_TextEditor
*editor
,
4334 const ME_Cursor
*start
,
4336 ME_Cursor
*candidate_min
,
4337 ME_Cursor
*candidate_max
)
4339 ME_Cursor cursor
= *start
, neutral_end
, space_end
;
4340 BOOL candidateStarted
= FALSE
, quoted
= FALSE
;
4345 WCHAR
*str
= get_text( cursor
.run
, 0 );
4346 int run_len
= cursor
.run
->len
;
4348 nChars
-= run_len
- cursor
.nOffset
;
4350 /* Find start of candidate */
4351 if (!candidateStarted
)
4353 while (cursor
.nOffset
< run_len
)
4355 c
= str
[cursor
.nOffset
];
4356 if (!iswspace( c
) && !isurlneutral( c
))
4358 *candidate_min
= cursor
;
4359 candidateStarted
= TRUE
;
4360 neutral_end
.para
= NULL
;
4361 space_end
.para
= NULL
;
4365 quoted
= (c
== '<');
4370 /* Find end of candidate */
4371 if (candidateStarted
)
4373 while (cursor
.nOffset
< run_len
)
4375 c
= str
[cursor
.nOffset
];
4378 if (quoted
&& c
!= '\r')
4380 if (!space_end
.para
)
4382 if (neutral_end
.para
)
4383 space_end
= neutral_end
;
4391 else if (isurlneutral( c
))
4393 if (quoted
&& c
== '>')
4395 neutral_end
.para
= NULL
;
4396 space_end
.para
= NULL
;
4399 if (!neutral_end
.para
)
4400 neutral_end
= cursor
;
4403 neutral_end
.para
= NULL
;
4410 if (!cursor_next_run( &cursor
, TRUE
))
4415 if (candidateStarted
)
4418 *candidate_max
= space_end
;
4419 else if (neutral_end
.para
)
4420 *candidate_max
= neutral_end
;
4422 *candidate_max
= cursor
;
4425 *candidate_max
= *candidate_min
= cursor
;
4430 * This proc evaluates the selection and returns TRUE if it can be considered an URL
4432 static BOOL
ME_IsCandidateAnURL(ME_TextEditor
*editor
, const ME_Cursor
*start
, int nChars
)
4434 #define MAX_PREFIX_LEN 9
4435 #define X(str) str, ARRAY_SIZE(str) - 1
4437 const WCHAR text
[MAX_PREFIX_LEN
];
4454 WCHAR bufferW
[MAX_PREFIX_LEN
+ 1];
4457 ME_GetTextW(editor
, bufferW
, MAX_PREFIX_LEN
, start
, nChars
, FALSE
, FALSE
);
4458 for (i
= 0; i
< ARRAY_SIZE(prefixes
); i
++)
4460 if (nChars
< prefixes
[i
].length
) continue;
4461 if (!memcmp(prefixes
[i
].text
, bufferW
, prefixes
[i
].length
* sizeof(WCHAR
)))
4465 #undef MAX_PREFIX_LEN
4469 * This proc walks through the indicated selection and evaluates whether each
4470 * section identified by ME_FindNextURLCandidate and in-between sections have
4471 * their proper CFE_LINK attributes set or unset. If the CFE_LINK attribute is
4472 * not what it is supposed to be, this proc sets or unsets it as appropriate.
4474 * Since this function can cause runs to be split, do not depend on the value
4475 * of the start cursor at the end of the function.
4477 * nChars may be set to INT_MAX to update to the end of the text.
4479 * Returns TRUE if at least one section was modified.
4481 static BOOL
ME_UpdateLinkAttribute(ME_TextEditor
*editor
, ME_Cursor
*start
, int nChars
)
4483 BOOL modified
= FALSE
;
4484 ME_Cursor startCur
= *start
;
4486 if (!editor
->AutoURLDetect_bEnable
) return FALSE
;
4491 ME_Cursor candidateStart
, candidateEnd
;
4493 if (ME_FindNextURLCandidate(editor
, &startCur
, nChars
,
4494 &candidateStart
, &candidateEnd
))
4496 /* Section before candidate is not an URL */
4497 int cMin
= ME_GetCursorOfs(&candidateStart
);
4498 int cMax
= ME_GetCursorOfs(&candidateEnd
);
4500 if (!ME_IsCandidateAnURL(editor
, &candidateStart
, cMax
- cMin
))
4501 candidateStart
= candidateEnd
;
4502 nChars
-= cMax
- ME_GetCursorOfs(&startCur
);
4506 /* No more candidates until end of selection */
4510 if (startCur
.run
!= candidateStart
.run
||
4511 startCur
.nOffset
!= candidateStart
.nOffset
)
4513 /* CFE_LINK effect should be consistently unset */
4514 link
.cbSize
= sizeof(link
);
4515 ME_GetCharFormat(editor
, &startCur
, &candidateStart
, &link
);
4516 if (!(link
.dwMask
& CFM_LINK
) || (link
.dwEffects
& CFE_LINK
))
4518 /* CFE_LINK must be unset from this range */
4519 memset(&link
, 0, sizeof(CHARFORMAT2W
));
4520 link
.cbSize
= sizeof(link
);
4521 link
.dwMask
= CFM_LINK
;
4523 ME_SetCharFormat(editor
, &startCur
, &candidateStart
, &link
);
4524 /* Update candidateEnd since setting character formats may split
4525 * runs, which can cause a cursor to be at an invalid offset within
4527 while (candidateEnd
.nOffset
>= candidateEnd
.run
->len
)
4529 candidateEnd
.nOffset
-= candidateEnd
.run
->len
;
4530 candidateEnd
.run
= run_next_all_paras( candidateEnd
.run
);
4535 if (candidateStart
.run
!= candidateEnd
.run
||
4536 candidateStart
.nOffset
!= candidateEnd
.nOffset
)
4538 /* CFE_LINK effect should be consistently set */
4539 link
.cbSize
= sizeof(link
);
4540 ME_GetCharFormat(editor
, &candidateStart
, &candidateEnd
, &link
);
4541 if (!(link
.dwMask
& CFM_LINK
) || !(link
.dwEffects
& CFE_LINK
))
4543 /* CFE_LINK must be set on this range */
4544 memset(&link
, 0, sizeof(CHARFORMAT2W
));
4545 link
.cbSize
= sizeof(link
);
4546 link
.dwMask
= CFM_LINK
;
4547 link
.dwEffects
= CFE_LINK
;
4548 ME_SetCharFormat(editor
, &candidateStart
, &candidateEnd
, &link
);
4552 startCur
= candidateEnd
;
4553 } while (nChars
> 0);