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?
230 #define NO_SHLWAPI_STREAM
236 #define STACK_SIZE_DEFAULT 100
237 #define STACK_SIZE_MAX 1000
239 #define TEXT_LIMIT_DEFAULT 32767
241 WINE_DEFAULT_DEBUG_CHANNEL(richedit
);
243 static BOOL
ME_UpdateLinkAttribute(ME_TextEditor
*editor
, ME_Cursor
*start
, int nChars
);
245 HINSTANCE dll_instance
= NULL
;
246 BOOL me_debug
= FALSE
;
248 static ME_TextBuffer
*ME_MakeText(void) {
249 ME_TextBuffer
*buf
= malloc(sizeof(*buf
));
250 ME_DisplayItem
*p1
= ME_MakeDI(diTextStart
);
251 ME_DisplayItem
*p2
= ME_MakeDI(diTextEnd
);
257 p1
->member
.para
.next_para
= p2
;
258 p2
->member
.para
.prev_para
= p1
;
259 p2
->member
.para
.nCharOfs
= 0;
263 buf
->pCharStyle
= NULL
;
268 ME_Paragraph
*editor_first_para( ME_TextEditor
*editor
)
270 return para_next( &editor
->pBuffer
->pFirst
->member
.para
);
273 /* Note, returns the diTextEnd sentinel paragraph */
274 ME_Paragraph
*editor_end_para( ME_TextEditor
*editor
)
276 return &editor
->pBuffer
->pLast
->member
.para
;
279 static BOOL
editor_beep( ME_TextEditor
*editor
, UINT type
)
281 return editor
->props
& TXTBIT_ALLOWBEEP
&& MessageBeep( type
);
284 static LRESULT
ME_StreamInText(ME_TextEditor
*editor
, DWORD dwFormat
, ME_InStream
*stream
, ME_Style
*style
)
287 LRESULT total_bytes_read
= 0;
288 BOOL is_read
= FALSE
;
289 DWORD cp
= CP_ACP
, copy
= 0;
290 char conv_buf
[4 + STREAMIN_BUFFER_SIZE
]; /* up to 4 additional UTF-8 bytes */
292 static const char bom_utf8
[] = {0xEF, 0xBB, 0xBF};
294 TRACE("%08lx %p\n", dwFormat
, stream
);
298 WCHAR wszText
[STREAMIN_BUFFER_SIZE
+1];
302 ME_StreamInFill(stream
);
303 if (stream
->editstream
->dwError
)
307 total_bytes_read
+= stream
->dwSize
;
310 if (!(dwFormat
& SF_UNICODE
))
312 char * buf
= stream
->buffer
;
313 DWORD size
= stream
->dwSize
, end
;
318 if (stream
->dwSize
>= 3 && !memcmp(stream
->buffer
, bom_utf8
, 3))
330 memcpy(conv_buf
+ copy
, buf
, size
);
335 while ((buf
[end
-1] & 0xC0) == 0x80)
338 --total_bytes_read
; /* strange, but seems to match windows */
340 if (buf
[end
-1] & 0x80)
343 if ((buf
[end
-1] & 0xE0) == 0xC0)
345 if ((buf
[end
-1] & 0xF0) == 0xE0)
347 if ((buf
[end
-1] & 0xF8) == 0xF0)
350 if (size
- end
>= need
)
352 /* we have enough bytes for this sequence */
357 /* need more bytes, so don't transcode this sequence */
365 nWideChars
= MultiByteToWideChar(cp
, 0, buf
, end
, wszText
, STREAMIN_BUFFER_SIZE
);
372 memcpy(conv_buf
, buf
+ end
, size
- end
);
379 nWideChars
= stream
->dwSize
>> 1;
380 pText
= (WCHAR
*)stream
->buffer
;
383 ME_InsertTextFromCursor(editor
, 0, pText
, nWideChars
, style
);
384 if (stream
->dwSize
== 0)
388 return total_bytes_read
;
391 static void ME_ApplyBorderProperties(RTF_Info
*info
,
392 ME_BorderRect
*borderRect
,
393 RTFBorder
*borderDef
)
396 ME_Border
*pBorders
[] = {&borderRect
->top
,
400 for (i
= 0; i
< 4; i
++)
402 RTFColor
*colorDef
= info
->colorList
;
403 pBorders
[i
]->width
= borderDef
[i
].width
;
404 colorNum
= borderDef
[i
].color
;
405 while (colorDef
&& colorDef
->rtfCNum
!= colorNum
)
406 colorDef
= colorDef
->rtfNextColor
;
408 pBorders
[i
]->colorRef
= RGB(
409 colorDef
->rtfCRed
>= 0 ? colorDef
->rtfCRed
: 0,
410 colorDef
->rtfCGreen
>= 0 ? colorDef
->rtfCGreen
: 0,
411 colorDef
->rtfCBlue
>= 0 ? colorDef
->rtfCBlue
: 0);
413 pBorders
[i
]->colorRef
= RGB(0, 0, 0);
417 void ME_RTFCharAttrHook(RTF_Info
*info
)
420 fmt
.cbSize
= sizeof(fmt
);
424 switch(info
->rtfMinor
)
427 /* FIXME add more flags once they're implemented */
428 fmt
.dwMask
= CFM_BOLD
| CFM_ITALIC
| CFM_UNDERLINE
| CFM_UNDERLINETYPE
| CFM_STRIKEOUT
|
429 CFM_COLOR
| CFM_BACKCOLOR
| CFM_SIZE
| CFM_WEIGHT
;
430 fmt
.dwEffects
= CFE_AUTOCOLOR
| CFE_AUTOBACKCOLOR
;
431 fmt
.yHeight
= 12*20; /* 12pt */
432 fmt
.wWeight
= FW_NORMAL
;
433 fmt
.bUnderlineType
= CFU_UNDERLINE
;
436 fmt
.dwMask
= CFM_BOLD
| CFM_WEIGHT
;
437 fmt
.dwEffects
= info
->rtfParam
? CFE_BOLD
: 0;
438 fmt
.wWeight
= info
->rtfParam
? FW_BOLD
: FW_NORMAL
;
441 fmt
.dwMask
= CFM_ITALIC
;
442 fmt
.dwEffects
= info
->rtfParam
? fmt
.dwMask
: 0;
445 fmt
.dwMask
= CFM_UNDERLINETYPE
| CFM_UNDERLINE
;
446 fmt
.bUnderlineType
= CFU_UNDERLINE
;
447 fmt
.dwEffects
= info
->rtfParam
? CFE_UNDERLINE
: 0;
449 case rtfDotUnderline
:
450 fmt
.dwMask
= CFM_UNDERLINETYPE
| CFM_UNDERLINE
;
451 fmt
.bUnderlineType
= CFU_UNDERLINEDOTTED
;
452 fmt
.dwEffects
= info
->rtfParam
? CFE_UNDERLINE
: 0;
455 fmt
.dwMask
= CFM_UNDERLINETYPE
| CFM_UNDERLINE
;
456 fmt
.bUnderlineType
= CFU_UNDERLINEDOUBLE
;
457 fmt
.dwEffects
= info
->rtfParam
? CFE_UNDERLINE
: 0;
459 case rtfWordUnderline
:
460 fmt
.dwMask
= CFM_UNDERLINETYPE
| CFM_UNDERLINE
;
461 fmt
.bUnderlineType
= CFU_UNDERLINEWORD
;
462 fmt
.dwEffects
= info
->rtfParam
? CFE_UNDERLINE
: 0;
465 fmt
.dwMask
= CFM_UNDERLINE
;
469 fmt
.dwMask
= CFM_STRIKEOUT
;
470 fmt
.dwEffects
= info
->rtfParam
? fmt
.dwMask
: 0;
474 case rtfSubScrShrink
:
475 case rtfSuperScrShrink
:
477 fmt
.dwMask
= CFM_SUBSCRIPT
|CFM_SUPERSCRIPT
;
478 if (info
->rtfMinor
== rtfSubScrShrink
) fmt
.dwEffects
= CFE_SUBSCRIPT
;
479 if (info
->rtfMinor
== rtfSuperScrShrink
) fmt
.dwEffects
= CFE_SUPERSCRIPT
;
480 if (info
->rtfMinor
== rtfNoSuperSub
) fmt
.dwEffects
= 0;
483 fmt
.dwMask
= CFM_HIDDEN
;
484 fmt
.dwEffects
= info
->rtfParam
? fmt
.dwMask
: 0;
487 fmt
.dwMask
= CFM_BACKCOLOR
;
489 if (info
->rtfParam
== 0)
490 fmt
.dwEffects
= CFE_AUTOBACKCOLOR
;
491 else if (info
->rtfParam
!= rtfNoParam
)
493 RTFColor
*c
= RTFGetColor(info
, info
->rtfParam
);
494 if (c
&& c
->rtfCBlue
>= 0)
495 fmt
.crBackColor
= (c
->rtfCBlue
<<16)|(c
->rtfCGreen
<<8)|(c
->rtfCRed
);
497 fmt
.dwEffects
= CFE_AUTOBACKCOLOR
;
501 fmt
.dwMask
= CFM_COLOR
;
503 if (info
->rtfParam
== 0)
504 fmt
.dwEffects
= CFE_AUTOCOLOR
;
505 else if (info
->rtfParam
!= rtfNoParam
)
507 RTFColor
*c
= RTFGetColor(info
, info
->rtfParam
);
508 if (c
&& c
->rtfCBlue
>= 0)
509 fmt
.crTextColor
= (c
->rtfCBlue
<<16)|(c
->rtfCGreen
<<8)|(c
->rtfCRed
);
511 fmt
.dwEffects
= CFE_AUTOCOLOR
;
516 if (info
->rtfParam
!= rtfNoParam
)
518 RTFFont
*f
= RTFGetFont(info
, info
->rtfParam
);
521 MultiByteToWideChar(CP_ACP
, 0, f
->rtfFName
, -1, fmt
.szFaceName
, ARRAY_SIZE(fmt
.szFaceName
));
522 fmt
.szFaceName
[ARRAY_SIZE(fmt
.szFaceName
)-1] = '\0';
523 fmt
.bCharSet
= f
->rtfFCharSet
;
524 fmt
.dwMask
= CFM_FACE
| CFM_CHARSET
;
525 fmt
.bPitchAndFamily
= f
->rtfFPitch
| (f
->rtfFFamily
<< 4);
530 fmt
.dwMask
= CFM_SIZE
;
531 if (info
->rtfParam
!= rtfNoParam
)
532 fmt
.yHeight
= info
->rtfParam
*10;
537 RTFFlushOutputBuffer(info
);
538 /* FIXME too slow ? how come ? */
539 style2
= ME_ApplyStyle(info
->editor
, info
->style
, &fmt
);
540 ME_ReleaseStyle(info
->style
);
541 info
->style
= style2
;
542 info
->styleChanged
= TRUE
;
546 /* FIXME this function doesn't get any information about context of the RTF tag, which is very bad,
547 the same tags mean different things in different contexts */
548 void ME_RTFParAttrHook(RTF_Info
*info
)
550 switch(info
->rtfMinor
)
552 case rtfParDef
: /* restores default paragraph attributes */
553 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
554 info
->borderType
= RTFBorderParaLeft
;
555 else /* v1.0 - 3.0 */
556 info
->borderType
= RTFBorderParaTop
;
557 info
->fmt
.dwMask
= PFM_ALIGNMENT
| PFM_BORDER
| PFM_LINESPACING
| PFM_TABSTOPS
|
558 PFM_OFFSET
| PFM_RIGHTINDENT
| PFM_SPACEAFTER
| PFM_SPACEBEFORE
|
559 PFM_STARTINDENT
| PFM_RTLPARA
| PFM_NUMBERING
| PFM_NUMBERINGSTART
|
560 PFM_NUMBERINGSTYLE
| PFM_NUMBERINGTAB
;
562 info
->fmt
.wAlignment
= PFA_LEFT
;
563 info
->fmt
.cTabCount
= 0;
564 info
->fmt
.dxOffset
= info
->fmt
.dxStartIndent
= info
->fmt
.dxRightIndent
= 0;
565 info
->fmt
.wBorderWidth
= info
->fmt
.wBorders
= 0;
566 info
->fmt
.wBorderSpace
= 0;
567 info
->fmt
.bLineSpacingRule
= 0;
568 info
->fmt
.dySpaceBefore
= info
->fmt
.dySpaceAfter
= 0;
569 info
->fmt
.dyLineSpacing
= 0;
570 info
->fmt
.wEffects
&= ~PFE_RTLPARA
;
571 info
->fmt
.wNumbering
= 0;
572 info
->fmt
.wNumberingStart
= 0;
573 info
->fmt
.wNumberingStyle
= 0;
574 info
->fmt
.wNumberingTab
= 0;
576 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
578 if (info
->tableDef
&& info
->tableDef
->row_start
&&
579 info
->tableDef
->row_start
->nFlags
& MEPF_ROWEND
)
583 /* We are just after a table row. */
584 RTFFlushOutputBuffer(info
);
585 cursor
= info
->editor
->pCursors
[0];
587 if (para
== para_next( info
->tableDef
->row_start
)
588 && !cursor
.nOffset
&& !cursor
.run
->nCharOfs
)
590 /* Since the table row end, no text has been inserted, and the \intbl
591 * control word has not be used. We can confirm that we are not in a
594 info
->tableDef
->row_start
= NULL
;
595 info
->canInheritInTbl
= FALSE
;
599 else /* v1.0 - v3.0 */
601 info
->fmt
.dwMask
|= PFM_TABLE
;
602 info
->fmt
.wEffects
&= ~PFE_TABLE
;
606 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
608 while (info
->rtfParam
> info
->nestingLevel
)
610 RTFTable
*tableDef
= calloc(1, sizeof(*tableDef
));
611 tableDef
->parent
= info
->tableDef
;
612 info
->tableDef
= tableDef
;
614 RTFFlushOutputBuffer(info
);
615 if (tableDef
->row_start
&& tableDef
->row_start
->nFlags
& MEPF_ROWEND
)
617 ME_Paragraph
*para
= para_next( tableDef
->row_start
);
618 tableDef
->row_start
= table_insert_row_start_at_para( info
->editor
, para
);
623 cursor
= info
->editor
->pCursors
[0];
624 if (cursor
.nOffset
|| cursor
.run
->nCharOfs
)
625 ME_InsertTextFromCursor(info
->editor
, 0, L
"\r", 1, info
->style
);
626 tableDef
->row_start
= table_insert_row_start( info
->editor
, info
->editor
->pCursors
);
629 info
->nestingLevel
++;
631 info
->canInheritInTbl
= FALSE
;
636 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
638 if (info
->nestingLevel
< 1)
644 info
->tableDef
= calloc(1, sizeof(*info
->tableDef
));
645 tableDef
= info
->tableDef
;
646 RTFFlushOutputBuffer(info
);
647 if (tableDef
->row_start
&& tableDef
->row_start
->nFlags
& MEPF_ROWEND
)
648 para
= para_next( tableDef
->row_start
);
650 para
= info
->editor
->pCursors
[0].para
;
652 tableDef
->row_start
= table_insert_row_start_at_para( info
->editor
, para
);
654 info
->nestingLevel
= 1;
655 info
->canInheritInTbl
= TRUE
;
658 } else { /* v1.0 - v3.0 */
659 info
->fmt
.dwMask
|= PFM_TABLE
;
660 info
->fmt
.wEffects
|= PFE_TABLE
;
666 if ((info
->fmt
.dwMask
& (PFM_STARTINDENT
| PFM_OFFSET
)) != (PFM_STARTINDENT
| PFM_OFFSET
))
669 fmt
.cbSize
= sizeof(fmt
);
670 editor_get_selection_para_fmt( info
->editor
, &fmt
);
671 info
->fmt
.dwMask
|= PFM_STARTINDENT
| PFM_OFFSET
;
672 info
->fmt
.dxStartIndent
= fmt
.dxStartIndent
;
673 info
->fmt
.dxOffset
= fmt
.dxOffset
;
675 if (info
->rtfMinor
== rtfFirstIndent
)
677 info
->fmt
.dxStartIndent
+= info
->fmt
.dxOffset
+ info
->rtfParam
;
678 info
->fmt
.dxOffset
= -info
->rtfParam
;
681 info
->fmt
.dxStartIndent
= info
->rtfParam
- info
->fmt
.dxOffset
;
684 info
->fmt
.dwMask
|= PFM_RIGHTINDENT
;
685 info
->fmt
.dxRightIndent
= info
->rtfParam
;
689 info
->fmt
.dwMask
|= PFM_ALIGNMENT
;
690 info
->fmt
.wAlignment
= PFA_LEFT
;
693 info
->fmt
.dwMask
|= PFM_ALIGNMENT
;
694 info
->fmt
.wAlignment
= PFA_RIGHT
;
697 info
->fmt
.dwMask
|= PFM_ALIGNMENT
;
698 info
->fmt
.wAlignment
= PFA_CENTER
;
701 if (!(info
->fmt
.dwMask
& PFM_TABSTOPS
))
704 fmt
.cbSize
= sizeof(fmt
);
705 editor_get_selection_para_fmt( info
->editor
, &fmt
);
706 memcpy(info
->fmt
.rgxTabs
, fmt
.rgxTabs
,
707 fmt
.cTabCount
* sizeof(fmt
.rgxTabs
[0]));
708 info
->fmt
.cTabCount
= fmt
.cTabCount
;
709 info
->fmt
.dwMask
|= PFM_TABSTOPS
;
711 if (info
->fmt
.cTabCount
< MAX_TAB_STOPS
&& info
->rtfParam
< 0x1000000)
712 info
->fmt
.rgxTabs
[info
->fmt
.cTabCount
++] = info
->rtfParam
;
715 info
->fmt
.dwMask
|= PFM_KEEP
;
716 info
->fmt
.wEffects
|= PFE_KEEP
;
718 case rtfNoWidowControl
:
719 info
->fmt
.dwMask
|= PFM_NOWIDOWCONTROL
;
720 info
->fmt
.wEffects
|= PFE_NOWIDOWCONTROL
;
723 info
->fmt
.dwMask
|= PFM_KEEPNEXT
;
724 info
->fmt
.wEffects
|= PFE_KEEPNEXT
;
727 info
->fmt
.dwMask
|= PFM_SPACEAFTER
;
728 info
->fmt
.dySpaceAfter
= info
->rtfParam
;
731 info
->fmt
.dwMask
|= PFM_SPACEBEFORE
;
732 info
->fmt
.dySpaceBefore
= info
->rtfParam
;
734 case rtfSpaceBetween
:
735 info
->fmt
.dwMask
|= PFM_LINESPACING
;
736 if ((int)info
->rtfParam
> 0)
738 info
->fmt
.dyLineSpacing
= info
->rtfParam
;
739 info
->fmt
.bLineSpacingRule
= 3;
743 info
->fmt
.dyLineSpacing
= info
->rtfParam
;
744 info
->fmt
.bLineSpacingRule
= 4;
747 case rtfSpaceMultiply
:
748 info
->fmt
.dwMask
|= PFM_LINESPACING
;
749 info
->fmt
.dyLineSpacing
= info
->rtfParam
* 20;
750 info
->fmt
.bLineSpacingRule
= 5;
753 info
->fmt
.dwMask
|= PFM_NUMBERING
;
754 info
->fmt
.wNumbering
= PFN_BULLET
;
757 info
->fmt
.dwMask
|= PFM_NUMBERING
;
758 info
->fmt
.wNumbering
= 2; /* FIXME: MSDN says it's not used ?? */
761 info
->borderType
= RTFBorderParaLeft
;
762 info
->fmt
.wBorders
|= 1;
763 info
->fmt
.dwMask
|= PFM_BORDER
;
766 info
->borderType
= RTFBorderParaRight
;
767 info
->fmt
.wBorders
|= 2;
768 info
->fmt
.dwMask
|= PFM_BORDER
;
771 info
->borderType
= RTFBorderParaTop
;
772 info
->fmt
.wBorders
|= 4;
773 info
->fmt
.dwMask
|= PFM_BORDER
;
775 case rtfBorderBottom
:
776 info
->borderType
= RTFBorderParaBottom
;
777 info
->fmt
.wBorders
|= 8;
778 info
->fmt
.dwMask
|= PFM_BORDER
;
780 case rtfBorderSingle
:
781 info
->fmt
.wBorders
&= ~0x700;
782 info
->fmt
.wBorders
|= 1 << 8;
783 info
->fmt
.dwMask
|= PFM_BORDER
;
786 info
->fmt
.wBorders
&= ~0x700;
787 info
->fmt
.wBorders
|= 2 << 8;
788 info
->fmt
.dwMask
|= PFM_BORDER
;
790 case rtfBorderShadow
:
791 info
->fmt
.wBorders
&= ~0x700;
792 info
->fmt
.wBorders
|= 10 << 8;
793 info
->fmt
.dwMask
|= PFM_BORDER
;
795 case rtfBorderDouble
:
796 info
->fmt
.wBorders
&= ~0x700;
797 info
->fmt
.wBorders
|= 7 << 8;
798 info
->fmt
.dwMask
|= PFM_BORDER
;
801 info
->fmt
.wBorders
&= ~0x700;
802 info
->fmt
.wBorders
|= 11 << 8;
803 info
->fmt
.dwMask
|= PFM_BORDER
;
807 int borderSide
= info
->borderType
& RTFBorderSideMask
;
808 RTFTable
*tableDef
= info
->tableDef
;
809 if ((info
->borderType
& RTFBorderTypeMask
) == RTFBorderTypeCell
)
812 if (!tableDef
|| tableDef
->numCellsDefined
>= MAX_TABLE_CELLS
)
814 border
= &tableDef
->cells
[tableDef
->numCellsDefined
].border
[borderSide
];
815 border
->width
= info
->rtfParam
;
818 info
->fmt
.wBorderWidth
= info
->rtfParam
;
819 info
->fmt
.dwMask
|= PFM_BORDER
;
823 info
->fmt
.wBorderSpace
= info
->rtfParam
;
824 info
->fmt
.dwMask
|= PFM_BORDER
;
828 RTFTable
*tableDef
= info
->tableDef
;
829 int borderSide
= info
->borderType
& RTFBorderSideMask
;
830 int borderType
= info
->borderType
& RTFBorderTypeMask
;
833 case RTFBorderTypePara
:
834 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
836 /* v1.0 - 3.0 treat paragraph and row borders the same. */
837 case RTFBorderTypeRow
:
839 tableDef
->border
[borderSide
].color
= info
->rtfParam
;
842 case RTFBorderTypeCell
:
843 if (tableDef
&& tableDef
->numCellsDefined
< MAX_TABLE_CELLS
) {
844 tableDef
->cells
[tableDef
->numCellsDefined
].border
[borderSide
].color
= info
->rtfParam
;
851 info
->fmt
.dwMask
|= PFM_RTLPARA
;
852 info
->fmt
.wEffects
|= PFE_RTLPARA
;
855 info
->fmt
.dwMask
|= PFM_RTLPARA
;
856 info
->fmt
.wEffects
&= ~PFE_RTLPARA
;
861 void ME_RTFTblAttrHook(RTF_Info
*info
)
863 switch (info
->rtfMinor
)
867 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
868 info
->borderType
= 0; /* Not sure */
869 else /* v1.0 - 3.0 */
870 info
->borderType
= RTFBorderRowTop
;
871 if (!info
->tableDef
) {
872 info
->tableDef
= ME_MakeTableDef(info
->editor
);
874 ME_InitTableDef(info
->editor
, info
->tableDef
);
883 info
->tableDef
= ME_MakeTableDef(info
->editor
);
885 cellNum
= info
->tableDef
->numCellsDefined
;
886 if (cellNum
>= MAX_TABLE_CELLS
)
888 info
->tableDef
->cells
[cellNum
].rightBoundary
= info
->rtfParam
;
889 if (cellNum
< MAX_TAB_STOPS
)
891 /* Tab stops were used to store cell positions before v4.1 but v4.1
892 * still seems to set the tabstops without using them. */
893 PARAFORMAT2
*fmt
= &info
->editor
->pCursors
[0].para
->fmt
;
894 fmt
->rgxTabs
[cellNum
] &= ~0x00FFFFFF;
895 fmt
->rgxTabs
[cellNum
] |= 0x00FFFFFF & info
->rtfParam
;
897 info
->tableDef
->numCellsDefined
++;
901 info
->borderType
= RTFBorderRowTop
;
904 info
->borderType
= RTFBorderRowLeft
;
906 case rtfRowBordBottom
:
907 info
->borderType
= RTFBorderRowBottom
;
909 case rtfRowBordRight
:
910 info
->borderType
= RTFBorderRowRight
;
913 info
->borderType
= RTFBorderCellTop
;
915 case rtfCellBordLeft
:
916 info
->borderType
= RTFBorderCellLeft
;
918 case rtfCellBordBottom
:
919 info
->borderType
= RTFBorderCellBottom
;
921 case rtfCellBordRight
:
922 info
->borderType
= RTFBorderCellRight
;
926 info
->tableDef
->gapH
= info
->rtfParam
;
930 info
->tableDef
->leftEdge
= info
->rtfParam
;
935 void ME_RTFSpecialCharHook(RTF_Info
*info
)
937 RTFTable
*tableDef
= info
->tableDef
;
938 switch (info
->rtfMinor
)
941 if (info
->editor
->bEmulateVersion10
) /* v1.0 - v3.0 */
943 /* else fall through since v4.1 treats rtfNestCell and rtfCell the same */
947 RTFFlushOutputBuffer(info
);
948 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
950 if (tableDef
->row_start
)
952 if (!info
->nestingLevel
&& tableDef
->row_start
->nFlags
& MEPF_ROWEND
)
954 ME_Paragraph
*para
= para_next( tableDef
->row_start
);
955 tableDef
->row_start
= table_insert_row_start_at_para( info
->editor
, para
);
956 info
->nestingLevel
= 1;
958 table_insert_cell( info
->editor
, info
->editor
->pCursors
);
961 else /* v1.0 - v3.0 */
963 ME_Paragraph
*para
= info
->editor
->pCursors
[0].para
;
965 if (para_in_table( para
) && tableDef
->numCellsInserted
< tableDef
->numCellsDefined
)
968 ME_InsertTextFromCursor(info
->editor
, 0, &tab
, 1, info
->style
);
969 tableDef
->numCellsInserted
++;
974 if (info
->editor
->bEmulateVersion10
) /* v1.0 - v3.0 */
976 /* else fall through since v4.1 treats rtfNestRow and rtfRow the same */
986 RTFFlushOutputBuffer(info
);
987 if (!info
->editor
->bEmulateVersion10
) /* v4.1 */
989 if (!tableDef
->row_start
) break;
990 if (!info
->nestingLevel
&& tableDef
->row_start
->nFlags
& MEPF_ROWEND
)
992 para
= para_next( tableDef
->row_start
);
993 tableDef
->row_start
= table_insert_row_start_at_para( info
->editor
, para
);
994 info
->nestingLevel
++;
996 para
= tableDef
->row_start
;
997 cell
= table_row_first_cell( para
);
998 assert( cell
&& !cell_prev( cell
) );
999 if (tableDef
->numCellsDefined
< 1)
1001 /* 2000 twips appears to be the cell size that native richedit uses
1002 * when no cell sizes are specified. */
1003 const int default_size
= 2000;
1004 int right_boundary
= default_size
;
1005 cell
->nRightBoundary
= right_boundary
;
1006 while (cell_next( cell
))
1008 cell
= cell_next( cell
);
1009 right_boundary
+= default_size
;
1010 cell
->nRightBoundary
= right_boundary
;
1012 para
= table_insert_cell( info
->editor
, info
->editor
->pCursors
);
1013 cell
= para_cell( para
);
1014 cell
->nRightBoundary
= right_boundary
;
1018 for (i
= 0; i
< tableDef
->numCellsDefined
; i
++)
1020 RTFCell
*cellDef
= &tableDef
->cells
[i
];
1021 cell
->nRightBoundary
= cellDef
->rightBoundary
;
1022 ME_ApplyBorderProperties( info
, &cell
->border
, cellDef
->border
);
1023 cell
= cell_next( cell
);
1026 para
= table_insert_cell( info
->editor
, info
->editor
->pCursors
);
1027 cell
= para_cell( para
);
1030 /* Cell for table row delimiter is empty */
1031 cell
->nRightBoundary
= tableDef
->cells
[i
- 1].rightBoundary
;
1034 run
= para_first_run( cell_first_para( cell
) );
1035 if (info
->editor
->pCursors
[0].run
!= run
|| info
->editor
->pCursors
[0].nOffset
)
1038 /* Delete inserted cells that aren't defined. */
1039 info
->editor
->pCursors
[1].run
= run
;
1040 info
->editor
->pCursors
[1].para
= run
->para
;
1041 info
->editor
->pCursors
[1].nOffset
= 0;
1042 nOfs
= ME_GetCursorOfs(&info
->editor
->pCursors
[1]);
1043 nChars
= ME_GetCursorOfs(&info
->editor
->pCursors
[0]) - nOfs
;
1044 ME_InternalDeleteText(info
->editor
, &info
->editor
->pCursors
[1],
1048 para
= table_insert_row_end( info
->editor
, info
->editor
->pCursors
);
1049 para
->fmt
.dxOffset
= abs(info
->tableDef
->gapH
);
1050 para
->fmt
.dxStartIndent
= info
->tableDef
->leftEdge
;
1051 ME_ApplyBorderProperties( info
, ¶
->border
, tableDef
->border
);
1052 info
->nestingLevel
--;
1053 if (!info
->nestingLevel
)
1055 if (info
->canInheritInTbl
) tableDef
->row_start
= para
;
1058 while (info
->tableDef
)
1060 tableDef
= info
->tableDef
;
1061 info
->tableDef
= tableDef
->parent
;
1068 info
->tableDef
= tableDef
->parent
;
1072 else /* v1.0 - v3.0 */
1074 para
= info
->editor
->pCursors
[0].para
;
1075 para
->fmt
.dxOffset
= info
->tableDef
->gapH
;
1076 para
->fmt
.dxStartIndent
= info
->tableDef
->leftEdge
;
1078 ME_ApplyBorderProperties( info
, ¶
->border
, tableDef
->border
);
1079 while (tableDef
->numCellsInserted
< tableDef
->numCellsDefined
)
1082 ME_InsertTextFromCursor(info
->editor
, 0, &tab
, 1, info
->style
);
1083 tableDef
->numCellsInserted
++;
1085 para
->fmt
.cTabCount
= min(tableDef
->numCellsDefined
, MAX_TAB_STOPS
);
1086 if (!tableDef
->numCellsDefined
) para
->fmt
.wEffects
&= ~PFE_TABLE
;
1087 ME_InsertTextFromCursor(info
->editor
, 0, L
"\r", 1, info
->style
);
1088 tableDef
->numCellsInserted
= 0;
1094 if (info
->editor
->bEmulateVersion10
) /* v1.0 - 3.0 */
1098 RTFFlushOutputBuffer(info
);
1099 para
= info
->editor
->pCursors
[0].para
;
1100 if (para_in_table( para
))
1102 /* rtfPar is treated like a space within a table. */
1103 info
->rtfClass
= rtfText
;
1104 info
->rtfMajor
= ' ';
1106 else if (info
->rtfMinor
== rtfPar
&& tableDef
)
1107 tableDef
->numCellsInserted
= 0;
1113 static HRESULT
insert_static_object(ME_TextEditor
*editor
, HENHMETAFILE hemf
, HBITMAP hbmp
,
1116 LPOLEOBJECT lpObject
= NULL
;
1117 LPSTORAGE lpStorage
= NULL
;
1118 LPOLECLIENTSITE lpClientSite
= NULL
;
1119 LPDATAOBJECT lpDataObject
= NULL
;
1120 LPOLECACHE lpOleCache
= NULL
;
1124 HRESULT hr
= E_FAIL
;
1129 stgm
.tymed
= TYMED_ENHMF
;
1130 stgm
.hEnhMetaFile
= hemf
;
1131 fm
.cfFormat
= CF_ENHMETAFILE
;
1135 stgm
.tymed
= TYMED_GDI
;
1136 stgm
.hBitmap
= hbmp
;
1137 fm
.cfFormat
= CF_BITMAP
;
1141 stgm
.pUnkForRelease
= NULL
;
1144 fm
.dwAspect
= DVASPECT_CONTENT
;
1146 fm
.tymed
= stgm
.tymed
;
1148 if (OleCreateDefaultHandler(&CLSID_NULL
, NULL
, &IID_IOleObject
, (void**)&lpObject
) == S_OK
&&
1149 IRichEditOle_GetClientSite(editor
->richole
, &lpClientSite
) == S_OK
&&
1150 IOleObject_SetClientSite(lpObject
, lpClientSite
) == S_OK
&&
1151 IOleObject_GetUserClassID(lpObject
, &clsid
) == S_OK
&&
1152 IOleObject_QueryInterface(lpObject
, &IID_IOleCache
, (void**)&lpOleCache
) == S_OK
&&
1153 IOleCache_Cache(lpOleCache
, &fm
, 0, &conn
) == S_OK
&&
1154 IOleObject_QueryInterface(lpObject
, &IID_IDataObject
, (void**)&lpDataObject
) == S_OK
&&
1155 IDataObject_SetData(lpDataObject
, &fm
, &stgm
, TRUE
) == S_OK
)
1159 reobject
.cbStruct
= sizeof(reobject
);
1160 reobject
.cp
= REO_CP_SELECTION
;
1161 reobject
.clsid
= clsid
;
1162 reobject
.poleobj
= lpObject
;
1163 reobject
.pstg
= lpStorage
;
1164 reobject
.polesite
= lpClientSite
;
1165 /* convert from twips to .01 mm */
1166 reobject
.sizel
.cx
= MulDiv(sz
->cx
, 254, 144);
1167 reobject
.sizel
.cy
= MulDiv(sz
->cy
, 254, 144);
1168 reobject
.dvaspect
= DVASPECT_CONTENT
;
1169 reobject
.dwFlags
= 0; /* FIXME */
1170 reobject
.dwUser
= 0;
1172 hr
= editor_insert_oleobj(editor
, &reobject
);
1175 if (lpObject
) IOleObject_Release(lpObject
);
1176 if (lpClientSite
) IOleClientSite_Release(lpClientSite
);
1177 if (lpStorage
) IStorage_Release(lpStorage
);
1178 if (lpDataObject
) IDataObject_Release(lpDataObject
);
1179 if (lpOleCache
) IOleCache_Release(lpOleCache
);
1184 static void ME_RTFReadShpPictGroup( RTF_Info
*info
)
1192 if (info
->rtfClass
== rtfEOF
) return;
1193 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1195 if (--level
== 0) break;
1197 else if (RTFCheckCM( info
, rtfGroup
, rtfBeginGroup
))
1203 RTFRouteToken( info
);
1204 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1209 RTFRouteToken( info
); /* feed "}" back to router */
1213 static DWORD
read_hex_data( RTF_Info
*info
, BYTE
**out
)
1215 DWORD read
= 0, size
= 1024;
1221 if (info
->rtfClass
!= rtfText
)
1223 ERR("Called with incorrect token\n");
1230 val
= info
->rtfMajor
;
1231 for (flip
= TRUE
;; flip
= !flip
)
1233 RTFGetToken( info
);
1234 if (info
->rtfClass
== rtfEOF
)
1239 if (info
->rtfClass
!= rtfText
) break;
1245 buf
= realloc(buf
, size
);
1248 buf
[read
++] = RTFCharToHex(val
) * 16 + RTFCharToHex(info
->rtfMajor
);
1251 val
= info
->rtfMajor
;
1253 if (flip
) FIXME("wrong hex string\n");
1259 static void ME_RTFReadPictGroup(RTF_Info
*info
)
1262 BYTE
*buffer
= NULL
;
1267 enum gfxkind
{gfx_unknown
= 0, gfx_enhmetafile
, gfx_metafile
, gfx_dib
} gfx
= gfx_unknown
;
1275 RTFGetToken( info
);
1277 if (info
->rtfClass
== rtfText
)
1282 size
= read_hex_data( info
, &buffer
);
1286 RTFSkipGroup( info
);
1288 } /* We potentially have a new token so fall through. */
1290 if (info
->rtfClass
== rtfEOF
) return;
1292 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1294 if (--level
== 0) break;
1297 if (RTFCheckCM( info
, rtfGroup
, rtfBeginGroup
))
1302 if (!RTFCheckCM( info
, rtfControl
, rtfPictAttr
))
1304 RTFRouteToken( info
);
1305 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1310 if (RTFCheckMM( info
, rtfPictAttr
, rtfWinMetafile
))
1312 mfp
.mm
= info
->rtfParam
;
1315 else if (RTFCheckMM( info
, rtfPictAttr
, rtfDevIndBitmap
))
1317 if (info
->rtfParam
!= 0) FIXME("dibitmap should be 0 (%d)\n", info
->rtfParam
);
1320 else if (RTFCheckMM( info
, rtfPictAttr
, rtfEmfBlip
))
1321 gfx
= gfx_enhmetafile
;
1322 else if (RTFCheckMM( info
, rtfPictAttr
, rtfPicWid
))
1323 mfp
.xExt
= info
->rtfParam
;
1324 else if (RTFCheckMM( info
, rtfPictAttr
, rtfPicHt
))
1325 mfp
.yExt
= info
->rtfParam
;
1326 else if (RTFCheckMM( info
, rtfPictAttr
, rtfPicGoalWid
))
1327 sz
.cx
= info
->rtfParam
;
1328 else if (RTFCheckMM( info
, rtfPictAttr
, rtfPicGoalHt
))
1329 sz
.cy
= info
->rtfParam
;
1331 FIXME("Non supported attribute: %d %d %d\n", info
->rtfClass
, info
->rtfMajor
, info
->rtfMinor
);
1338 case gfx_enhmetafile
:
1339 if ((hemf
= SetEnhMetaFileBits( size
, buffer
)))
1340 insert_static_object( info
->editor
, hemf
, NULL
, &sz
);
1343 if ((hemf
= SetWinMetaFileBits( size
, buffer
, NULL
, &mfp
)))
1344 insert_static_object( info
->editor
, hemf
, NULL
, &sz
);
1348 BITMAPINFO
*bi
= (BITMAPINFO
*)buffer
;
1350 unsigned nc
= bi
->bmiHeader
.biClrUsed
;
1352 /* not quite right, especially for bitfields type of compression */
1353 if (!nc
&& bi
->bmiHeader
.biBitCount
<= 8)
1354 nc
= 1 << bi
->bmiHeader
.biBitCount
;
1355 if ((hbmp
= CreateDIBitmap( hdc
, &bi
->bmiHeader
,
1356 CBM_INIT
, (char*)(bi
+ 1) + nc
* sizeof(RGBQUAD
),
1357 bi
, DIB_RGB_COLORS
)) )
1358 insert_static_object( info
->editor
, NULL
, hbmp
, &sz
);
1359 ReleaseDC( 0, hdc
);
1367 RTFRouteToken( info
); /* feed "}" back to router */
1371 /* for now, lookup the \result part and use it, whatever the object */
1372 static void ME_RTFReadObjectGroup(RTF_Info
*info
)
1377 if (info
->rtfClass
== rtfEOF
)
1379 if (RTFCheckCM(info
, rtfGroup
, rtfEndGroup
))
1381 if (RTFCheckCM(info
, rtfGroup
, rtfBeginGroup
))
1384 if (info
->rtfClass
== rtfEOF
)
1386 if (RTFCheckCMM(info
, rtfControl
, rtfDestination
, rtfObjResult
))
1390 while (RTFGetToken (info
) != rtfEOF
)
1392 if (info
->rtfClass
== rtfGroup
)
1394 if (info
->rtfMajor
== rtfBeginGroup
) level
++;
1395 else if (info
->rtfMajor
== rtfEndGroup
&& --level
< 0) break;
1397 RTFRouteToken(info
);
1400 else RTFSkipGroup(info
);
1403 if (!RTFCheckCM (info
, rtfControl
, rtfObjAttr
))
1405 FIXME("Non supported attribute: %d %d %d\n", info
->rtfClass
, info
->rtfMajor
, info
->rtfMinor
);
1409 RTFRouteToken(info
); /* feed "}" back to router */
1412 static void ME_RTFReadParnumGroup( RTF_Info
*info
)
1414 int level
= 1, type
= -1;
1415 WORD indent
= 0, start
= 1;
1416 WCHAR txt_before
= 0, txt_after
= 0;
1420 RTFGetToken( info
);
1422 if (RTFCheckCMM( info
, rtfControl
, rtfDestination
, rtfParNumTextBefore
) ||
1423 RTFCheckCMM( info
, rtfControl
, rtfDestination
, rtfParNumTextAfter
))
1425 int loc
= info
->rtfMinor
;
1427 RTFGetToken( info
);
1428 if (info
->rtfClass
== rtfText
)
1430 if (loc
== rtfParNumTextBefore
)
1431 txt_before
= info
->rtfMajor
;
1433 txt_after
= info
->rtfMajor
;
1436 /* falling through to catch EOFs and group level changes */
1439 if (info
->rtfClass
== rtfEOF
)
1442 if (RTFCheckCM( info
, rtfGroup
, rtfEndGroup
))
1444 if (--level
== 0) break;
1448 if (RTFCheckCM( info
, rtfGroup
, rtfBeginGroup
))
1454 /* Ignore non para-attr */
1455 if (!RTFCheckCM( info
, rtfControl
, rtfParAttr
))
1458 switch (info
->rtfMinor
)
1460 case rtfParLevel
: /* Para level is ignored */
1467 case rtfParNumDecimal
:
1470 case rtfParNumULetter
:
1471 type
= PFN_UCLETTER
;
1473 case rtfParNumURoman
:
1476 case rtfParNumLLetter
:
1477 type
= PFN_LCLETTER
;
1479 case rtfParNumLRoman
:
1483 case rtfParNumIndent
:
1484 indent
= info
->rtfParam
;
1486 case rtfParNumStartAt
:
1487 start
= info
->rtfParam
;
1494 info
->fmt
.dwMask
|= (PFM_NUMBERING
| PFM_NUMBERINGSTART
| PFM_NUMBERINGSTYLE
| PFM_NUMBERINGTAB
);
1495 info
->fmt
.wNumbering
= type
;
1496 info
->fmt
.wNumberingStart
= start
;
1497 info
->fmt
.wNumberingStyle
= PFNS_PAREN
;
1498 if (type
!= PFN_BULLET
)
1500 if (txt_before
== 0 && txt_after
== 0)
1501 info
->fmt
.wNumberingStyle
= PFNS_PLAIN
;
1502 else if (txt_after
== '.')
1503 info
->fmt
.wNumberingStyle
= PFNS_PERIOD
;
1504 else if (txt_before
== '(' && txt_after
== ')')
1505 info
->fmt
.wNumberingStyle
= PFNS_PARENS
;
1507 info
->fmt
.wNumberingTab
= indent
;
1510 TRACE("type %d indent %d start %d txt before %04x txt after %04x\n",
1511 type
, indent
, start
, txt_before
, txt_after
);
1513 RTFRouteToken( info
); /* feed "}" back to router */
1516 static void ME_RTFReadHook(RTF_Info
*info
)
1518 switch(info
->rtfClass
)
1521 switch(info
->rtfMajor
)
1524 if (info
->stackTop
< maxStack
) {
1525 info
->stack
[info
->stackTop
].style
= info
->style
;
1526 ME_AddRefStyle(info
->style
);
1527 info
->stack
[info
->stackTop
].codePage
= info
->codePage
;
1528 info
->stack
[info
->stackTop
].unicodeLength
= info
->unicodeLength
;
1531 info
->styleChanged
= FALSE
;
1535 RTFFlushOutputBuffer(info
);
1537 if (info
->stackTop
<= 0)
1538 info
->rtfClass
= rtfEOF
;
1539 if (info
->stackTop
< 0)
1542 ME_ReleaseStyle(info
->style
);
1543 info
->style
= info
->stack
[info
->stackTop
].style
;
1544 info
->codePage
= info
->stack
[info
->stackTop
].codePage
;
1545 info
->unicodeLength
= info
->stack
[info
->stackTop
].unicodeLength
;
1554 ME_StreamInFill(ME_InStream
*stream
)
1556 stream
->editstream
->dwError
= stream
->editstream
->pfnCallback(stream
->editstream
->dwCookie
,
1557 (BYTE
*)stream
->buffer
,
1558 sizeof(stream
->buffer
),
1559 (LONG
*)&stream
->dwSize
);
1563 static LRESULT
ME_StreamIn(ME_TextEditor
*editor
, DWORD format
, EDITSTREAM
*stream
, BOOL stripLastCR
)
1569 int nEventMask
= editor
->nEventMask
;
1570 ME_InStream inStream
;
1571 BOOL invalidRTF
= FALSE
;
1572 ME_Cursor
*selStart
, *selEnd
;
1573 LRESULT num_read
= 0; /* bytes read for SF_TEXT, non-control chars inserted for SF_RTF */
1575 TRACE("stream==%p editor==%p format==0x%lX\n", stream
, editor
, format
);
1576 editor
->nEventMask
= 0;
1578 ME_GetSelectionOfs(editor
, &from
, &to
);
1579 if (format
& SFF_SELECTION
&& editor
->mode
& TM_RICHTEXT
)
1581 ME_GetSelection(editor
, &selStart
, &selEnd
);
1582 style
= ME_GetSelectionInsertStyle(editor
);
1584 ME_InternalDeleteText(editor
, selStart
, to
- from
, FALSE
);
1586 /* Don't insert text at the end of the table row */
1587 if (!editor
->bEmulateVersion10
) /* v4.1 */
1589 ME_Paragraph
*para
= editor
->pCursors
->para
;
1590 if (para
->nFlags
& (MEPF_ROWSTART
| MEPF_ROWEND
))
1592 para
= para_next( para
);
1593 editor
->pCursors
[0].para
= para
;
1594 editor
->pCursors
[0].run
= para_first_run( para
);
1595 editor
->pCursors
[0].nOffset
= 0;
1597 editor
->pCursors
[1] = editor
->pCursors
[0];
1599 else /* v1.0 - 3.0 */
1601 if (editor
->pCursors
[0].run
->nFlags
& MERF_ENDPARA
&&
1602 para_in_table( editor
->pCursors
[0].para
))
1608 style
= editor
->pBuffer
->pDefaultStyle
;
1609 ME_AddRefStyle(style
);
1610 if (format
& SFF_SELECTION
)
1612 ME_GetSelection(editor
, &selStart
, &selEnd
);
1613 ME_InternalDeleteText(editor
, selStart
, to
- from
, FALSE
);
1617 set_selection_cursors(editor
, 0, 0);
1618 ME_InternalDeleteText(editor
, &editor
->pCursors
[1],
1619 ME_GetTextLength(editor
), FALSE
);
1622 ME_ClearTempStyle(editor
);
1623 editor_set_default_para_fmt( editor
, &editor
->pCursors
[0].para
->fmt
);
1627 /* Back up undo mode to a local variable */
1628 nUndoMode
= editor
->nUndoMode
;
1630 /* Only create an undo if SFF_SELECTION is set */
1631 if (!(format
& SFF_SELECTION
))
1632 editor
->nUndoMode
= umIgnore
;
1634 inStream
.editstream
= stream
;
1635 inStream
.editstream
->dwError
= 0;
1636 inStream
.dwSize
= 0;
1637 inStream
.dwUsed
= 0;
1639 if (format
& SF_RTF
)
1641 /* Check if it's really RTF, and if it is not, use plain text */
1642 ME_StreamInFill(&inStream
);
1643 if (!inStream
.editstream
->dwError
)
1645 if ((!editor
->bEmulateVersion10
&& strncmp(inStream
.buffer
, "{\\rtf", 5) && strncmp(inStream
.buffer
, "{\\urtf", 6))
1646 || (editor
->bEmulateVersion10
&& *inStream
.buffer
!= '{'))
1649 inStream
.editstream
->dwError
= -16;
1654 if (!invalidRTF
&& !inStream
.editstream
->dwError
)
1657 from
= ME_GetCursorOfs(&editor
->pCursors
[0]);
1658 if (format
& SF_RTF
) {
1660 /* setup the RTF parser */
1661 memset(&parser
, 0, sizeof parser
);
1662 RTFSetEditStream(&parser
, &inStream
);
1663 parser
.rtfFormat
= format
&(SF_TEXT
|SF_RTF
);
1664 parser
.editor
= editor
;
1665 parser
.style
= style
;
1666 WriterInit(&parser
);
1668 RTFSetReadHook(&parser
, ME_RTFReadHook
);
1669 RTFSetDestinationCallback(&parser
, rtfShpPict
, ME_RTFReadShpPictGroup
);
1670 RTFSetDestinationCallback(&parser
, rtfPict
, ME_RTFReadPictGroup
);
1671 RTFSetDestinationCallback(&parser
, rtfObject
, ME_RTFReadObjectGroup
);
1672 RTFSetDestinationCallback(&parser
, rtfParNumbering
, ME_RTFReadParnumGroup
);
1673 if (!parser
.editor
->bEmulateVersion10
) /* v4.1 */
1675 RTFSetDestinationCallback(&parser
, rtfNoNestTables
, RTFSkipGroup
);
1676 RTFSetDestinationCallback(&parser
, rtfNestTableProps
, RTFReadGroup
);
1680 /* do the parsing */
1682 RTFFlushOutputBuffer(&parser
);
1683 if (!editor
->bEmulateVersion10
) /* v4.1 */
1685 if (parser
.tableDef
&& parser
.tableDef
->row_start
&&
1686 (parser
.nestingLevel
> 0 || parser
.canInheritInTbl
))
1688 /* Delete any incomplete table row at the end of the rich text. */
1692 parser
.rtfMinor
= rtfRow
;
1693 /* Complete the table row before deleting it.
1694 * By doing it this way we will have the current paragraph format set
1695 * properly to reflect that is not in the complete table, and undo items
1696 * will be added for this change to the current paragraph format. */
1697 if (parser
.nestingLevel
> 0)
1699 while (parser
.nestingLevel
> 1)
1700 ME_RTFSpecialCharHook(&parser
); /* Decrements nestingLevel */
1701 para
= parser
.tableDef
->row_start
;
1702 ME_RTFSpecialCharHook(&parser
);
1706 para
= parser
.tableDef
->row_start
;
1707 ME_RTFSpecialCharHook(&parser
);
1708 assert( para
->nFlags
& MEPF_ROWEND
);
1709 para
= para_next( para
);
1712 editor
->pCursors
[1].para
= para
;
1713 editor
->pCursors
[1].run
= para_first_run( para
);
1714 editor
->pCursors
[1].nOffset
= 0;
1715 nOfs
= ME_GetCursorOfs(&editor
->pCursors
[1]);
1716 nChars
= ME_GetCursorOfs(&editor
->pCursors
[0]) - nOfs
;
1717 ME_InternalDeleteText(editor
, &editor
->pCursors
[1], nChars
, TRUE
);
1718 if (parser
.tableDef
) parser
.tableDef
->row_start
= NULL
;
1721 RTFDestroy(&parser
);
1723 if (parser
.stackTop
> 0)
1725 while (--parser
.stackTop
>= 0)
1727 ME_ReleaseStyle(parser
.style
);
1728 parser
.style
= parser
.stack
[parser
.stackTop
].style
;
1730 if (!inStream
.editstream
->dwError
)
1731 inStream
.editstream
->dwError
= HRESULT_FROM_WIN32(ERROR_HANDLE_EOF
);
1734 /* Remove last line break, as mandated by tests. This is not affected by
1735 CR/LF counters, since RTF streaming presents only \para tokens, which
1736 are converted according to the standard rules: \r for 2.0, \r\n for 1.0
1738 if (stripLastCR
&& !(format
& SFF_SELECTION
)) {
1740 ME_GetSelection(editor
, &selStart
, &selEnd
);
1741 newto
= ME_GetCursorOfs(selEnd
);
1742 if (newto
> to
+ (editor
->bEmulateVersion10
? 1 : 0)) {
1743 WCHAR lastchar
[3] = {'\0', '\0'};
1744 int linebreakSize
= editor
->bEmulateVersion10
? 2 : 1;
1745 ME_Cursor linebreakCursor
= *selEnd
, lastcharCursor
= *selEnd
;
1748 /* Set the final eop to the char fmt of the last char */
1749 cf
.cbSize
= sizeof(cf
);
1750 cf
.dwMask
= CFM_ALL2
;
1751 ME_MoveCursorChars(editor
, &lastcharCursor
, -1, FALSE
);
1752 ME_GetCharFormat(editor
, &lastcharCursor
, &linebreakCursor
, &cf
);
1753 set_selection_cursors(editor
, newto
, -1);
1754 ME_SetSelectionCharFormat(editor
, &cf
);
1755 set_selection_cursors(editor
, newto
, newto
);
1757 ME_MoveCursorChars(editor
, &linebreakCursor
, -linebreakSize
, FALSE
);
1758 ME_GetTextW(editor
, lastchar
, 2, &linebreakCursor
, linebreakSize
, FALSE
, FALSE
);
1759 if (lastchar
[0] == '\r' && (lastchar
[1] == '\n' || lastchar
[1] == '\0')) {
1760 ME_InternalDeleteText(editor
, &linebreakCursor
, linebreakSize
, FALSE
);
1764 to
= ME_GetCursorOfs(&editor
->pCursors
[0]);
1765 num_read
= to
- from
;
1767 style
= parser
.style
;
1769 else if (format
& SF_TEXT
)
1771 num_read
= ME_StreamInText(editor
, format
, &inStream
, style
);
1772 to
= ME_GetCursorOfs(&editor
->pCursors
[0]);
1775 ERR("EM_STREAMIN without SF_TEXT or SF_RTF\n");
1776 /* put the cursor at the top */
1777 if (!(format
& SFF_SELECTION
))
1778 set_selection_cursors(editor
, 0, 0);
1779 cursor_from_char_ofs( editor
, from
, &start
);
1780 ME_UpdateLinkAttribute(editor
, &start
, to
- from
);
1783 /* Restore saved undo mode */
1784 editor
->nUndoMode
= nUndoMode
;
1786 /* even if we didn't add an undo, we need to commit anything on the stack */
1787 ME_CommitUndo(editor
);
1789 /* If SFF_SELECTION isn't set, delete any undos from before we started too */
1790 if (!(format
& SFF_SELECTION
))
1791 ME_EmptyUndoStack(editor
);
1793 ME_ReleaseStyle(style
);
1794 editor
->nEventMask
= nEventMask
;
1795 ME_UpdateRepaint(editor
, FALSE
);
1796 if (!(format
& SFF_SELECTION
)) {
1797 ME_ClearTempStyle(editor
);
1799 ME_SendSelChange(editor
);
1800 ME_SendRequestResize(editor
, FALSE
);
1806 typedef struct tagME_RTFStringStreamStruct
1811 } ME_RTFStringStreamStruct
;
1813 static DWORD CALLBACK
ME_ReadFromRTFString(DWORD_PTR dwCookie
, LPBYTE lpBuff
, LONG cb
, LONG
*pcb
)
1815 ME_RTFStringStreamStruct
*pStruct
= (ME_RTFStringStreamStruct
*)dwCookie
;
1818 count
= min(cb
, pStruct
->length
- pStruct
->pos
);
1819 memmove(lpBuff
, pStruct
->string
+ pStruct
->pos
, count
);
1820 pStruct
->pos
+= count
;
1826 ME_StreamInRTFString(ME_TextEditor
*editor
, BOOL selection
, char *string
)
1829 ME_RTFStringStreamStruct data
;
1831 data
.string
= string
;
1832 data
.length
= strlen(string
);
1834 es
.dwCookie
= (DWORD_PTR
)&data
;
1835 es
.pfnCallback
= ME_ReadFromRTFString
;
1836 ME_StreamIn(editor
, SF_RTF
| (selection
? SFF_SELECTION
: 0), &es
, TRUE
);
1841 ME_FindText(ME_TextEditor
*editor
, DWORD flags
, const CHARRANGE
*chrg
, const WCHAR
*text
, CHARRANGE
*chrgText
)
1843 const int nLen
= lstrlenW(text
);
1844 const int nTextLen
= ME_GetTextLength(editor
);
1847 WCHAR wLastChar
= ' ';
1849 TRACE("flags==0x%08lx, chrg->cpMin==%ld, chrg->cpMax==%ld text==%s\n",
1850 flags
, chrg
->cpMin
, chrg
->cpMax
, debugstr_w(text
));
1852 if (flags
& ~(FR_DOWN
| FR_MATCHCASE
| FR_WHOLEWORD
))
1853 FIXME("Flags 0x%08lx not implemented\n",
1854 flags
& ~(FR_DOWN
| FR_MATCHCASE
| FR_WHOLEWORD
));
1857 if (chrg
->cpMax
== -1)
1860 nMax
= chrg
->cpMax
> nTextLen
? nTextLen
: chrg
->cpMax
;
1862 /* In 1.0 emulation, if cpMax reaches end of text, add the FR_DOWN flag */
1863 if (editor
->bEmulateVersion10
&& nMax
== nTextLen
)
1868 /* In 1.0 emulation, cpMin must always be no greater than cpMax */
1869 if (editor
->bEmulateVersion10
&& nMax
< nMin
)
1873 chrgText
->cpMin
= -1;
1874 chrgText
->cpMax
= -1;
1879 /* when searching up, if cpMin < cpMax, then instead of searching
1880 * on [cpMin,cpMax], we search on [0,cpMin], otherwise, search on
1881 * [cpMax, cpMin]. The exception is when cpMax is -1, in which
1882 * case, it is always bigger than cpMin.
1884 if (!editor
->bEmulateVersion10
&& !(flags
& FR_DOWN
))
1888 nMax
= nMin
> nTextLen
? nTextLen
: nMin
;
1889 if (nMin
< nSwap
|| chrg
->cpMax
== -1)
1895 if (!nLen
|| nMin
< 0 || nMax
< 0 || nMax
< nMin
)
1898 chrgText
->cpMin
= chrgText
->cpMax
= -1;
1902 if (flags
& FR_DOWN
) /* Forward search */
1904 /* If possible, find the character before where the search starts */
1905 if ((flags
& FR_WHOLEWORD
) && nMin
)
1907 cursor_from_char_ofs( editor
, nMin
- 1, &cursor
);
1908 wLastChar
= *get_text( cursor
.run
, cursor
.nOffset
);
1909 ME_MoveCursorChars(editor
, &cursor
, 1, FALSE
);
1911 else cursor_from_char_ofs( editor
, nMin
, &cursor
);
1913 while (cursor
.run
&& ME_GetCursorOfs(&cursor
) + nLen
<= nMax
)
1915 ME_Run
*run
= cursor
.run
;
1916 int nCurStart
= cursor
.nOffset
;
1919 while (run
&& ME_CharCompare( *get_text( run
, nCurStart
+ nMatched
), text
[nMatched
], (flags
& FR_MATCHCASE
)))
1921 if ((flags
& FR_WHOLEWORD
) && iswalnum(wLastChar
))
1925 if (nMatched
== nLen
)
1927 ME_Run
*next_run
= run
;
1928 int nNextStart
= nCurStart
;
1931 /* Check to see if next character is a whitespace */
1932 if (flags
& FR_WHOLEWORD
)
1934 if (nCurStart
+ nMatched
== run
->len
)
1936 next_run
= run_next_all_paras( run
);
1937 nNextStart
= -nMatched
;
1941 wNextChar
= *get_text( next_run
, nNextStart
+ nMatched
);
1945 if (iswalnum(wNextChar
))
1949 cursor
.nOffset
+= cursor
.para
->nCharOfs
+ cursor
.run
->nCharOfs
;
1952 chrgText
->cpMin
= cursor
.nOffset
;
1953 chrgText
->cpMax
= cursor
.nOffset
+ nLen
;
1955 TRACE("found at %d-%d\n", cursor
.nOffset
, cursor
.nOffset
+ nLen
);
1956 return cursor
.nOffset
;
1958 if (nCurStart
+ nMatched
== run
->len
)
1960 run
= run_next_all_paras( run
);
1961 nCurStart
= -nMatched
;
1965 wLastChar
= *get_text( run
, nCurStart
+ nMatched
);
1970 if (cursor
.nOffset
== cursor
.run
->len
)
1972 if (run_next_all_paras( cursor
.run
))
1974 cursor
.run
= run_next_all_paras( cursor
.run
);
1975 cursor
.para
= cursor
.run
->para
;
1983 else /* Backward search */
1985 /* If possible, find the character after where the search ends */
1986 if ((flags
& FR_WHOLEWORD
) && nMax
< nTextLen
- 1)
1988 cursor_from_char_ofs( editor
, nMax
+ 1, &cursor
);
1989 wLastChar
= *get_text( cursor
.run
, cursor
.nOffset
);
1990 ME_MoveCursorChars(editor
, &cursor
, -1, FALSE
);
1992 else cursor_from_char_ofs( editor
, nMax
, &cursor
);
1994 while (cursor
.run
&& ME_GetCursorOfs(&cursor
) - nLen
>= nMin
)
1996 ME_Run
*run
= cursor
.run
;
1997 ME_Paragraph
*para
= cursor
.para
;
1998 int nCurEnd
= cursor
.nOffset
;
2001 if (nCurEnd
== 0 && run_prev_all_paras( run
))
2003 run
= run_prev_all_paras( run
);
2008 while (run
&& ME_CharCompare( *get_text( run
, nCurEnd
- nMatched
- 1 ),
2009 text
[nLen
- nMatched
- 1], (flags
& FR_MATCHCASE
) ))
2011 if ((flags
& FR_WHOLEWORD
) && iswalnum(wLastChar
))
2015 if (nMatched
== nLen
)
2017 ME_Run
*prev_run
= run
;
2018 int nPrevEnd
= nCurEnd
;
2022 /* Check to see if previous character is a whitespace */
2023 if (flags
& FR_WHOLEWORD
)
2025 if (nPrevEnd
- nMatched
== 0)
2027 prev_run
= run_prev_all_paras( run
);
2028 if (prev_run
) nPrevEnd
= prev_run
->len
+ nMatched
;
2031 if (prev_run
) wPrevChar
= *get_text( prev_run
, nPrevEnd
- nMatched
- 1 );
2032 else wPrevChar
= ' ';
2034 if (iswalnum(wPrevChar
))
2038 nStart
= para
->nCharOfs
+ run
->nCharOfs
+ nCurEnd
- nMatched
;
2041 chrgText
->cpMin
= nStart
;
2042 chrgText
->cpMax
= nStart
+ nLen
;
2044 TRACE("found at %d-%d\n", nStart
, nStart
+ nLen
);
2047 if (nCurEnd
- nMatched
== 0)
2049 if (run_prev_all_paras( run
))
2051 run
= run_prev_all_paras( run
);
2054 /* Don't care about pCurItem becoming NULL here; it's already taken
2055 * care of in the exterior loop condition */
2056 nCurEnd
= run
->len
+ nMatched
;
2060 wLastChar
= *get_text( run
, nCurEnd
- nMatched
- 1 );
2065 if (cursor
.nOffset
< 0)
2067 if (run_prev_all_paras( cursor
.run
) )
2069 cursor
.run
= run_prev_all_paras( cursor
.run
);
2070 cursor
.para
= cursor
.run
->para
;
2071 cursor
.nOffset
= cursor
.run
->len
;
2078 TRACE("not found\n");
2080 chrgText
->cpMin
= chrgText
->cpMax
= -1;
2084 static int ME_GetTextEx(ME_TextEditor
*editor
, GETTEXTEX
*ex
, LPARAM pText
)
2089 if (!ex
->cb
|| !pText
) return 0;
2091 if (ex
->flags
& ~(GT_SELECTION
| GT_USECRLF
))
2092 FIXME("GETTEXTEX flags 0x%08lx not supported\n", ex
->flags
& ~(GT_SELECTION
| GT_USECRLF
));
2094 if (ex
->flags
& GT_SELECTION
)
2097 int nStartCur
= ME_GetSelectionOfs(editor
, &from
, &to
);
2098 start
= editor
->pCursors
[nStartCur
];
2103 ME_SetCursorToStart(editor
, &start
);
2106 if (ex
->codepage
== CP_UNICODE
)
2108 return ME_GetTextW(editor
, (LPWSTR
)pText
, ex
->cb
/ sizeof(WCHAR
) - 1,
2109 &start
, nChars
, ex
->flags
& GT_USECRLF
, FALSE
);
2113 /* potentially each char may be a CR, why calculate the exact value with O(N) when
2114 we can just take a bigger buffer? :)
2115 The above assumption still holds with CR/LF counters, since CR->CRLF expansion
2116 occurs only in richedit 2.0 mode, in which line breaks have only one CR
2118 int crlfmul
= (ex
->flags
& GT_USECRLF
) ? 2 : 1;
2123 buflen
= min(crlfmul
* nChars
, ex
->cb
- 1);
2124 buffer
= malloc((buflen
+ 1) * sizeof(WCHAR
));
2126 nChars
= ME_GetTextW(editor
, buffer
, buflen
, &start
, nChars
, ex
->flags
& GT_USECRLF
, FALSE
);
2127 rc
= WideCharToMultiByte(ex
->codepage
, 0, buffer
, nChars
+ 1,
2128 (LPSTR
)pText
, ex
->cb
, ex
->lpDefaultChar
, ex
->lpUsedDefChar
);
2129 if (rc
) rc
--; /* do not count 0 terminator */
2136 static int get_text_range( ME_TextEditor
*editor
, WCHAR
*buffer
,
2137 const ME_Cursor
*start
, int len
)
2139 if (!buffer
) return 0;
2140 return ME_GetTextW( editor
, buffer
, INT_MAX
, start
, len
, FALSE
, FALSE
);
2143 int set_selection( ME_TextEditor
*editor
, int to
, int from
)
2147 TRACE("%d - %d\n", to
, from
);
2149 if (!editor
->bHideSelection
) ME_InvalidateSelection( editor
);
2150 end
= set_selection_cursors( editor
, to
, from
);
2151 editor_ensure_visible( editor
, &editor
->pCursors
[0] );
2152 if (!editor
->bHideSelection
) ME_InvalidateSelection( editor
);
2153 update_caret( editor
);
2154 ME_Repaint( editor
);
2155 ME_SendSelChange( editor
);
2160 typedef struct tagME_GlobalDestStruct
2164 } ME_GlobalDestStruct
;
2166 static DWORD CALLBACK
ME_ReadFromHGLOBALUnicode(DWORD_PTR dwCookie
, LPBYTE lpBuff
, LONG cb
, LONG
*pcb
)
2168 ME_GlobalDestStruct
*pData
= (ME_GlobalDestStruct
*)dwCookie
;
2173 pDest
= (WORD
*)lpBuff
;
2174 pSrc
= GlobalLock(pData
->hData
);
2175 for (i
= 0; i
<cb
&& pSrc
[pData
->nLength
+i
]; i
++) {
2176 pDest
[i
] = pSrc
[pData
->nLength
+i
];
2178 pData
->nLength
+= i
;
2180 GlobalUnlock(pData
->hData
);
2184 static DWORD CALLBACK
ME_ReadFromHGLOBALRTF(DWORD_PTR dwCookie
, LPBYTE lpBuff
, LONG cb
, LONG
*pcb
)
2186 ME_GlobalDestStruct
*pData
= (ME_GlobalDestStruct
*)dwCookie
;
2191 pSrc
= GlobalLock(pData
->hData
);
2192 for (i
= 0; i
<cb
&& pSrc
[pData
->nLength
+i
]; i
++) {
2193 pDest
[i
] = pSrc
[pData
->nLength
+i
];
2195 pData
->nLength
+= i
;
2197 GlobalUnlock(pData
->hData
);
2201 static HRESULT
paste_rtf(ME_TextEditor
*editor
, FORMATETC
*fmt
, STGMEDIUM
*med
)
2204 ME_GlobalDestStruct gds
;
2207 gds
.hData
= med
->hGlobal
;
2209 es
.dwCookie
= (DWORD_PTR
)&gds
;
2210 es
.pfnCallback
= ME_ReadFromHGLOBALRTF
;
2211 hr
= ME_StreamIn( editor
, SF_RTF
| SFF_SELECTION
, &es
, FALSE
) == 0 ? E_FAIL
: S_OK
;
2212 ReleaseStgMedium( med
);
2216 static HRESULT
paste_text(ME_TextEditor
*editor
, FORMATETC
*fmt
, STGMEDIUM
*med
)
2219 ME_GlobalDestStruct gds
;
2222 gds
.hData
= med
->hGlobal
;
2224 es
.dwCookie
= (DWORD_PTR
)&gds
;
2225 es
.pfnCallback
= ME_ReadFromHGLOBALUnicode
;
2226 hr
= ME_StreamIn( editor
, SF_TEXT
| SF_UNICODE
| SFF_SELECTION
, &es
, FALSE
) == 0 ? E_FAIL
: S_OK
;
2227 ReleaseStgMedium( med
);
2231 static HRESULT
paste_emf(ME_TextEditor
*editor
, FORMATETC
*fmt
, STGMEDIUM
*med
)
2236 hr
= insert_static_object( editor
, med
->hEnhMetaFile
, NULL
, &sz
);
2239 ME_CommitUndo( editor
);
2240 ME_UpdateRepaint( editor
, FALSE
);
2243 ReleaseStgMedium( med
);
2248 static struct paste_format
2251 HRESULT (*paste
)(ME_TextEditor
*, FORMATETC
*, STGMEDIUM
*);
2255 {{ -1, NULL
, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
}, paste_rtf
, L
"Rich Text Format" },
2256 {{ CF_UNICODETEXT
, NULL
, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
}, paste_text
},
2257 {{ CF_ENHMETAFILE
, NULL
, DVASPECT_CONTENT
, -1, TYMED_ENHMF
}, paste_emf
},
2261 static void init_paste_formats(void)
2263 struct paste_format
*format
;
2268 for (format
= paste_formats
; format
->fmt
.cfFormat
; format
++)
2271 format
->fmt
.cfFormat
= RegisterClipboardFormatW( format
->name
);
2277 static BOOL
paste_special(ME_TextEditor
*editor
, UINT cf
, REPASTESPECIAL
*ps
, BOOL check_only
)
2281 struct paste_format
*format
;
2284 /* Protect read-only edit control from modification */
2285 if (editor
->props
& TXTBIT_READONLY
)
2287 if (!check_only
) editor_beep( editor
, MB_ICONERROR
);
2291 init_paste_formats();
2293 if (ps
&& ps
->dwAspect
!= DVASPECT_CONTENT
)
2294 FIXME("Ignoring aspect %lx\n", ps
->dwAspect
);
2296 hr
= OleGetClipboard( &data
);
2297 if (hr
!= S_OK
) return FALSE
;
2299 if (cf
== CF_TEXT
) cf
= CF_UNICODETEXT
;
2302 for (format
= paste_formats
; format
->fmt
.cfFormat
; format
++)
2304 if (cf
&& cf
!= format
->fmt
.cfFormat
) continue;
2305 hr
= IDataObject_QueryGetData( data
, &format
->fmt
);
2310 hr
= IDataObject_GetData( data
, &format
->fmt
, &med
);
2311 if (hr
!= S_OK
) goto done
;
2312 hr
= format
->paste( editor
, &format
->fmt
, &med
);
2319 IDataObject_Release( data
);
2324 static HRESULT
editor_copy( ME_TextEditor
*editor
, ME_Cursor
*start
, int chars
, IDataObject
**data_out
)
2326 IDataObject
*data
= NULL
;
2329 if (editor
->lpOleCallback
)
2332 range
.cpMin
= ME_GetCursorOfs( start
);
2333 range
.cpMax
= range
.cpMin
+ chars
;
2334 hr
= IRichEditOleCallback_GetClipboardData( editor
->lpOleCallback
, &range
, RECO_COPY
, &data
);
2337 if (FAILED( hr
) || !data
)
2338 hr
= ME_GetDataObject( editor
, start
, chars
, &data
);
2340 if (SUCCEEDED( hr
))
2346 hr
= OleSetClipboard( data
);
2347 IDataObject_Release( data
);
2354 HRESULT
editor_copy_or_cut( ME_TextEditor
*editor
, BOOL cut
, ME_Cursor
*start
, int count
,
2355 IDataObject
**data_out
)
2359 if (cut
&& (editor
->props
& TXTBIT_READONLY
))
2361 return E_ACCESSDENIED
;
2364 hr
= editor_copy( editor
, start
, count
, data_out
);
2365 if (SUCCEEDED(hr
) && cut
)
2367 ME_InternalDeleteText( editor
, start
, count
, FALSE
);
2368 ME_CommitUndo( editor
);
2369 ME_UpdateRepaint( editor
, TRUE
);
2374 static BOOL
copy_or_cut( ME_TextEditor
*editor
, BOOL cut
)
2378 int start_cursor
= ME_GetSelectionOfs( editor
, &offs
, &count
);
2379 ME_Cursor
*sel_start
= &editor
->pCursors
[start_cursor
];
2381 if (editor
->password_char
) return FALSE
;
2384 hr
= editor_copy_or_cut( editor
, cut
, sel_start
, count
, NULL
);
2385 if (FAILED( hr
)) editor_beep( editor
, MB_ICONERROR
);
2387 return SUCCEEDED( hr
);
2390 static void ME_UpdateSelectionLinkAttribute(ME_TextEditor
*editor
)
2392 ME_Paragraph
*start_para
, *end_para
;
2393 ME_Cursor
*from
, *to
, start
;
2396 if (!editor
->AutoURLDetect_bEnable
) return;
2398 ME_GetSelection(editor
, &from
, &to
);
2400 /* Find paragraph previous to the one that contains start cursor */
2401 start_para
= from
->para
;
2402 if (para_prev( start_para
)) start_para
= para_prev( start_para
);
2404 /* Find paragraph that contains end cursor */
2405 end_para
= para_next( to
->para
);
2407 start
.para
= start_para
;
2408 start
.run
= para_first_run( start_para
);
2410 num_chars
= end_para
->nCharOfs
- start_para
->nCharOfs
;
2412 ME_UpdateLinkAttribute( editor
, &start
, num_chars
);
2415 static BOOL
handle_enter(ME_TextEditor
*editor
)
2417 BOOL shift_is_down
= GetKeyState(VK_SHIFT
) & 0x8000;
2419 if (editor
->props
& TXTBIT_MULTILINE
)
2421 ME_Cursor cursor
= editor
->pCursors
[0];
2422 ME_Paragraph
*para
= cursor
.para
;
2424 ME_Style
*style
, *eop_style
;
2426 if (editor
->props
& TXTBIT_READONLY
)
2428 editor_beep( editor
, MB_ICONERROR
);
2432 ME_GetSelectionOfs(editor
, &from
, &to
);
2433 if (editor
->nTextLimit
> ME_GetTextLength(editor
) - (to
-from
))
2435 if (!editor
->bEmulateVersion10
) /* v4.1 */
2437 if (para
->nFlags
& MEPF_ROWEND
)
2439 /* Add a new table row after this row. */
2440 para
= table_append_row( editor
, para
);
2441 para
= para_next( para
);
2442 editor
->pCursors
[0].para
= para
;
2443 editor
->pCursors
[0].run
= para_first_run( para
);
2444 editor
->pCursors
[0].nOffset
= 0;
2445 editor
->pCursors
[1] = editor
->pCursors
[0];
2446 ME_CommitUndo(editor
);
2447 ME_UpdateRepaint(editor
, FALSE
);
2450 else if (para
== editor
->pCursors
[1].para
&&
2451 cursor
.nOffset
+ cursor
.run
->nCharOfs
== 0 &&
2452 para_prev( para
) && para_prev( para
)->nFlags
& MEPF_ROWSTART
&&
2453 !para_prev( para
)->nCharOfs
)
2455 /* Insert a newline before the table. */
2456 para
= para_prev( para
);
2457 para
->nFlags
&= ~MEPF_ROWSTART
;
2458 editor
->pCursors
[0].para
= para
;
2459 editor
->pCursors
[0].run
= para_first_run( para
);
2460 editor
->pCursors
[1] = editor
->pCursors
[0];
2461 ME_InsertTextFromCursor( editor
, 0, L
"\r", 1, editor
->pCursors
[0].run
->style
);
2462 para
= editor_first_para( editor
);
2463 editor_set_default_para_fmt( editor
, ¶
->fmt
);
2465 para_mark_rewrap( editor
, para
);
2466 editor
->pCursors
[0].para
= para
;
2467 editor
->pCursors
[0].run
= para_first_run( para
);
2468 editor
->pCursors
[1] = editor
->pCursors
[0];
2469 para_next( para
)->nFlags
|= MEPF_ROWSTART
;
2470 ME_CommitCoalescingUndo(editor
);
2471 ME_UpdateRepaint(editor
, FALSE
);
2475 else /* v1.0 - 3.0 */
2477 ME_Paragraph
*para
= cursor
.para
;
2478 if (para_in_table( para
))
2480 if (cursor
.run
->nFlags
& MERF_ENDPARA
)
2484 ME_ContinueCoalescingTransaction(editor
);
2485 para
= table_append_row( editor
, para
);
2486 editor
->pCursors
[0].para
= para
;
2487 editor
->pCursors
[0].run
= para_first_run( para
);
2488 editor
->pCursors
[0].nOffset
= 0;
2489 editor
->pCursors
[1] = editor
->pCursors
[0];
2490 ME_CommitCoalescingUndo(editor
);
2491 ME_UpdateRepaint(editor
, FALSE
);
2497 ME_ContinueCoalescingTransaction(editor
);
2498 if (cursor
.run
->nCharOfs
+ cursor
.nOffset
== 0 &&
2499 para_prev( para
) && !para_in_table( para_prev( para
) ))
2501 /* Insert newline before table */
2502 cursor
.run
= para_end_run( para_prev( para
) );
2505 editor
->pCursors
[0].run
= cursor
.run
;
2506 editor
->pCursors
[0].para
= para_prev( para
);
2508 editor
->pCursors
[0].nOffset
= 0;
2509 editor
->pCursors
[1] = editor
->pCursors
[0];
2510 ME_InsertTextFromCursor( editor
, 0, L
"\r", 1, editor
->pCursors
[0].run
->style
);
2514 editor
->pCursors
[1] = editor
->pCursors
[0];
2515 para
= table_append_row( editor
, para
);
2516 editor
->pCursors
[0].para
= para
;
2517 editor
->pCursors
[0].run
= para_first_run( para
);
2518 editor
->pCursors
[0].nOffset
= 0;
2519 editor
->pCursors
[1] = editor
->pCursors
[0];
2521 ME_CommitCoalescingUndo(editor
);
2522 ME_UpdateRepaint(editor
, FALSE
);
2528 style
= style_get_insert_style( editor
, editor
->pCursors
);
2530 /* Normally the new eop style is the insert style, however in a list it is copied from the existing
2531 eop style (this prevents the list label style changing when the new eop is inserted).
2532 No extra ref is taken here on eop_style. */
2533 if (para
->fmt
.wNumbering
)
2534 eop_style
= para
->eop_run
->style
;
2537 ME_ContinueCoalescingTransaction(editor
);
2539 ME_InsertEndRowFromCursor(editor
, 0);
2541 if (!editor
->bEmulateVersion10
)
2542 ME_InsertTextFromCursor(editor
, 0, L
"\r", 1, eop_style
);
2544 ME_InsertTextFromCursor(editor
, 0, L
"\r\n", 2, eop_style
);
2545 ME_CommitCoalescingUndo(editor
);
2548 ME_UpdateSelectionLinkAttribute(editor
);
2549 ME_UpdateRepaint(editor
, FALSE
);
2550 ME_SaveTempStyle(editor
, style
); /* set the temp insert style for the new para */
2551 ME_ReleaseStyle(style
);
2559 ME_KeyDown(ME_TextEditor
*editor
, WORD nKey
)
2561 BOOL ctrl_is_down
= GetKeyState(VK_CONTROL
) & 0x8000;
2562 BOOL shift_is_down
= GetKeyState(VK_SHIFT
) & 0x8000;
2564 if (editor
->bMouseCaptured
)
2566 if (nKey
!= VK_SHIFT
&& nKey
!= VK_CONTROL
&& nKey
!= VK_MENU
)
2567 editor
->nSelectionType
= stPosition
;
2575 editor
->nUDArrowX
= -1;
2581 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
2582 ME_ArrowKey(editor
, nKey
, shift_is_down
, ctrl_is_down
);
2586 editor
->nUDArrowX
= -1;
2587 /* FIXME backspace and delete aren't the same, they act different wrt paragraph style of the merged paragraph */
2588 if (editor
->props
& TXTBIT_READONLY
)
2590 if (ME_IsSelection(editor
))
2592 ME_DeleteSelection(editor
);
2593 ME_CommitUndo(editor
);
2595 else if (nKey
== VK_DELETE
)
2597 /* Delete stops group typing.
2598 * (See MSDN remarks on EM_STOPGROUPTYPING message) */
2599 ME_DeleteTextAtCursor(editor
, 1, 1);
2600 ME_CommitUndo(editor
);
2602 else if (ME_ArrowKey(editor
, VK_LEFT
, FALSE
, FALSE
))
2604 BOOL bDeletionSucceeded
;
2605 /* Backspace can be grouped for a single undo */
2606 ME_ContinueCoalescingTransaction(editor
);
2607 bDeletionSucceeded
= ME_DeleteTextAtCursor(editor
, 1, 1);
2608 if (!bDeletionSucceeded
&& !editor
->bEmulateVersion10
) { /* v4.1 */
2609 /* Deletion was prevented so the cursor is moved back to where it was.
2610 * (e.g. this happens when trying to delete cell boundaries)
2612 ME_ArrowKey(editor
, VK_RIGHT
, FALSE
, FALSE
);
2614 ME_CommitCoalescingUndo(editor
);
2618 table_move_from_row_start( editor
);
2619 ME_UpdateSelectionLinkAttribute(editor
);
2620 ME_UpdateRepaint(editor
, FALSE
);
2621 ME_SendRequestResize(editor
, FALSE
);
2624 if (!editor
->bEmulateVersion10
)
2625 return handle_enter(editor
);
2630 set_selection( editor
, 0, -1 );
2636 return paste_special( editor
, 0, NULL
, FALSE
);
2641 return copy_or_cut(editor
, nKey
== 'X');
2659 if (nKey
!= VK_SHIFT
&& nKey
!= VK_CONTROL
&& nKey
&& nKey
!= VK_MENU
)
2660 editor
->nUDArrowX
= -1;
2667 chf
.cbSize
= sizeof(chf
);
2669 ME_GetSelectionCharFormat(editor
, &chf
);
2670 ME_DumpStyleToBuf(&chf
, buf
);
2671 MessageBoxA(NULL
, buf
, "Style dump", MB_OK
);
2675 ME_CheckCharOffsets(editor
);
2682 static LRESULT
handle_wm_char( ME_TextEditor
*editor
, WCHAR wstr
, LPARAM flags
)
2684 if (editor
->bMouseCaptured
)
2687 if (editor
->props
& TXTBIT_READONLY
)
2689 editor_beep( editor
, MB_ICONERROR
);
2690 return 0; /* FIXME really 0 ? */
2693 if (editor
->bEmulateVersion10
&& wstr
== '\r')
2694 handle_enter(editor
);
2696 if ((unsigned)wstr
>= ' ' || wstr
== '\t')
2698 ME_Cursor cursor
= editor
->pCursors
[0];
2699 ME_Paragraph
*para
= cursor
.para
;
2701 BOOL ctrl_is_down
= GetKeyState(VK_CONTROL
) & 0x8000;
2702 ME_GetSelectionOfs(editor
, &from
, &to
);
2704 /* v4.1 allows tabs to be inserted with ctrl key down */
2705 !(ctrl_is_down
&& !editor
->bEmulateVersion10
))
2707 BOOL selected_row
= FALSE
;
2709 if (ME_IsSelection(editor
) &&
2710 cursor
.run
->nCharOfs
+ cursor
.nOffset
== 0 &&
2711 to
== ME_GetCursorOfs(&editor
->pCursors
[0]) && para_prev( para
))
2713 para
= para_prev( para
);
2714 selected_row
= TRUE
;
2716 if (para_in_table( para
))
2718 table_handle_tab( editor
, selected_row
);
2719 ME_CommitUndo(editor
);
2723 else if (!editor
->bEmulateVersion10
) /* v4.1 */
2725 if (para
->nFlags
& MEPF_ROWEND
)
2729 para
= para_next( para
);
2730 if (para
->nFlags
& MEPF_ROWSTART
) para
= para_next( para
);
2731 editor
->pCursors
[0].para
= para
;
2732 editor
->pCursors
[0].run
= para_first_run( para
);
2733 editor
->pCursors
[0].nOffset
= 0;
2734 editor
->pCursors
[1] = editor
->pCursors
[0];
2738 else /* v1.0 - 3.0 */
2740 if (para_in_table( para
) && cursor
.run
->nFlags
& MERF_ENDPARA
&& from
== to
)
2742 /* Text should not be inserted at the end of the table. */
2743 editor_beep( editor
, -1 );
2747 /* FIXME maybe it would make sense to call EM_REPLACESEL instead ? */
2748 /* WM_CHAR is restricted to nTextLimit */
2749 if(editor
->nTextLimit
> ME_GetTextLength(editor
) - (to
-from
))
2751 ME_Style
*style
= style_get_insert_style( editor
, editor
->pCursors
);
2752 ME_ContinueCoalescingTransaction(editor
);
2753 ME_InsertTextFromCursor(editor
, 0, &wstr
, 1, style
);
2754 ME_ReleaseStyle(style
);
2755 ME_CommitCoalescingUndo(editor
);
2756 ITextHost_TxSetCursor(editor
->texthost
, NULL
, FALSE
);
2759 ME_UpdateSelectionLinkAttribute(editor
);
2760 ME_UpdateRepaint(editor
, FALSE
);
2765 /* Process the message and calculate the new click count.
2767 * returns: The click count if it is mouse down event, else returns 0. */
2768 static int ME_CalculateClickCount(ME_TextEditor
*editor
, UINT msg
, WPARAM wParam
,
2771 static int clickNum
= 0;
2772 if (msg
< WM_MOUSEFIRST
|| msg
> WM_MOUSELAST
)
2775 if ((msg
== WM_LBUTTONDBLCLK
) ||
2776 (msg
== WM_RBUTTONDBLCLK
) ||
2777 (msg
== WM_MBUTTONDBLCLK
) ||
2778 (msg
== WM_XBUTTONDBLCLK
))
2780 msg
-= (WM_LBUTTONDBLCLK
- WM_LBUTTONDOWN
);
2783 if ((msg
== WM_LBUTTONDOWN
) ||
2784 (msg
== WM_RBUTTONDOWN
) ||
2785 (msg
== WM_MBUTTONDOWN
) ||
2786 (msg
== WM_XBUTTONDOWN
))
2788 static MSG prevClickMsg
;
2790 /* Compare the editor instead of the hwnd so that the this
2791 * can still be done for windowless richedit controls. */
2792 clickMsg
.hwnd
= (HWND
)editor
;
2793 clickMsg
.message
= msg
;
2794 clickMsg
.wParam
= wParam
;
2795 clickMsg
.lParam
= lParam
;
2796 clickMsg
.time
= GetMessageTime();
2797 clickMsg
.pt
.x
= (short)LOWORD(lParam
);
2798 clickMsg
.pt
.y
= (short)HIWORD(lParam
);
2799 if ((clickNum
!= 0) &&
2800 (clickMsg
.message
== prevClickMsg
.message
) &&
2801 (clickMsg
.hwnd
== prevClickMsg
.hwnd
) &&
2802 (clickMsg
.wParam
== prevClickMsg
.wParam
) &&
2803 (clickMsg
.time
- prevClickMsg
.time
< GetDoubleClickTime()) &&
2804 (abs(clickMsg
.pt
.x
- prevClickMsg
.pt
.x
) < GetSystemMetrics(SM_CXDOUBLECLK
)/2) &&
2805 (abs(clickMsg
.pt
.y
- prevClickMsg
.pt
.y
) < GetSystemMetrics(SM_CYDOUBLECLK
)/2))
2811 prevClickMsg
= clickMsg
;
2818 static BOOL
is_link( ME_Run
*run
)
2820 return (run
->style
->fmt
.dwMask
& CFM_LINK
) && (run
->style
->fmt
.dwEffects
& CFE_LINK
);
2823 void editor_set_cursor( ME_TextEditor
*editor
, int x
, int y
)
2826 static HCURSOR cursor_arrow
, cursor_hand
, cursor_ibeam
, cursor_reverse
;
2831 cursor_arrow
= LoadCursorW( NULL
, MAKEINTRESOURCEW( IDC_ARROW
) );
2832 cursor_hand
= LoadCursorW( NULL
, MAKEINTRESOURCEW( IDC_HAND
) );
2833 cursor_ibeam
= LoadCursorW( NULL
, MAKEINTRESOURCEW( IDC_IBEAM
) );
2834 cursor_reverse
= LoadCursorW( dll_instance
, MAKEINTRESOURCEW( OCR_REVERSE
) );
2837 cursor
= cursor_ibeam
;
2839 if ((editor
->nSelectionType
== stLine
&& editor
->bMouseCaptured
) ||
2840 (!editor
->bEmulateVersion10
&& y
< editor
->rcFormat
.top
&& x
< editor
->rcFormat
.left
))
2841 cursor
= cursor_reverse
;
2842 else if (y
< editor
->rcFormat
.top
|| y
> editor
->rcFormat
.bottom
)
2844 if (editor
->bEmulateVersion10
) cursor
= cursor_arrow
;
2845 else cursor
= cursor_ibeam
;
2847 else if (x
< editor
->rcFormat
.left
) cursor
= cursor_reverse
;
2848 else if (cursor_from_coords( editor
, x
, y
, &pos
))
2850 ME_Run
*run
= pos
.run
;
2852 if (is_link( run
)) cursor
= cursor_hand
;
2854 else if (ME_IsSelection( editor
))
2857 int offset
= ME_GetCursorOfs( &pos
);
2859 ME_GetSelectionOfs( editor
, &start
, &end
);
2860 if (start
<= offset
&& end
>= offset
) cursor
= cursor_arrow
;
2864 ITextHost_TxSetCursor( editor
->texthost
, cursor
, cursor
== cursor_ibeam
);
2867 static LONG
ME_GetSelectionType(ME_TextEditor
*editor
)
2869 LONG sel_type
= SEL_EMPTY
;
2872 ME_GetSelectionOfs(editor
, &start
, &end
);
2874 sel_type
= SEL_EMPTY
;
2877 LONG object_count
= 0, character_count
= 0;
2880 for (i
= 0; i
< end
- start
; i
++)
2884 cursor_from_char_ofs( editor
, start
+ i
, &cursor
);
2885 if (cursor
.run
->reobj
) object_count
++;
2886 else character_count
++;
2887 if (character_count
>= 2 && object_count
>= 2)
2888 return (SEL_TEXT
| SEL_MULTICHAR
| SEL_OBJECT
| SEL_MULTIOBJECT
);
2890 if (character_count
)
2892 sel_type
|= SEL_TEXT
;
2893 if (character_count
>= 2)
2894 sel_type
|= SEL_MULTICHAR
;
2898 sel_type
|= SEL_OBJECT
;
2899 if (object_count
>= 2)
2900 sel_type
|= SEL_MULTIOBJECT
;
2906 static BOOL
ME_ShowContextMenu(ME_TextEditor
*editor
, int x
, int y
)
2913 if (!editor
->lpOleCallback
|| !editor
->have_texthost2
) return FALSE
;
2914 if (FAILED( ITextHost2_TxGetWindow( editor
->texthost
, &hwnd
))) return FALSE
;
2915 parent
= GetParent( hwnd
);
2916 if (!parent
) parent
= hwnd
;
2918 ME_GetSelectionOfs( editor
, &selrange
.cpMin
, &selrange
.cpMax
);
2919 seltype
= ME_GetSelectionType( editor
);
2920 if (SUCCEEDED( IRichEditOleCallback_GetContextMenu( editor
->lpOleCallback
, seltype
, NULL
, &selrange
, &menu
) ))
2922 TrackPopupMenu( menu
, TPM_LEFTALIGN
| TPM_RIGHTBUTTON
, x
, y
, 0, parent
, NULL
);
2923 DestroyMenu( menu
);
2928 ME_TextEditor
*ME_MakeEditor(ITextHost
*texthost
, BOOL bEmulateVersion10
)
2930 ME_TextEditor
*ed
= malloc( sizeof(*ed
) );
2936 ed
->sizeWindow
.cx
= ed
->sizeWindow
.cy
= 0;
2937 if (ITextHost_QueryInterface( texthost
, &IID_ITextHost2
, (void **)&ed
->texthost
) == S_OK
)
2939 ITextHost_Release( texthost
);
2940 ed
->have_texthost2
= TRUE
;
2944 ed
->texthost
= (ITextHost2
*)texthost
;
2945 ed
->have_texthost2
= FALSE
;
2948 ed
->bEmulateVersion10
= bEmulateVersion10
;
2949 ed
->in_place_active
= FALSE
;
2951 ITextHost_TxGetPropertyBits( ed
->texthost
, TXTBIT_RICHTEXT
| TXTBIT_MULTILINE
| TXTBIT_READONLY
|
2952 TXTBIT_USEPASSWORD
| TXTBIT_HIDESELECTION
| TXTBIT_SAVESELECTION
|
2953 TXTBIT_AUTOWORDSEL
| TXTBIT_VERTICAL
| TXTBIT_WORDWRAP
| TXTBIT_ALLOWBEEP
|
2956 ITextHost_TxGetScrollBars( ed
->texthost
, &ed
->scrollbars
);
2957 ed
->pBuffer
= ME_MakeText();
2958 ed
->nZoomNumerator
= ed
->nZoomDenominator
= 0;
2959 ed
->nAvailWidth
= 0; /* wrap to client area */
2960 list_init( &ed
->style_list
);
2962 hdc
= ITextHost_TxGetDC( ed
->texthost
);
2963 ME_MakeFirstParagraph( ed
, hdc
);
2964 /* The four cursors are for:
2965 * 0 - The position where the caret is shown
2966 * 1 - The anchored end of the selection (for normal selection)
2967 * 2 & 3 - The anchored start and end respectively for word, line,
2968 * or paragraph selection.
2971 ed
->pCursors
= malloc( ed
->nCursors
* sizeof(*ed
->pCursors
) );
2972 ME_SetCursorToStart(ed
, &ed
->pCursors
[0]);
2973 ed
->pCursors
[1] = ed
->pCursors
[0];
2974 ed
->pCursors
[2] = ed
->pCursors
[0];
2975 ed
->pCursors
[3] = ed
->pCursors
[1];
2976 ed
->nLastTotalLength
= ed
->nTotalLength
= 0;
2977 ed
->nLastTotalWidth
= ed
->nTotalWidth
= 0;
2980 ed
->nModifyStep
= 0;
2981 ed
->nTextLimit
= TEXT_LIMIT_DEFAULT
;
2982 list_init( &ed
->undo_stack
);
2983 list_init( &ed
->redo_stack
);
2984 ed
->nUndoStackSize
= 0;
2985 ed
->nUndoLimit
= STACK_SIZE_DEFAULT
;
2986 ed
->nUndoMode
= umAddToUndo
;
2987 ed
->undo_ctl_state
= undoActive
;
2988 ed
->nParagraphs
= 1;
2989 ed
->nLastSelStart
= ed
->nLastSelEnd
= 0;
2990 ed
->last_sel_start_para
= ed
->last_sel_end_para
= ed
->pCursors
[0].para
;
2991 ed
->bHideSelection
= FALSE
;
2992 ed
->pfnWordBreak
= NULL
;
2994 ed
->lpOleCallback
= NULL
;
2995 ed
->mode
= TM_MULTILEVELUNDO
| TM_MULTICODEPAGE
;
2996 ed
->mode
|= (ed
->props
& TXTBIT_RICHTEXT
) ? TM_RICHTEXT
: TM_PLAINTEXT
;
2997 ed
->AutoURLDetect_bEnable
= FALSE
;
2998 ed
->bHaveFocus
= FALSE
;
2999 ed
->freeze_count
= 0;
3000 ed
->bMouseCaptured
= FALSE
;
3001 ed
->caret_hidden
= FALSE
;
3002 ed
->caret_height
= 0;
3003 for (i
=0; i
<HFONT_CACHE_SIZE
; i
++)
3005 ed
->pFontCache
[i
].nRefs
= 0;
3006 ed
->pFontCache
[i
].nAge
= 0;
3007 ed
->pFontCache
[i
].hFont
= NULL
;
3010 ME_CheckCharOffsets(ed
);
3011 SetRectEmpty(&ed
->rcFormat
);
3012 hr
= ITextHost_TxGetSelectionBarWidth( ed
->texthost
, &selbarwidth
);
3013 /* FIXME: Convert selbarwidth from HIMETRIC to pixels */
3014 if (hr
== S_OK
&& selbarwidth
) ed
->selofs
= SELECTIONBAR_WIDTH
;
3015 else ed
->selofs
= 0;
3016 ed
->nSelectionType
= stPosition
;
3018 ed
->password_char
= 0;
3019 if (ed
->props
& TXTBIT_USEPASSWORD
)
3020 ITextHost_TxGetPasswordChar( ed
->texthost
, &ed
->password_char
);
3022 ed
->bWordWrap
= (ed
->props
& TXTBIT_WORDWRAP
) && (ed
->props
& TXTBIT_MULTILINE
);
3024 ed
->notified_cr
.cpMin
= ed
->notified_cr
.cpMax
= 0;
3026 /* Default scrollbar information */
3027 ed
->vert_si
.cbSize
= sizeof(SCROLLINFO
);
3028 ed
->vert_si
.nMin
= 0;
3029 ed
->vert_si
.nMax
= 0;
3030 ed
->vert_si
.nPage
= 0;
3031 ed
->vert_si
.nPos
= 0;
3032 ed
->vert_sb_enabled
= 0;
3034 ed
->horz_si
.cbSize
= sizeof(SCROLLINFO
);
3035 ed
->horz_si
.nMin
= 0;
3036 ed
->horz_si
.nMax
= 0;
3037 ed
->horz_si
.nPage
= 0;
3038 ed
->horz_si
.nPos
= 0;
3039 ed
->horz_sb_enabled
= 0;
3041 if (ed
->scrollbars
& ES_DISABLENOSCROLL
)
3043 if (ed
->scrollbars
& WS_VSCROLL
)
3045 ITextHost_TxSetScrollRange( ed
->texthost
, SB_VERT
, 0, 1, TRUE
);
3046 ITextHost_TxEnableScrollBar( ed
->texthost
, SB_VERT
, ESB_DISABLE_BOTH
);
3048 if (ed
->scrollbars
& WS_HSCROLL
)
3050 ITextHost_TxSetScrollRange( ed
->texthost
, SB_HORZ
, 0, 1, TRUE
);
3051 ITextHost_TxEnableScrollBar( ed
->texthost
, SB_HORZ
, ESB_DISABLE_BOTH
);
3055 ed
->wheel_remain
= 0;
3057 ed
->back_style
= TXTBACK_OPAQUE
;
3058 ITextHost_TxGetBackStyle( ed
->texthost
, &ed
->back_style
);
3060 list_init( &ed
->reobj_list
);
3061 OleInitialize(NULL
);
3063 wrap_marked_paras_dc( ed
, hdc
, FALSE
);
3064 ITextHost_TxReleaseDC( ed
->texthost
, hdc
);
3069 void ME_DestroyEditor(ME_TextEditor
*editor
)
3071 ME_DisplayItem
*p
= editor
->pBuffer
->pFirst
, *pNext
= NULL
;
3072 ME_Style
*s
, *cursor2
;
3075 ME_ClearTempStyle(editor
);
3076 ME_EmptyUndoStack(editor
);
3077 editor
->pBuffer
->pFirst
= NULL
;
3081 if (p
->type
== diParagraph
)
3082 para_destroy( editor
, &p
->member
.para
);
3084 ME_DestroyDisplayItem(p
);
3088 LIST_FOR_EACH_ENTRY_SAFE( s
, cursor2
, &editor
->style_list
, ME_Style
, entry
)
3089 ME_DestroyStyle( s
);
3091 ME_ReleaseStyle(editor
->pBuffer
->pDefaultStyle
);
3092 for (i
=0; i
<HFONT_CACHE_SIZE
; i
++)
3094 if (editor
->pFontCache
[i
].hFont
)
3095 DeleteObject(editor
->pFontCache
[i
].hFont
);
3097 if(editor
->lpOleCallback
)
3098 IRichEditOleCallback_Release(editor
->lpOleCallback
);
3102 free(editor
->pBuffer
);
3103 free(editor
->pCursors
);
3107 static inline int get_default_line_height( ME_TextEditor
*editor
)
3111 if (editor
->pBuffer
&& editor
->pBuffer
->pDefaultStyle
)
3112 height
= editor
->pBuffer
->pDefaultStyle
->tm
.tmHeight
;
3113 if (height
<= 0) height
= 24;
3118 static inline int calc_wheel_change( int *remain
, int amount_per_click
)
3120 int change
= amount_per_click
* (float)*remain
/ WHEEL_DELTA
;
3121 *remain
-= WHEEL_DELTA
* change
/ amount_per_click
;
3125 void link_notify(ME_TextEditor
*editor
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
3128 ME_Cursor cursor
; /* The start of the clicked text. */
3132 x
= (short)LOWORD(lParam
);
3133 y
= (short)HIWORD(lParam
);
3134 if (!cursor_from_coords( editor
, x
, y
, &cursor
)) return;
3136 if (is_link( cursor
.run
))
3137 { /* The clicked run has CFE_LINK set */
3138 info
.nmhdr
.hwndFrom
= NULL
;
3139 info
.nmhdr
.idFrom
= 0;
3140 info
.nmhdr
.code
= EN_LINK
;
3142 info
.wParam
= wParam
;
3143 info
.lParam
= lParam
;
3146 /* find the first contiguous run with CFE_LINK set */
3147 info
.chrg
.cpMin
= ME_GetCursorOfs(&cursor
);
3149 while ((run
= run_prev( run
)) && is_link( run
))
3150 info
.chrg
.cpMin
-= run
->len
;
3152 /* find the last contiguous run with CFE_LINK set */
3153 info
.chrg
.cpMax
= ME_GetCursorOfs(&cursor
) + cursor
.run
->len
;
3155 while ((run
= run_next( run
)) && is_link( run
))
3156 info
.chrg
.cpMax
+= run
->len
;
3158 ITextHost_TxNotify(editor
->texthost
, info
.nmhdr
.code
, &info
);
3162 void ME_ReplaceSel(ME_TextEditor
*editor
, BOOL can_undo
, const WCHAR
*str
, int len
)
3168 nStartCursor
= ME_GetSelectionOfs(editor
, &from
, &to
);
3169 style
= ME_GetSelectionInsertStyle(editor
);
3170 ME_InternalDeleteText(editor
, &editor
->pCursors
[nStartCursor
], to
-from
, FALSE
);
3171 ME_InsertTextFromCursor(editor
, 0, str
, len
, style
);
3172 ME_ReleaseStyle(style
);
3173 /* drop temporary style if line end */
3175 * FIXME question: does abc\n mean: put abc,
3176 * clear temp style, put \n? (would require a change)
3178 if (len
>0 && str
[len
-1] == '\n')
3179 ME_ClearTempStyle(editor
);
3180 ME_CommitUndo(editor
);
3181 ME_UpdateSelectionLinkAttribute(editor
);
3183 ME_EmptyUndoStack(editor
);
3184 ME_UpdateRepaint(editor
, FALSE
);
3187 static void ME_SetText(ME_TextEditor
*editor
, void *text
, BOOL unicode
)
3189 LONG codepage
= unicode
? CP_UNICODE
: CP_ACP
;
3192 LPWSTR wszText
= ME_ToUnicode(codepage
, text
, &textLen
);
3193 ME_InsertTextFromCursor(editor
, 0, wszText
, textLen
, editor
->pBuffer
->pDefaultStyle
);
3194 ME_EndToUnicode(codepage
, wszText
);
3197 static LRESULT
handle_EM_SETCHARFORMAT( ME_TextEditor
*editor
, WPARAM flags
, const CHARFORMAT2W
*fmt_in
)
3200 BOOL changed
= TRUE
;
3201 ME_Cursor start
, end
;
3203 if (!cfany_to_cf2w( &fmt
, fmt_in
)) return 0;
3205 if (flags
& SCF_ALL
)
3207 if (editor
->mode
& TM_PLAINTEXT
)
3209 ME_SetDefaultCharFormat( editor
, &fmt
);
3213 ME_SetCursorToStart( editor
, &start
);
3214 ME_SetCharFormat( editor
, &start
, NULL
, &fmt
);
3215 editor
->nModifyStep
= 1;
3218 else if (flags
& SCF_SELECTION
)
3220 if (editor
->mode
& TM_PLAINTEXT
) return 0;
3221 if (flags
& SCF_WORD
)
3223 end
= editor
->pCursors
[0];
3224 ME_MoveCursorWords( editor
, &end
, +1 );
3226 ME_MoveCursorWords( editor
, &start
, -1 );
3227 ME_SetCharFormat( editor
, &start
, &end
, &fmt
);
3229 changed
= ME_IsSelection( editor
);
3230 ME_SetSelectionCharFormat( editor
, &fmt
);
3231 if (changed
) editor
->nModifyStep
= 1;
3233 else /* SCF_DEFAULT */
3235 ME_SetDefaultCharFormat( editor
, &fmt
);
3238 ME_CommitUndo( editor
);
3241 ME_WrapMarkedParagraphs( editor
);
3242 ME_UpdateScrollBar( editor
);
3247 #define UNSUPPORTED_MSG(e) \
3249 FIXME(#e ": stub\n"); \
3250 *phresult = S_FALSE; \
3253 /* Handle messages for windowless and windowed richedit controls.
3255 * The LRESULT that is returned is a return value for window procs,
3256 * and the phresult parameter is the COM return code needed by the
3257 * text services interface. */
3258 LRESULT
editor_handle_message( ME_TextEditor
*editor
, UINT msg
, WPARAM wParam
,
3259 LPARAM lParam
, HRESULT
* phresult
)
3265 UNSUPPORTED_MSG(EM_DISPLAYBAND
)
3266 UNSUPPORTED_MSG(EM_FINDWORDBREAK
)
3267 UNSUPPORTED_MSG(EM_FMTLINES
)
3268 UNSUPPORTED_MSG(EM_FORMATRANGE
)
3269 UNSUPPORTED_MSG(EM_GETBIDIOPTIONS
)
3270 UNSUPPORTED_MSG(EM_GETEDITSTYLE
)
3271 UNSUPPORTED_MSG(EM_GETIMECOMPMODE
)
3272 UNSUPPORTED_MSG(EM_GETIMESTATUS
)
3273 UNSUPPORTED_MSG(EM_SETIMESTATUS
)
3274 UNSUPPORTED_MSG(EM_GETLANGOPTIONS
)
3275 UNSUPPORTED_MSG(EM_GETREDONAME
)
3276 UNSUPPORTED_MSG(EM_GETTYPOGRAPHYOPTIONS
)
3277 UNSUPPORTED_MSG(EM_GETUNDONAME
)
3278 UNSUPPORTED_MSG(EM_GETWORDBREAKPROCEX
)
3279 UNSUPPORTED_MSG(EM_SETBIDIOPTIONS
)
3280 UNSUPPORTED_MSG(EM_SETEDITSTYLE
)
3281 UNSUPPORTED_MSG(EM_SETLANGOPTIONS
)
3282 UNSUPPORTED_MSG(EM_SETMARGINS
)
3283 UNSUPPORTED_MSG(EM_SETPALETTE
)
3284 UNSUPPORTED_MSG(EM_SETTABSTOPS
)
3285 UNSUPPORTED_MSG(EM_SETTYPOGRAPHYOPTIONS
)
3286 UNSUPPORTED_MSG(EM_SETWORDBREAKPROCEX
)
3288 /* Messages specific to Richedit controls */
3291 return ME_StreamIn(editor
, wParam
, (EDITSTREAM
*)lParam
, TRUE
);
3293 return ME_StreamOut(editor
, wParam
, (EDITSTREAM
*)lParam
);
3294 case EM_EMPTYUNDOBUFFER
:
3295 ME_EmptyUndoStack(editor
);
3299 /* Note: wParam/lParam can be NULL */
3301 LONG
*pfrom
= wParam
? (LONG
*)wParam
: &from
;
3302 LONG
*pto
= lParam
? (LONG
*)lParam
: &to
;
3303 ME_GetSelectionOfs(editor
, pfrom
, pto
);
3304 if ((*pfrom
|*pto
) & 0xFFFF0000)
3306 return MAKELONG(*pfrom
,*pto
);
3310 CHARRANGE
*pRange
= (CHARRANGE
*)lParam
;
3311 ME_GetSelectionOfs(editor
, &pRange
->cpMin
, &pRange
->cpMax
);
3312 TRACE("EM_EXGETSEL = (%ld,%ld)\n", pRange
->cpMin
, pRange
->cpMax
);
3315 case EM_SETUNDOLIMIT
:
3317 editor_enable_undo(editor
);
3318 if ((int)wParam
< 0)
3319 editor
->nUndoLimit
= STACK_SIZE_DEFAULT
;
3321 editor
->nUndoLimit
= min(wParam
, STACK_SIZE_MAX
);
3322 /* Setting a max stack size keeps wine from getting killed
3323 for hogging memory. Windows allocates all this memory at once, so
3324 no program would realistically set a value above our maximum. */
3325 return editor
->nUndoLimit
;
3328 return !list_empty( &editor
->undo_stack
);
3330 return !list_empty( &editor
->redo_stack
);
3331 case WM_UNDO
: /* FIXME: actually not the same */
3333 return ME_Undo(editor
);
3335 return ME_Redo(editor
);
3336 case EM_SETFONTSIZE
:
3339 LONG tmp_size
, size
;
3340 BOOL is_increase
= ((LONG
)wParam
> 0);
3342 if (editor
->mode
& TM_PLAINTEXT
)
3345 cf
.cbSize
= sizeof(cf
);
3346 cf
.dwMask
= CFM_SIZE
;
3347 ME_GetSelectionCharFormat(editor
, &cf
);
3348 tmp_size
= (cf
.yHeight
/ 20) + wParam
;
3352 else if (tmp_size
> 12 && tmp_size
< 28 && tmp_size
% 2)
3353 size
= tmp_size
+ (is_increase
? 1 : -1);
3354 else if (tmp_size
> 28 && tmp_size
< 36)
3355 size
= is_increase
? 36 : 28;
3356 else if (tmp_size
> 36 && tmp_size
< 48)
3357 size
= is_increase
? 48 : 36;
3358 else if (tmp_size
> 48 && tmp_size
< 72)
3359 size
= is_increase
? 72 : 48;
3360 else if (tmp_size
> 72 && tmp_size
< 80)
3361 size
= is_increase
? 80 : 72;
3362 else if (tmp_size
> 80 && tmp_size
< 1638)
3363 size
= 10 * (is_increase
? (tmp_size
/ 10 + 1) : (tmp_size
/ 10));
3364 else if (tmp_size
>= 1638)
3369 cf
.yHeight
= size
* 20; /* convert twips to points */
3370 ME_SetSelectionCharFormat(editor
, &cf
);
3371 ME_CommitUndo(editor
);
3372 ME_WrapMarkedParagraphs(editor
);
3373 ME_UpdateScrollBar(editor
);
3379 return set_selection( editor
, wParam
, lParam
);
3381 case EM_SETSCROLLPOS
:
3383 POINT
*point
= (POINT
*)lParam
;
3384 scroll_abs( editor
, point
->x
, point
->y
, TRUE
);
3387 case EM_AUTOURLDETECT
:
3389 if (wParam
==1 || wParam
==0)
3391 editor
->AutoURLDetect_bEnable
= (BOOL
)wParam
;
3394 return E_INVALIDARG
;
3396 case EM_GETAUTOURLDETECT
:
3398 return editor
->AutoURLDetect_bEnable
;
3402 CHARRANGE range
= *(CHARRANGE
*)lParam
;
3404 return set_selection( editor
, range
.cpMin
, range
.cpMax
);
3409 SETTEXTEX
*pStruct
= (SETTEXTEX
*)wParam
;
3413 BOOL bRtf
, bUnicode
, bSelection
, bUTF8
;
3414 int oldModify
= editor
->nModifyStep
;
3415 static const char utf8_bom
[] = {0xef, 0xbb, 0xbf};
3417 if (!pStruct
) return 0;
3419 /* If we detect ascii rtf at the start of the string,
3420 * we know it isn't unicode. */
3421 bRtf
= (lParam
&& (!strncmp((char *)lParam
, "{\\rtf", 5) ||
3422 !strncmp((char *)lParam
, "{\\urtf", 6)));
3423 bUnicode
= !bRtf
&& pStruct
->codepage
== CP_UNICODE
;
3424 bUTF8
= (lParam
&& (!strncmp((char *)lParam
, utf8_bom
, 3)));
3426 TRACE("EM_SETTEXTEX - %s, flags %ld, cp %d\n",
3427 bUnicode
? debugstr_w((LPCWSTR
)lParam
) : debugstr_a((LPCSTR
)lParam
),
3428 pStruct
->flags
, pStruct
->codepage
);
3430 bSelection
= (pStruct
->flags
& ST_SELECTION
) != 0;
3432 int nStartCursor
= ME_GetSelectionOfs(editor
, &from
, &to
);
3433 style
= ME_GetSelectionInsertStyle(editor
);
3434 ME_InternalDeleteText(editor
, &editor
->pCursors
[nStartCursor
], to
- from
, FALSE
);
3437 ME_SetCursorToStart(editor
, &start
);
3438 ME_InternalDeleteText(editor
, &start
, ME_GetTextLength(editor
), FALSE
);
3439 style
= editor
->pBuffer
->pDefaultStyle
;
3443 ME_StreamInRTFString(editor
, bSelection
, (char *)lParam
);
3445 /* FIXME: The length returned doesn't include the rtf control
3446 * characters, only the actual text. */
3447 len
= lParam
? strlen((char *)lParam
) : 0;
3450 if (bUTF8
&& !bUnicode
) {
3451 wszText
= ME_ToUnicode(CP_UTF8
, (void *)(lParam
+3), &len
);
3452 ME_InsertTextFromCursor(editor
, 0, wszText
, len
, style
);
3453 ME_EndToUnicode(CP_UTF8
, wszText
);
3455 wszText
= ME_ToUnicode(pStruct
->codepage
, (void *)lParam
, &len
);
3456 ME_InsertTextFromCursor(editor
, 0, wszText
, len
, style
);
3457 ME_EndToUnicode(pStruct
->codepage
, wszText
);
3462 ME_ReleaseStyle(style
);
3463 ME_UpdateSelectionLinkAttribute(editor
);
3467 ME_SetCursorToStart(editor
, &cursor
);
3468 ME_UpdateLinkAttribute(editor
, &cursor
, INT_MAX
);
3470 ME_CommitUndo(editor
);
3471 if (!(pStruct
->flags
& ST_KEEPUNDO
))
3473 editor
->nModifyStep
= oldModify
;
3474 ME_EmptyUndoStack(editor
);
3476 ME_UpdateRepaint(editor
, FALSE
);
3479 case EM_SELECTIONTYPE
:
3480 return ME_GetSelectionType(editor
);
3482 return editor
->nModifyStep
== 0 ? 0 : -1;
3486 editor
->nModifyStep
= 1;
3488 editor
->nModifyStep
= 0;
3492 case EM_SETEVENTMASK
:
3494 DWORD nOldMask
= editor
->nEventMask
;
3496 editor
->nEventMask
= lParam
;
3499 case EM_GETEVENTMASK
:
3500 return editor
->nEventMask
;
3501 case EM_SETCHARFORMAT
:
3502 return handle_EM_SETCHARFORMAT( editor
, wParam
, (CHARFORMAT2W
*)lParam
);
3503 case EM_GETCHARFORMAT
:
3505 CHARFORMAT2W tmp
, *dst
= (CHARFORMAT2W
*)lParam
;
3506 if (dst
->cbSize
!= sizeof(CHARFORMATA
) &&
3507 dst
->cbSize
!= sizeof(CHARFORMATW
) &&
3508 dst
->cbSize
!= sizeof(CHARFORMAT2A
) &&
3509 dst
->cbSize
!= sizeof(CHARFORMAT2W
))
3511 tmp
.cbSize
= sizeof(tmp
);
3513 ME_GetDefaultCharFormat(editor
, &tmp
);
3515 ME_GetSelectionCharFormat(editor
, &tmp
);
3516 cf2w_to_cfany(dst
, &tmp
);
3519 case EM_SETPARAFORMAT
:
3521 BOOL result
= editor_set_selection_para_fmt( editor
, (PARAFORMAT2
*)lParam
);
3522 ME_WrapMarkedParagraphs(editor
);
3523 ME_UpdateScrollBar(editor
);
3524 ME_CommitUndo(editor
);
3527 case EM_GETPARAFORMAT
:
3528 editor_get_selection_para_fmt( editor
, (PARAFORMAT2
*)lParam
);
3529 return ((PARAFORMAT2
*)lParam
)->dwMask
;
3530 case EM_GETFIRSTVISIBLELINE
:
3532 ME_Paragraph
*para
= editor_first_para( editor
);
3534 int y
= editor
->vert_si
.nPos
;
3537 while (para_next( para
))
3539 if (y
< para
->pt
.y
+ para
->nHeight
) break;
3540 count
+= para
->nRows
;
3541 para
= para_next( para
);
3544 row
= para_first_row( para
);
3547 if (y
< para
->pt
.y
+ row
->pt
.y
+ row
->nHeight
) break;
3549 row
= row_next( row
);
3553 case EM_HIDESELECTION
:
3555 editor
->bHideSelection
= (wParam
!= 0);
3556 ME_InvalidateSelection(editor
);
3561 if (!(editor
->props
& TXTBIT_MULTILINE
))
3563 ME_ScrollDown( editor
, lParam
* get_default_line_height( editor
) );
3569 int nStartCursor
= ME_GetSelectionOfs(editor
, &from
, &to
);
3570 ME_InternalDeleteText(editor
, &editor
->pCursors
[nStartCursor
], to
-from
, FALSE
);
3571 ME_CommitUndo(editor
);
3572 ME_UpdateRepaint(editor
, TRUE
);
3577 WCHAR
*text
= (WCHAR
*)lParam
;
3578 int len
= text
? lstrlenW( text
) : 0;
3580 TRACE( "EM_REPLACESEL - %s\n", debugstr_w( text
) );
3581 ME_ReplaceSel( editor
, !!wParam
, text
, len
);
3584 case EM_SCROLLCARET
:
3585 editor_ensure_visible( editor
, &editor
->pCursors
[0] );
3592 BOOL bRepaint
= LOWORD(lParam
);
3595 wParam
= (WPARAM
)GetStockObject(SYSTEM_FONT
);
3597 if (!GetObjectW((HGDIOBJ
)wParam
, sizeof(LOGFONTW
), &lf
))
3600 hDC
= ITextHost_TxGetDC(editor
->texthost
);
3601 ME_CharFormatFromLogFont(hDC
, &lf
, &fmt
);
3602 ITextHost_TxReleaseDC(editor
->texthost
, hDC
);
3603 if (editor
->mode
& TM_RICHTEXT
) {
3605 ME_SetCursorToStart(editor
, &start
);
3606 ME_SetCharFormat(editor
, &start
, NULL
, &fmt
);
3608 ME_SetDefaultCharFormat(editor
, &fmt
);
3610 ME_CommitUndo(editor
);
3611 editor_mark_rewrap_all( editor
);
3612 ME_WrapMarkedParagraphs(editor
);
3613 ME_UpdateScrollBar(editor
);
3621 ME_SetCursorToStart(editor
, &cursor
);
3622 ME_InternalDeleteText(editor
, &cursor
, ME_GetTextLength(editor
), FALSE
);
3625 TRACE("WM_SETTEXT lParam==%Ix\n",lParam
);
3626 if (!strncmp((char *)lParam
, "{\\rtf", 5) ||
3627 !strncmp((char *)lParam
, "{\\urtf", 6))
3629 /* Undocumented: WM_SETTEXT supports RTF text */
3630 ME_StreamInRTFString(editor
, 0, (char *)lParam
);
3633 ME_SetText( editor
, (void*)lParam
, TRUE
);
3636 TRACE("WM_SETTEXT - NULL\n");
3637 ME_SetCursorToStart(editor
, &cursor
);
3638 ME_UpdateLinkAttribute(editor
, &cursor
, INT_MAX
);
3639 set_selection_cursors(editor
, 0, 0);
3640 editor
->nModifyStep
= 0;
3641 ME_CommitUndo(editor
);
3642 ME_EmptyUndoStack(editor
);
3643 ME_UpdateRepaint(editor
, FALSE
);
3647 return paste_special( editor
, 0, NULL
, TRUE
);
3649 case WM_MBUTTONDOWN
:
3653 case EM_PASTESPECIAL
:
3654 paste_special( editor
, wParam
, (REPASTESPECIAL
*)lParam
, FALSE
);
3658 copy_or_cut(editor
, msg
== WM_CUT
);
3660 case WM_GETTEXTLENGTH
:
3662 GETTEXTLENGTHEX how
;
3663 how
.flags
= GTL_CLOSE
| (editor
->bEmulateVersion10
? 0 : GTL_USECRLF
) | GTL_NUMCHARS
;
3664 how
.codepage
= CP_UNICODE
;
3665 return ME_GetTextLengthEx(editor
, &how
);
3667 case EM_GETTEXTLENGTHEX
:
3668 return ME_GetTextLengthEx(editor
, (GETTEXTLENGTHEX
*)wParam
);
3672 ex
.cb
= wParam
* sizeof(WCHAR
);
3673 ex
.flags
= GT_USECRLF
;
3674 ex
.codepage
= CP_UNICODE
;
3675 ex
.lpDefaultChar
= NULL
;
3676 ex
.lpUsedDefChar
= NULL
;
3677 return ME_GetTextEx(editor
, &ex
, lParam
);
3680 return ME_GetTextEx(editor
, (GETTEXTEX
*)wParam
, lParam
);
3684 int nStartCur
= ME_GetSelectionOfs(editor
, &nFrom
, &nTo
);
3685 ME_Cursor
*from
= &editor
->pCursors
[nStartCur
];
3686 return get_text_range( editor
, (WCHAR
*)lParam
, from
, nTo
- nFrom
);
3688 case EM_GETSCROLLPOS
:
3690 POINT
*point
= (POINT
*)lParam
;
3691 point
->x
= editor
->horz_si
.nPos
;
3692 point
->y
= editor
->vert_si
.nPos
;
3693 /* 16-bit scaled value is returned as stored in scrollinfo */
3694 if (editor
->horz_si
.nMax
> 0xffff)
3695 point
->x
= MulDiv(point
->x
, 0xffff, editor
->horz_si
.nMax
);
3696 if (editor
->vert_si
.nMax
> 0xffff)
3697 point
->y
= MulDiv(point
->y
, 0xffff, editor
->vert_si
.nMax
);
3700 case EM_GETTEXTRANGE
:
3702 TEXTRANGEW
*rng
= (TEXTRANGEW
*)lParam
;
3704 int nStart
= rng
->chrg
.cpMin
;
3705 int nEnd
= rng
->chrg
.cpMax
;
3706 int textlength
= ME_GetTextLength(editor
);
3708 TRACE( "EM_GETTEXTRANGE min = %ld max = %ld textlength = %d\n", rng
->chrg
.cpMin
, rng
->chrg
.cpMax
, textlength
);
3709 if (nStart
< 0) return 0;
3710 if ((nStart
== 0 && nEnd
== -1) || nEnd
> textlength
)
3712 if (nStart
>= nEnd
) return 0;
3714 cursor_from_char_ofs( editor
, nStart
, &start
);
3715 return get_text_range( editor
, rng
->lpstrText
, &start
, nEnd
- nStart
);
3721 const unsigned int nMaxChars
= *(WORD
*) lParam
;
3722 unsigned int nCharsLeft
= nMaxChars
;
3723 char *dest
= (char *) lParam
;
3724 ME_Cursor start
, end
;
3726 TRACE( "EM_GETLINE: row=%d, nMaxChars=%d\n", (int)wParam
, nMaxChars
);
3728 row
= row_from_row_number( editor
, wParam
);
3729 if (row
== NULL
) return 0;
3731 row_first_cursor( row
, &start
);
3732 row_end_cursor( row
, &end
, TRUE
);
3738 int ofs
= (run
== start
.run
) ? start
.nOffset
: 0;
3739 int len
= (run
== end
.run
) ? end
.nOffset
: run
->len
;
3741 str
= get_text( run
, ofs
);
3742 nCopy
= min( nCharsLeft
, len
);
3744 memcpy(dest
, str
, nCopy
* sizeof(WCHAR
));
3745 dest
+= nCopy
* sizeof(WCHAR
);
3746 nCharsLeft
-= nCopy
;
3747 if (run
== end
.run
) break;
3748 run
= row_next_run( row
, run
);
3751 /* append line termination, space allowing */
3752 if (nCharsLeft
> 0) *((WCHAR
*)dest
) = '\0';
3754 TRACE("EM_GETLINE: got %u characters\n", nMaxChars
- nCharsLeft
);
3755 return nMaxChars
- nCharsLeft
;
3757 case EM_GETLINECOUNT
:
3759 int count
= editor
->total_rows
;
3760 ME_Run
*prev_run
, *last_run
;
3762 last_run
= para_end_run( para_prev( editor_end_para( editor
) ) );
3763 prev_run
= run_prev_all_paras( last_run
);
3765 if (editor
->bEmulateVersion10
&& prev_run
&& last_run
->nCharOfs
== 0 &&
3766 prev_run
->len
== 1 && *get_text( prev_run
, 0 ) == '\r')
3768 /* In 1.0 emulation, the last solitary \r at the very end of the text
3769 (if one exists) is NOT a line break.
3770 FIXME: this is an ugly hack. This should have a more regular model. */
3774 count
= max(1, count
);
3775 TRACE("EM_GETLINECOUNT: count==%d\n", count
);
3778 case EM_LINEFROMCHAR
:
3780 if (wParam
== -1) wParam
= ME_GetCursorOfs( editor
->pCursors
+ 1 );
3781 return row_number_from_char_ofs( editor
, wParam
);
3783 case EM_EXLINEFROMCHAR
:
3785 if (lParam
== -1) lParam
= ME_GetCursorOfs( editor
->pCursors
+ 1 );
3786 return row_number_from_char_ofs( editor
, lParam
);
3794 if (wParam
== -1) row
= row_from_cursor( editor
->pCursors
);
3795 else row
= row_from_row_number( editor
, wParam
);
3796 if (!row
) return -1;
3798 row_first_cursor( row
, &cursor
);
3799 ofs
= ME_GetCursorOfs( &cursor
);
3800 TRACE( "EM_LINEINDEX: nCharOfs==%d\n", ofs
);
3806 int start_ofs
, end_ofs
;
3809 if (wParam
> ME_GetTextLength(editor
))
3813 FIXME("EM_LINELENGTH: returning number of unselected characters on lines with selection unsupported.\n");
3816 cursor_from_char_ofs( editor
, wParam
, &cursor
);
3817 row
= row_from_cursor( &cursor
);
3818 row_first_cursor( row
, &cursor
);
3819 start_ofs
= ME_GetCursorOfs( &cursor
);
3820 row_end_cursor( row
, &cursor
, FALSE
);
3821 end_ofs
= ME_GetCursorOfs( &cursor
);
3822 TRACE( "EM_LINELENGTH(%Id)==%d\n", wParam
, end_ofs
- start_ofs
);
3823 return end_ofs
- start_ofs
;
3825 case EM_EXLIMITTEXT
:
3827 if ((int)lParam
< 0)
3830 editor
->nTextLimit
= 65536;
3832 editor
->nTextLimit
= (int) lParam
;
3838 editor
->nTextLimit
= 65536;
3840 editor
->nTextLimit
= (int) wParam
;
3843 case EM_GETLIMITTEXT
:
3845 return editor
->nTextLimit
;
3850 FINDTEXTW
*ft
= (FINDTEXTW
*)lParam
;
3851 return ME_FindText(editor
, wParam
, &ft
->chrg
, ft
->lpstrText
, NULL
);
3854 case EM_FINDTEXTEXW
:
3856 FINDTEXTEXW
*ex
= (FINDTEXTEXW
*)lParam
;
3857 return ME_FindText(editor
, wParam
, &ex
->chrg
, ex
->lpstrText
, &ex
->chrgText
);
3860 if (!wParam
|| !lParam
)
3862 *(int *)wParam
= editor
->nZoomNumerator
;
3863 *(int *)lParam
= editor
->nZoomDenominator
;
3866 return ME_SetZoom(editor
, wParam
, lParam
);
3867 case EM_CHARFROMPOS
:
3870 POINTL
*pt
= (POINTL
*)lParam
;
3872 cursor_from_coords(editor
, pt
->x
, pt
->y
, &cursor
);
3873 return ME_GetCursorOfs(&cursor
);
3875 case EM_POSFROMCHAR
:
3878 int nCharOfs
, nLength
;
3882 /* detect which API version we're dealing with */
3883 if (wParam
>= 0x40000)
3885 nLength
= ME_GetTextLength(editor
);
3886 nCharOfs
= min(nCharOfs
, nLength
);
3887 nCharOfs
= max(nCharOfs
, 0);
3889 cursor_from_char_ofs( editor
, nCharOfs
, &cursor
);
3890 pt
.y
= cursor
.run
->pt
.y
;
3891 pt
.x
= cursor
.run
->pt
.x
+
3892 ME_PointFromChar( editor
, cursor
.run
, cursor
.nOffset
, TRUE
);
3893 pt
.y
+= cursor
.para
->pt
.y
+ editor
->rcFormat
.top
;
3894 pt
.x
+= editor
->rcFormat
.left
;
3896 pt
.x
-= editor
->horz_si
.nPos
;
3897 pt
.y
-= editor
->vert_si
.nPos
;
3899 if (wParam
>= 0x40000) *(POINTL
*)wParam
= pt
;
3901 return (wParam
>= 0x40000) ? 0 : MAKELONG( pt
.x
, pt
.y
);
3903 case WM_LBUTTONDBLCLK
:
3904 case WM_LBUTTONDOWN
:
3906 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
3907 ITextHost_TxSetFocus(editor
->texthost
);
3908 ME_LButtonDown(editor
, (short)LOWORD(lParam
), (short)HIWORD(lParam
),
3909 ME_CalculateClickCount(editor
, msg
, wParam
, lParam
));
3910 ITextHost_TxSetCapture(editor
->texthost
, TRUE
);
3911 editor
->bMouseCaptured
= TRUE
;
3912 link_notify( editor
, msg
, wParam
, lParam
);
3916 if (editor
->bMouseCaptured
)
3917 ME_MouseMove(editor
, (short)LOWORD(lParam
), (short)HIWORD(lParam
));
3919 link_notify( editor
, msg
, wParam
, lParam
);
3922 if (editor
->bMouseCaptured
) {
3923 ITextHost_TxSetCapture(editor
->texthost
, FALSE
);
3924 editor
->bMouseCaptured
= FALSE
;
3926 if (editor
->nSelectionType
== stDocument
)
3927 editor
->nSelectionType
= stPosition
;
3930 link_notify( editor
, msg
, wParam
, lParam
);
3934 case WM_RBUTTONDOWN
:
3935 case WM_RBUTTONDBLCLK
:
3936 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
3937 link_notify( editor
, msg
, wParam
, lParam
);
3939 case WM_CONTEXTMENU
:
3940 if (!ME_ShowContextMenu(editor
, (short)LOWORD(lParam
), (short)HIWORD(lParam
)))
3944 editor
->bHaveFocus
= TRUE
;
3945 create_caret(editor
);
3946 update_caret(editor
);
3947 ITextHost_TxNotify( editor
->texthost
, EN_SETFOCUS
, NULL
);
3948 if (!editor
->bHideSelection
&& (editor
->props
& TXTBIT_HIDESELECTION
))
3949 ME_InvalidateSelection( editor
);
3952 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
3953 editor
->bHaveFocus
= FALSE
;
3954 editor
->wheel_remain
= 0;
3957 ITextHost_TxNotify( editor
->texthost
, EN_KILLFOCUS
, NULL
);
3958 if (!editor
->bHideSelection
&& (editor
->props
& TXTBIT_HIDESELECTION
))
3959 ME_InvalidateSelection( editor
);
3962 TRACE("editor wnd command = %d\n", LOWORD(wParam
));
3965 if (ME_KeyDown(editor
, LOWORD(wParam
)))
3969 return handle_wm_char( editor
, wParam
, lParam
);
3971 if (wParam
== UNICODE_NOCHAR
) return TRUE
;
3972 if (wParam
<= 0x000fffff)
3974 if (wParam
> 0xffff) /* convert to surrogates */
3977 handle_wm_char( editor
, (wParam
>> 10) + 0xd800, 0 );
3978 handle_wm_char( editor
, (wParam
& 0x03ff) + 0xdc00, 0 );
3981 handle_wm_char( editor
, wParam
, 0 );
3984 case EM_STOPGROUPTYPING
:
3985 ME_CommitUndo(editor
); /* End coalesced undos for typed characters */
3989 const int scrollUnit
= 7;
3991 switch(LOWORD(wParam
))
3994 scroll_abs( editor
, 0, 0, TRUE
);
3997 scroll_abs( editor
, editor
->horz_si
.nMax
- (int)editor
->horz_si
.nPage
,
3998 editor
->vert_si
.nMax
- (int)editor
->vert_si
.nPage
, TRUE
);
4001 ME_ScrollLeft(editor
, scrollUnit
);
4004 ME_ScrollRight(editor
, scrollUnit
);
4007 ME_ScrollLeft(editor
, editor
->sizeWindow
.cx
);
4010 ME_ScrollRight(editor
, editor
->sizeWindow
.cx
);
4013 case SB_THUMBPOSITION
:
4015 int pos
= HIWORD(wParam
);
4016 if (editor
->horz_si
.nMax
> 0xffff)
4017 pos
= MulDiv(pos
, editor
->horz_si
.nMax
, 0xffff);
4018 scroll_h_abs( editor
, pos
, FALSE
);
4024 case EM_SCROLL
: /* fall through */
4028 int lineHeight
= get_default_line_height( editor
);
4030 origNPos
= editor
->vert_si
.nPos
;
4032 switch(LOWORD(wParam
))
4035 scroll_abs( editor
, 0, 0, TRUE
);
4038 scroll_abs( editor
, editor
->horz_si
.nMax
- (int)editor
->horz_si
.nPage
,
4039 editor
->vert_si
.nMax
- (int)editor
->vert_si
.nPage
, TRUE
);
4042 ME_ScrollUp(editor
,lineHeight
);
4045 ME_ScrollDown(editor
,lineHeight
);
4048 ME_ScrollUp(editor
,editor
->sizeWindow
.cy
);
4051 ME_ScrollDown(editor
,editor
->sizeWindow
.cy
);
4054 case SB_THUMBPOSITION
:
4056 int pos
= HIWORD(wParam
);
4057 if (editor
->vert_si
.nMax
> 0xffff)
4058 pos
= MulDiv(pos
, editor
->vert_si
.nMax
, 0xffff);
4059 scroll_v_abs( editor
, pos
, FALSE
);
4063 if (msg
== EM_SCROLL
)
4064 return 0x00010000 | (((editor
->vert_si
.nPos
- origNPos
)/lineHeight
) & 0xffff);
4069 int delta
= GET_WHEEL_DELTA_WPARAM( wParam
);
4070 BOOL ctrl_is_down
= GetKeyState( VK_CONTROL
) & 0x8000;
4072 /* if scrolling changes direction, ignore left overs */
4073 if ((delta
< 0 && editor
->wheel_remain
< 0) ||
4074 (delta
> 0 && editor
->wheel_remain
> 0))
4075 editor
->wheel_remain
+= delta
;
4077 editor
->wheel_remain
= delta
;
4079 if (editor
->wheel_remain
)
4083 if (!editor
->nZoomNumerator
|| !editor
->nZoomDenominator
)
4087 numerator
= editor
->nZoomNumerator
* 100 / editor
->nZoomDenominator
;
4089 numerator
+= calc_wheel_change( &editor
->wheel_remain
, 10 );
4090 if (numerator
>= 10 && numerator
<= 500)
4091 ME_SetZoom(editor
, numerator
, 100);
4096 SystemParametersInfoW( SPI_GETWHEELSCROLLLINES
, 0, &max_lines
, 0 );
4098 lines
= calc_wheel_change( &editor
->wheel_remain
, (int)max_lines
);
4100 ME_ScrollDown( editor
, -lines
* get_default_line_height( editor
) );
4105 case EM_REQUESTRESIZE
:
4106 ME_SendRequestResize(editor
, TRUE
);
4108 /* IME messages to make richedit controls IME aware */
4109 case WM_IME_SETCONTEXT
:
4110 case WM_IME_CONTROL
:
4112 case WM_IME_COMPOSITIONFULL
:
4114 case WM_IME_STARTCOMPOSITION
:
4116 ME_DeleteSelection(editor
);
4117 editor
->imeStartIndex
= ME_GetCursorOfs(&editor
->pCursors
[0]);
4118 ME_CommitUndo(editor
);
4119 ME_UpdateRepaint(editor
, FALSE
);
4122 case WM_IME_COMPOSITION
:
4126 ME_Style
*style
= style_get_insert_style( editor
, editor
->pCursors
);
4127 hIMC
= ITextHost_TxImmGetContext(editor
->texthost
);
4128 ME_DeleteSelection(editor
);
4129 ME_SaveTempStyle(editor
, style
);
4130 if (lParam
& (GCS_RESULTSTR
|GCS_COMPSTR
))
4132 LPWSTR lpCompStr
= NULL
;
4134 DWORD dwIndex
= lParam
& GCS_RESULTSTR
;
4136 dwIndex
= GCS_COMPSTR
;
4138 dwBufLen
= ImmGetCompositionStringW(hIMC
, dwIndex
, NULL
, 0);
4139 lpCompStr
= malloc(dwBufLen
+ sizeof(WCHAR
));
4140 ImmGetCompositionStringW(hIMC
, dwIndex
, lpCompStr
, dwBufLen
);
4141 lpCompStr
[dwBufLen
/sizeof(WCHAR
)] = 0;
4142 ME_InsertTextFromCursor(editor
,0,lpCompStr
,dwBufLen
/sizeof(WCHAR
),style
);
4145 if (dwIndex
== GCS_COMPSTR
)
4146 set_selection_cursors(editor
,editor
->imeStartIndex
,
4147 editor
->imeStartIndex
+ dwBufLen
/sizeof(WCHAR
));
4149 editor
->imeStartIndex
= ME_GetCursorOfs(&editor
->pCursors
[0]);
4151 ME_ReleaseStyle(style
);
4152 ME_CommitUndo(editor
);
4153 ME_UpdateRepaint(editor
, FALSE
);
4156 case WM_IME_ENDCOMPOSITION
:
4158 ME_DeleteSelection(editor
);
4159 editor
->imeStartIndex
=-1;
4162 case EM_GETOLEINTERFACE
:
4163 IRichEditOle_AddRef( editor
->richole
);
4164 *(IRichEditOle
**)lParam
= editor
->richole
;
4167 case EM_SETOLECALLBACK
:
4168 if(editor
->lpOleCallback
)
4169 IRichEditOleCallback_Release(editor
->lpOleCallback
);
4170 editor
->lpOleCallback
= (IRichEditOleCallback
*)lParam
;
4171 if(editor
->lpOleCallback
)
4172 IRichEditOleCallback_AddRef(editor
->lpOleCallback
);
4174 case EM_GETWORDBREAKPROC
:
4175 return (LRESULT
)editor
->pfnWordBreak
;
4176 case EM_SETWORDBREAKPROC
:
4178 EDITWORDBREAKPROCW pfnOld
= editor
->pfnWordBreak
;
4180 editor
->pfnWordBreak
= (EDITWORDBREAKPROCW
)lParam
;
4181 return (LRESULT
)pfnOld
;
4183 case EM_GETTEXTMODE
:
4184 return editor
->mode
;
4185 case EM_SETTEXTMODE
:
4190 if (ME_GetTextLength(editor
) ||
4191 !list_empty( &editor
->undo_stack
) || !list_empty( &editor
->redo_stack
))
4192 return E_UNEXPECTED
;
4194 /* Check for mutually exclusive flags in adjacent bits of wParam */
4195 if ((wParam
& (TM_RICHTEXT
| TM_MULTILEVELUNDO
| TM_MULTICODEPAGE
)) &
4196 (wParam
& (TM_PLAINTEXT
| TM_SINGLELEVELUNDO
| TM_SINGLECODEPAGE
)) << 1)
4197 return E_INVALIDARG
;
4199 if (wParam
& (TM_RICHTEXT
| TM_PLAINTEXT
))
4201 mask
|= TM_RICHTEXT
| TM_PLAINTEXT
;
4202 changes
|= wParam
& (TM_RICHTEXT
| TM_PLAINTEXT
);
4203 if (wParam
& TM_PLAINTEXT
) {
4204 /* Clear selection since it should be possible to select the
4205 * end of text run for rich text */
4206 ME_InvalidateSelection(editor
);
4207 ME_SetCursorToStart(editor
, &editor
->pCursors
[0]);
4208 editor
->pCursors
[1] = editor
->pCursors
[0];
4209 /* plain text can only have the default style. */
4210 ME_ClearTempStyle(editor
);
4211 ME_AddRefStyle(editor
->pBuffer
->pDefaultStyle
);
4212 ME_ReleaseStyle( editor
->pCursors
[0].run
->style
);
4213 editor
->pCursors
[0].run
->style
= editor
->pBuffer
->pDefaultStyle
;
4216 /* FIXME: Currently no support for undo level and code page options */
4217 editor
->mode
= (editor
->mode
& ~mask
) | changes
;
4220 case EM_SETTARGETDEVICE
:
4223 BOOL
new = (lParam
== 0 && (editor
->props
& TXTBIT_MULTILINE
));
4224 if (editor
->nAvailWidth
|| editor
->bWordWrap
!= new)
4226 editor
->bWordWrap
= new;
4227 editor
->nAvailWidth
= 0; /* wrap to client area */
4228 ME_RewrapRepaint(editor
);
4231 int width
= max(0, lParam
);
4232 if ((editor
->props
& TXTBIT_MULTILINE
) &&
4233 (!editor
->bWordWrap
|| editor
->nAvailWidth
!= width
))
4235 editor
->nAvailWidth
= width
;
4236 editor
->bWordWrap
= TRUE
;
4237 ME_RewrapRepaint(editor
);
4239 FIXME("EM_SETTARGETDEVICE doesn't use non-NULL target devices\n");
4244 *phresult
= S_FALSE
;
4250 /* Fill buffer with srcChars unicode characters from the start cursor.
4252 * buffer: destination buffer
4253 * buflen: length of buffer in characters excluding the NULL terminator.
4254 * start: start of editor text to copy into buffer.
4255 * srcChars: Number of characters to use from the editor text.
4256 * bCRLF: if true, replaces all end of lines with \r\n pairs.
4258 * returns the number of characters written excluding the NULL terminator.
4260 * The written text is always NULL terminated.
4262 int ME_GetTextW(ME_TextEditor
*editor
, WCHAR
*buffer
, int buflen
,
4263 const ME_Cursor
*start
, int srcChars
, BOOL bCRLF
,
4266 ME_Run
*run
, *next_run
;
4267 const WCHAR
*pStart
= buffer
;
4271 /* bCRLF flag is only honored in 2.0 and up. 1.0 must always return text verbatim */
4272 if (editor
->bEmulateVersion10
) bCRLF
= FALSE
;
4275 next_run
= run_next_all_paras( run
);
4277 nLen
= run
->len
- start
->nOffset
;
4278 str
= get_text( run
, start
->nOffset
);
4280 while (srcChars
&& buflen
&& next_run
)
4282 if (bCRLF
&& run
->nFlags
& MERF_ENDPARA
&& ~run
->nFlags
& MERF_ENDCELL
)
4284 if (buflen
== 1) break;
4285 /* FIXME: native fails to reduce srcChars here for WM_GETTEXT or
4286 * EM_GETTEXTEX, however, this is done for copying text which
4287 * also uses this function. */
4288 srcChars
-= min(nLen
, srcChars
);
4294 nLen
= min(nLen
, srcChars
);
4298 nLen
= min(nLen
, buflen
);
4301 CopyMemory(buffer
, str
, sizeof(WCHAR
) * nLen
);
4306 next_run
= run_next_all_paras( run
);
4309 str
= get_text( run
, 0 );
4311 /* append '\r' to the last paragraph. */
4312 if (run
== para_end_run( para_prev( editor_end_para( editor
) ) ) && bEOP
)
4318 return buffer
- pStart
;
4321 static int __cdecl
wchar_comp( const void *key
, const void *elem
)
4323 return *(const WCHAR
*)key
- *(const WCHAR
*)elem
;
4326 /* neutral characters end the url if the next non-neutral character is a space character,
4327 otherwise they are included in the url. */
4328 static BOOL
isurlneutral( WCHAR c
)
4330 /* NB this list is sorted */
4331 static const WCHAR neutral_chars
[] = L
"!\"'(),-.:;<>?[]{}";
4333 /* Some shortcuts */
4334 if (isalnum( c
)) return FALSE
;
4335 if (c
> L
'}') return FALSE
;
4337 return !!bsearch( &c
, neutral_chars
, ARRAY_SIZE( neutral_chars
) - 1, sizeof(c
), wchar_comp
);
4341 * This proc takes a selection, and scans it forward in order to select the span
4342 * of a possible URL candidate. A possible URL candidate must start with isalnum
4343 * or one of the following special characters: *|/\+%#@ and must consist entirely
4344 * of the characters allowed to start the URL, plus : (colon) which may occur
4345 * at most once, and not at either end.
4347 static BOOL
ME_FindNextURLCandidate(ME_TextEditor
*editor
,
4348 const ME_Cursor
*start
,
4350 ME_Cursor
*candidate_min
,
4351 ME_Cursor
*candidate_max
)
4353 ME_Cursor cursor
= *start
, neutral_end
, space_end
;
4354 BOOL candidateStarted
= FALSE
, quoted
= FALSE
;
4359 WCHAR
*str
= get_text( cursor
.run
, 0 );
4360 int run_len
= cursor
.run
->len
;
4362 nChars
-= run_len
- cursor
.nOffset
;
4364 /* Find start of candidate */
4365 if (!candidateStarted
)
4367 while (cursor
.nOffset
< run_len
)
4369 c
= str
[cursor
.nOffset
];
4370 if (!iswspace( c
) && !isurlneutral( c
))
4372 *candidate_min
= cursor
;
4373 candidateStarted
= TRUE
;
4374 neutral_end
.para
= NULL
;
4375 space_end
.para
= NULL
;
4379 quoted
= (c
== '<');
4384 /* Find end of candidate */
4385 if (candidateStarted
)
4387 while (cursor
.nOffset
< run_len
)
4389 c
= str
[cursor
.nOffset
];
4392 if (quoted
&& c
!= '\r')
4394 if (!space_end
.para
)
4396 if (neutral_end
.para
)
4397 space_end
= neutral_end
;
4405 else if (isurlneutral( c
))
4407 if (quoted
&& c
== '>')
4409 neutral_end
.para
= NULL
;
4410 space_end
.para
= NULL
;
4413 if (!neutral_end
.para
)
4414 neutral_end
= cursor
;
4417 neutral_end
.para
= NULL
;
4424 if (!cursor_next_run( &cursor
, TRUE
))
4429 if (candidateStarted
)
4432 *candidate_max
= space_end
;
4433 else if (neutral_end
.para
)
4434 *candidate_max
= neutral_end
;
4436 *candidate_max
= cursor
;
4439 *candidate_max
= *candidate_min
= cursor
;
4444 * This proc evaluates the selection and returns TRUE if it can be considered an URL
4446 static BOOL
ME_IsCandidateAnURL(ME_TextEditor
*editor
, const ME_Cursor
*start
, int nChars
)
4448 #define MAX_PREFIX_LEN 9
4449 #define X(str) str, ARRAY_SIZE(str) - 1
4451 const WCHAR text
[MAX_PREFIX_LEN
];
4468 WCHAR bufferW
[MAX_PREFIX_LEN
+ 1];
4471 ME_GetTextW(editor
, bufferW
, MAX_PREFIX_LEN
, start
, nChars
, FALSE
, FALSE
);
4472 for (i
= 0; i
< ARRAY_SIZE(prefixes
); i
++)
4474 if (nChars
< prefixes
[i
].length
) continue;
4475 if (!memcmp(prefixes
[i
].text
, bufferW
, prefixes
[i
].length
* sizeof(WCHAR
)))
4479 #undef MAX_PREFIX_LEN
4483 * This proc walks through the indicated selection and evaluates whether each
4484 * section identified by ME_FindNextURLCandidate and in-between sections have
4485 * their proper CFE_LINK attributes set or unset. If the CFE_LINK attribute is
4486 * not what it is supposed to be, this proc sets or unsets it as appropriate.
4488 * Since this function can cause runs to be split, do not depend on the value
4489 * of the start cursor at the end of the function.
4491 * nChars may be set to INT_MAX to update to the end of the text.
4493 * Returns TRUE if at least one section was modified.
4495 static BOOL
ME_UpdateLinkAttribute(ME_TextEditor
*editor
, ME_Cursor
*start
, int nChars
)
4497 BOOL modified
= FALSE
;
4498 ME_Cursor startCur
= *start
;
4500 if (!editor
->AutoURLDetect_bEnable
) return FALSE
;
4505 ME_Cursor candidateStart
, candidateEnd
;
4507 if (ME_FindNextURLCandidate(editor
, &startCur
, nChars
,
4508 &candidateStart
, &candidateEnd
))
4510 /* Section before candidate is not an URL */
4511 int cMin
= ME_GetCursorOfs(&candidateStart
);
4512 int cMax
= ME_GetCursorOfs(&candidateEnd
);
4514 if (!ME_IsCandidateAnURL(editor
, &candidateStart
, cMax
- cMin
))
4515 candidateStart
= candidateEnd
;
4516 nChars
-= cMax
- ME_GetCursorOfs(&startCur
);
4520 /* No more candidates until end of selection */
4524 if (startCur
.run
!= candidateStart
.run
||
4525 startCur
.nOffset
!= candidateStart
.nOffset
)
4527 /* CFE_LINK effect should be consistently unset */
4528 link
.cbSize
= sizeof(link
);
4529 ME_GetCharFormat(editor
, &startCur
, &candidateStart
, &link
);
4530 if (!(link
.dwMask
& CFM_LINK
) || (link
.dwEffects
& CFE_LINK
))
4532 /* CFE_LINK must be unset from this range */
4533 memset(&link
, 0, sizeof(CHARFORMAT2W
));
4534 link
.cbSize
= sizeof(link
);
4535 link
.dwMask
= CFM_LINK
;
4537 ME_SetCharFormat(editor
, &startCur
, &candidateStart
, &link
);
4538 /* Update candidateEnd since setting character formats may split
4539 * runs, which can cause a cursor to be at an invalid offset within
4541 while (candidateEnd
.nOffset
>= candidateEnd
.run
->len
)
4543 candidateEnd
.nOffset
-= candidateEnd
.run
->len
;
4544 candidateEnd
.run
= run_next_all_paras( candidateEnd
.run
);
4549 if (candidateStart
.run
!= candidateEnd
.run
||
4550 candidateStart
.nOffset
!= candidateEnd
.nOffset
)
4552 /* CFE_LINK effect should be consistently set */
4553 link
.cbSize
= sizeof(link
);
4554 ME_GetCharFormat(editor
, &candidateStart
, &candidateEnd
, &link
);
4555 if (!(link
.dwMask
& CFM_LINK
) || !(link
.dwEffects
& CFE_LINK
))
4557 /* CFE_LINK must be set on this range */
4558 memset(&link
, 0, sizeof(CHARFORMAT2W
));
4559 link
.cbSize
= sizeof(link
);
4560 link
.dwMask
= CFM_LINK
;
4561 link
.dwEffects
= CFE_LINK
;
4562 ME_SetCharFormat(editor
, &candidateStart
, &candidateEnd
, &link
);
4566 startCur
= candidateEnd
;
4567 } while (nChars
> 0);