2 * RichEdit - Caret and selection functions.
4 * Copyright 2004 by Krzysztof Foltman
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 WINE_DEFAULT_DEBUG_CHANNEL(richedit
);
26 void ME_GetSelection(ME_TextEditor
*editor
, int *from
, int *to
)
28 *from
= ME_GetCursorOfs(editor
, 0);
29 *to
= ME_GetCursorOfs(editor
, 1);
39 int ME_GetTextLength(ME_TextEditor
*editor
)
41 return ME_CharOfsFromRunOfs(editor
, ME_FindItemBack(editor
->pBuffer
->pLast
, diRun
), 0);
45 int ME_GetTextLengthEx(ME_TextEditor
*editor
, GETTEXTLENGTHEX
*how
)
49 if (how
->flags
& GTL_PRECISE
&& how
->flags
& GTL_CLOSE
)
51 if (how
->flags
& GTL_NUMCHARS
&& how
->flags
& GTL_NUMBYTES
)
54 length
= ME_GetTextLength(editor
);
56 if (how
->flags
& GTL_USECRLF
)
57 length
+= editor
->nParagraphs
;
59 if (how
->flags
& GTL_NUMBYTES
)
63 if (how
->codepage
== 1200)
65 if (how
->flags
& GTL_PRECISE
)
66 FIXME("GTL_PRECISE flag unsupported. Using GTL_CLOSE\n");
67 if (GetCPInfo(how
->codepage
, &cpinfo
))
68 return length
* cpinfo
.MaxCharSize
;
69 ERR("Invalid codepage %u\n", how
->codepage
);
76 void ME_SetSelection(ME_TextEditor
*editor
, int from
, int to
)
78 if (from
== 0 && to
== -1)
80 editor
->pCursors
[1].pRun
= ME_FindItemFwd(editor
->pBuffer
->pFirst
, diRun
);
81 editor
->pCursors
[1].nOffset
= 0;
82 editor
->pCursors
[0].pRun
= ME_FindItemBack(editor
->pBuffer
->pLast
, diRun
);
83 editor
->pCursors
[0].nOffset
= 0;
85 ME_ClearTempStyle(editor
);
90 editor
->pCursors
[1] = editor
->pCursors
[0];
92 ME_ClearTempStyle(editor
);
101 ME_RunOfsFromCharOfs(editor
, from
, &editor
->pCursors
[1].pRun
, &editor
->pCursors
[1].nOffset
);
102 ME_RunOfsFromCharOfs(editor
, to
, &editor
->pCursors
[0].pRun
, &editor
->pCursors
[0].nOffset
);
105 void ME_MoveCaret(ME_TextEditor
*editor
)
107 HDC hDC
= GetDC(editor
->hWnd
);
110 ME_Cursor
*pCursor
= &editor
->pCursors
[0];
111 ME_DisplayItem
*pCursorRun
= pCursor
->pRun
;
112 ME_DisplayItem
*pSizeRun
= pCursor
->pRun
;
114 ME_InitContext(&c
, editor
, hDC
);
115 assert(!pCursor
->nOffset
|| !editor
->bCaretAtEnd
);
117 if (pCursorRun
->type
== diRun
) {
118 ME_DisplayItem
*row
= ME_FindItemBack(pCursorRun
, diStartRowOrParagraph
);
120 ME_DisplayItem
*run
= pCursorRun
;
121 ME_DisplayItem
*para
;
123 if (!pCursor
->nOffset
&& !editor
->bCaretAtEnd
)
125 ME_DisplayItem
*prev
= ME_FindItemBack(pCursorRun
, diRunOrStartRow
);
126 if (prev
->type
== diRun
)
129 assert(row
->type
== diStartRow
); /* paragraph -> run without start row ?*/
130 para
= ME_FindItemBack(row
, diParagraph
);
131 if (editor
->bCaretAtEnd
&& !pCursor
->nOffset
&&
132 run
== ME_FindItemFwd(row
, diRun
))
134 ME_DisplayItem
*tmp
= ME_FindItemBack(row
, diRunOrParagraph
);
135 if (tmp
->type
== diRun
)
137 row
= ME_FindItemBack(tmp
, diStartRow
);
138 pSizeRun
= run
= tmp
;
139 sz
= ME_GetRunSize(&c
, ¶
->member
.para
, &run
->member
.run
, ME_StrLen(run
->member
.run
.strText
));
142 if (pCursor
->nOffset
&& !(run
->member
.run
.nFlags
& MERF_SKIPPED
)) {
143 sz
= ME_GetRunSize(&c
, ¶
->member
.para
, &run
->member
.run
, pCursor
->nOffset
);
145 CreateCaret(editor
->hWnd
, NULL
, 0, pSizeRun
->member
.run
.nAscent
+pSizeRun
->member
.run
.nDescent
);
146 SetCaretPos(run
->member
.run
.pt
.x
+sz
.cx
,
147 para
->member
.para
.nYPos
+row
->member
.row
.nBaseline
+pSizeRun
->member
.run
.pt
.y
-pSizeRun
->member
.run
.nAscent
-ME_GetYScrollPos(editor
));
149 assert(0 == "Wrapped paragraph run without a row?");
150 CreateCaret(editor
->hWnd
, NULL
, 0, 10);
155 assert(0 == "Cursor not on a run");
156 CreateCaret(editor
->hWnd
, NULL
, 0, 10); /* FIXME use global font */
159 ME_DestroyContext(&c
);
160 ReleaseDC(editor
->hWnd
, hDC
);
163 void ME_ShowCaret(ME_TextEditor
*ed
)
169 void ME_HideCaret(ME_TextEditor
*ed
)
175 void ME_InternalDeleteText(ME_TextEditor
*editor
, int nOfs
,
184 ME_CursorFromCharOfs(editor
, nOfs
, &c
);
185 run
= &c
.pRun
->member
.run
;
186 if (run
->nFlags
& MERF_ENDPARA
) {
187 if (!ME_FindItemFwd(c
.pRun
, diParagraph
))
191 ME_JoinParagraphs(editor
, ME_GetParagraph(c
.pRun
));
192 /* ME_SkipAndPropagateCharOffset(p->pRun, shift); */
193 ME_CheckCharOffsets(editor
);
200 int nIntendedChars
= nChars
;
201 int nCharsToDelete
= nChars
;
205 ME_FindItemBack(c
.pRun
, diParagraph
)->member
.para
.nFlags
|= MEPF_REWRAP
;
208 ME_StrRelPos(run
->strText
, loc
, &nChars
);
209 /* nChars is the number of characters that should be deleted from the
210 FOLLOWING runs (these AFTER cursor.pRun)
211 nCharsToDelete is a number of chars to delete from THIS run */
212 nCharsToDelete
-= nChars
;
213 shift
-= nCharsToDelete
;
214 TRACE("Deleting %d (intended %d-remaning %d) chars at %d in '%s' (%d)\n",
215 nCharsToDelete
, nIntendedChars
, nChars
, c
.nOffset
,
216 debugstr_w(run
->strText
->szData
), run
->strText
->nLen
);
218 if (!c
.nOffset
&& ME_StrVLen(run
->strText
) == nCharsToDelete
)
220 /* undo = reinsert whole run */
221 /* nOfs is a character offset (from the start of the document
222 to the current (deleted) run */
223 ME_UndoItem
*pUndo
= ME_AddUndoItem(editor
, diUndoInsertRun
, c
.pRun
);
225 pUndo
->di
.member
.run
.nCharOfs
= nOfs
;
229 /* undo = reinsert partial run */
230 ME_UndoItem
*pUndo
= ME_AddUndoItem(editor
, diUndoInsertRun
, c
.pRun
);
232 ME_DestroyString(pUndo
->di
.member
.run
.strText
);
233 pUndo
->di
.member
.run
.nCharOfs
= nOfs
;
234 pUndo
->di
.member
.run
.strText
= ME_MakeStringN(run
->strText
->szData
+c
.nOffset
, nCharsToDelete
);
237 TRACE("Post deletion string: %s (%d)\n", debugstr_w(run
->strText
->szData
), run
->strText
->nLen
);
238 TRACE("Shift value: %d\n", shift
);
239 ME_StrDeleteV(run
->strText
, c
.nOffset
, nCharsToDelete
);
241 /* update cursors (including c) */
242 for (i
=-1; i
<editor
->nCursors
; i
++) {
243 ME_Cursor
*pThisCur
= editor
->pCursors
+ i
;
244 if (i
== -1) pThisCur
= &c
;
245 if (pThisCur
->pRun
== cursor
.pRun
) {
246 if (pThisCur
->nOffset
> cursor
.nOffset
) {
247 if (pThisCur
->nOffset
-cursor
.nOffset
< nCharsToDelete
)
248 pThisCur
->nOffset
= cursor
.nOffset
;
250 pThisCur
->nOffset
-= nCharsToDelete
;
251 assert(pThisCur
->nOffset
>= 0);
252 assert(pThisCur
->nOffset
<= ME_StrVLen(run
->strText
));
254 if (pThisCur
->nOffset
== ME_StrVLen(run
->strText
))
256 pThisCur
->pRun
= ME_FindItemFwd(pThisCur
->pRun
, diRunOrParagraphOrEnd
);
257 assert(pThisCur
->pRun
->type
== diRun
);
258 pThisCur
->nOffset
= 0;
263 /* c = updated data now */
265 if (c
.pRun
== cursor
.pRun
)
266 ME_SkipAndPropagateCharOffset(c
.pRun
, shift
);
268 ME_PropagateCharOffset(c
.pRun
, shift
);
270 if (!ME_StrVLen(cursor
.pRun
->member
.run
.strText
))
272 TRACE("Removing useless run\n");
273 ME_Remove(cursor
.pRun
);
274 ME_DestroyDisplayItem(cursor
.pRun
);
279 ME_CheckCharOffsets(editor);
286 void ME_DeleteTextAtCursor(ME_TextEditor
*editor
, int nCursor
,
289 assert(nCursor
>=0 && nCursor
<editor
->nCursors
);
290 ME_InternalDeleteText(editor
, ME_GetCursorOfs(editor
, nCursor
), nChars
);
293 static WCHAR wszSpace
[] = {' ', 0};
295 /* FIXME this is temporary, just to have something to test how bad graphics handler is */
296 void ME_InsertGraphicsFromCursor(ME_TextEditor
*editor
, int nCursor
)
298 ME_Cursor
*pCursor
= &editor
->pCursors
[nCursor
];
299 ME_DisplayItem
*pItem
= NULL
;
300 ME_DisplayItem
*pNewRun
= NULL
;
301 ME_Style
*pStyle
= ME_GetInsertStyle(editor
, nCursor
);
305 if (ME_IsSelection(editor
))
306 ME_DeleteSelection(editor
);
308 pUndo
= ME_AddUndoItem(editor
, diUndoDeleteRun
, NULL
);
310 pUndo
->nStart
= pCursor
->nOffset
+ pCursor
->pRun
->member
.run
.nCharOfs
+ ME_GetParagraph(pCursor
->pRun
)->member
.para
.nCharOfs
;
313 if (pCursor
->nOffset
)
315 ME_SplitRunSimple(editor
, pCursor
->pRun
, pCursor
->nOffset
);
317 pItem
= pCursor
->pRun
;
318 pNewRun
= ME_MakeRun(pStyle
, ME_MakeStringN(wszSpace
, 1), MERF_GRAPHICS
);
319 pNewRun
->member
.run
.nCharOfs
= pCursor
->pRun
->member
.run
.nCharOfs
;
320 ME_InsertBefore(pCursor
->pRun
, pNewRun
);
321 ME_PropagateCharOffset(pItem
, 1);
322 ME_CheckCharOffsets(editor
);
323 ME_SendSelChange(editor
);
326 void ME_InsertTextFromCursor(ME_TextEditor
*editor
, int nCursor
,
327 const WCHAR
*str
, int len
, ME_Style
*style
)
333 editor
->bCaretAtEnd
= FALSE
;
335 ME_AddRefStyle(style
);
337 /* FIXME really HERE ? */
338 if (ME_IsSelection(editor
))
339 ME_DeleteSelection(editor
);
341 assert(nCursor
>=0 && nCursor
<editor
->nCursors
);
345 /* FIXME this sucks - no respect for unicode (what else can be a line separator in unicode?) */
346 while(pos
-str
< len
&& *pos
!= '\r' && *pos
!= '\n' && *pos
!= '\t')
348 if (pos
-str
< len
&& *pos
== '\t') { /* handle tabs */
349 ME_DisplayItem
*pNewRun
= NULL
;
353 ME_InsertTextFromCursor(editor
, nCursor
, str
, pos
-str
, style
);
355 p
= &editor
->pCursors
[nCursor
];
357 assert(p
->pRun
->type
== diRun
);
358 pNewRun
= ME_MakeRun(style
, ME_MakeStringN(&tab
, 1), MERF_TAB
); /* addrefs style */
359 ME_InsertRun(editor
, ME_CharOfsFromRunOfs(editor
, p
->pRun
, p
->nOffset
), pNewRun
);
360 ME_DestroyDisplayItem(pNewRun
);
361 ME_ReleaseStyle(style
);
365 ME_InsertTextFromCursor(editor
, nCursor
, pos
, len
-(pos
-str
), style
);
369 if (pos
-str
< len
) { /* handle EOLs */
370 ME_DisplayItem
*tp
, *end_run
;
374 ME_InsertTextFromCursor(editor
, nCursor
, str
, pos
-str
, style
);
375 p
= &editor
->pCursors
[nCursor
];
376 tp
= ME_FindItemBack(p
->pRun
, diParagraph
);
377 para
= &tp
->member
.para
;
380 ME_SplitRunSimple(editor
, p
->pRun
, p
->nOffset
);
381 p
= &editor
->pCursors
[nCursor
];
383 tmp_style
= ME_GetInsertStyle(editor
, nCursor
);
384 /* ME_SplitParagraph increases style refcount */
385 tp
= ME_SplitParagraph(editor
, p
->pRun
, p
->pRun
->member
.run
.style
);
386 p
->pRun
= ME_FindItemFwd(tp
, diRun
);
387 end_run
= ME_FindItemBack(tp
, diRun
);
388 ME_ReleaseStyle(end_run
->member
.run
.style
);
389 end_run
->member
.run
.style
= tmp_style
;
391 if(pos
-str
< len
&& *pos
=='\r')
393 if(pos
-str
< len
&& *pos
=='\n')
396 ME_InsertTextFromCursor(editor
, nCursor
, pos
, len
-(pos
-str
), style
);
398 ME_ReleaseStyle(style
);
401 p
= &editor
->pCursors
[nCursor
];
403 ME_DisplayItem
*pNewRun
= NULL
;
405 assert(p
->pRun
->type
== diRun
);
406 pNewRun
= ME_MakeRun(style
, ME_MakeStringN(str
, len
), 0); /* addrefs style */
407 ME_InsertRun(editor
, ME_CharOfsFromRunOfs(editor
, p
->pRun
, p
->nOffset
), pNewRun
);
408 ME_DestroyDisplayItem(pNewRun
);
409 ME_ReleaseStyle(style
);
416 BOOL
ME_ArrowLeft(ME_TextEditor
*editor
, ME_Cursor
*p
)
419 p
->nOffset
= ME_StrRelPos2(p
->pRun
->member
.run
.strText
, p
->nOffset
, -1);
424 ME_DisplayItem
*pRun
= ME_FindItemBack(p
->pRun
, diRunOrParagraph
);
426 if (pRun
->type
== diRun
) {
428 assert(p
->pRun
->type
== diRun
);
429 assert(pRun
->member
.run
.strText
->nLen
);
430 p
->nOffset
= pRun
->member
.run
.strText
->nLen
;
432 p
->nOffset
= ME_StrRelPos2(pRun
->member
.run
.strText
, p
->nOffset
, -1);
438 if (pRun
->type
== diParagraph
)
440 if (pRun
->member
.para
.prev_para
->type
== diTextStart
)
442 assert(pRun
->member
.para
.prev_para
->type
== diParagraph
);
443 pRun
= ME_FindItemBack(pRun
, diRunOrParagraph
);
444 /* every paragraph ought to have at least one run */
445 assert(pRun
&& pRun
->type
== diRun
);
446 assert(pRun
->member
.run
.nFlags
& MERF_ENDPARA
);
456 BOOL
ME_ArrowRight(ME_TextEditor
*editor
, ME_Cursor
*p
)
458 int new_ofs
= ME_StrRelPos2(p
->pRun
->member
.run
.strText
, p
->nOffset
, 1);
459 if (new_ofs
<p
->pRun
->member
.run
.strText
->nLen
) {
460 p
->nOffset
= new_ofs
;
464 ME_DisplayItem
*pRun
= ME_FindItemFwd(p
->pRun
, diRun
);
467 assert(p
->pRun
->type
== diRun
);
474 int ME_GetCursorOfs(ME_TextEditor
*editor
, int nCursor
)
476 ME_Cursor
*pCursor
= &editor
->pCursors
[nCursor
];
478 return ME_GetParagraph(pCursor
->pRun
)->member
.para
.nCharOfs
479 + pCursor
->pRun
->member
.run
.nCharOfs
+ pCursor
->nOffset
;
482 int ME_FindPixelPos(ME_TextEditor
*editor
, int x
, int y
, ME_Cursor
*result
, BOOL
*is_eol
)
484 ME_DisplayItem
*p
= editor
->pBuffer
->pFirst
->member
.para
.next_para
;
490 while(p
!= editor
->pBuffer
->pLast
)
492 if (p
->type
== diParagraph
)
494 int ry
= y
- p
->member
.para
.nYPos
;
497 result
->pRun
= ME_FindItemFwd(p
, diRun
);
501 if (ry
>= p
->member
.para
.nHeight
)
503 p
= p
->member
.para
.next_para
;
506 p
= ME_FindItemFwd(p
, diStartRow
);
510 if (p
->type
== diStartRow
)
512 int ry
= y
- p
->member
.row
.nYPos
;
515 if (ry
>= p
->member
.row
.nHeight
)
517 p
= ME_FindItemFwd(p
, diStartRowOrParagraphOrEnd
);
518 if (p
->type
!= diStartRow
)
522 p
= ME_FindItemFwd(p
, diRun
);
525 if (p
->type
== diRun
)
528 rx
= x
- p
->member
.run
.pt
.x
;
531 if (rx
>= p
->member
.run
.nWidth
) /* not this run yet... find next item */
536 if (p
->type
== diRun
)
538 rx
= x
- p
->member
.run
.pt
.x
;
539 goto continue_search
;
541 if (p
->type
== diStartRow
)
543 p
= ME_FindItemFwd(p
, diRun
);
546 rx
= 0; /* FIXME not sure */
549 if (p
->type
== diParagraph
|| p
->type
== diTextEnd
)
551 rx
= 0; /* FIXME not sure */
559 if (p
->member
.run
.nFlags
& MERF_ENDPARA
)
562 result
->nOffset
= ME_CharFromPointCursor(editor
, rx
, &p
->member
.run
);
563 if (editor
->pCursors
[0].nOffset
== p
->member
.run
.strText
->nLen
&& rx
)
565 result
->pRun
= ME_FindItemFwd(editor
->pCursors
[0].pRun
, diRun
);
574 result
->pRun
= ME_FindItemBack(p
, diRun
);
576 assert(result
->pRun
->member
.run
.nFlags
& MERF_ENDPARA
);
580 void ME_LButtonDown(ME_TextEditor
*editor
, int x
, int y
)
582 ME_Cursor tmp_cursor
;
583 int is_selection
= 0;
585 editor
->nUDArrowX
= -1;
587 y
+= ME_GetYScrollPos(editor
);
589 tmp_cursor
= editor
->pCursors
[0];
590 is_selection
= ME_IsSelection(editor
);
592 ME_FindPixelPos(editor
, x
, y
, &editor
->pCursors
[0], &editor
->bCaretAtEnd
);
594 if (GetKeyState(VK_SHIFT
)>=0)
596 editor
->pCursors
[1] = editor
->pCursors
[0];
601 editor
->pCursors
[1] = tmp_cursor
;
605 HideCaret(editor
->hWnd
);
606 ME_MoveCaret(editor
);
609 ShowCaret(editor
->hWnd
);
610 ME_ClearTempStyle(editor
);
611 ME_SendSelChange(editor
);
614 void ME_MouseMove(ME_TextEditor
*editor
, int x
, int y
)
616 ME_Cursor tmp_cursor
;
618 y
+= ME_GetYScrollPos(editor
);
620 tmp_cursor
= editor
->pCursors
[0];
621 if (!ME_FindPixelPos(editor
, x
, y
, &editor
->pCursors
[0], &editor
->bCaretAtEnd
))
624 if (tmp_cursor
.pRun
== editor
->pCursors
[0].pRun
&&
625 tmp_cursor
.nOffset
== editor
->pCursors
[0].nOffset
)
628 HideCaret(editor
->hWnd
);
629 ME_MoveCaret(editor
);
631 ShowCaret(editor
->hWnd
);
632 ME_SendSelChange(editor
);
635 static ME_DisplayItem
*ME_FindRunInRow(ME_TextEditor
*editor
, ME_DisplayItem
*pRow
,
636 int x
, int *pOffset
, int *pbCaretAtEnd
)
638 ME_DisplayItem
*pNext
, *pLastRun
;
639 pNext
= ME_FindItemFwd(pRow
, diRunOrStartRow
);
640 assert(pNext
->type
== diRun
);
642 *pbCaretAtEnd
= FALSE
;
644 int run_x
= pNext
->member
.run
.pt
.x
;
645 int width
= pNext
->member
.run
.nWidth
;
648 if (pOffset
) *pOffset
= 0;
651 if (x
>= run_x
&& x
< run_x
+width
)
653 int ch
= ME_CharFromPointCursor(editor
, x
-run_x
, &pNext
->member
.run
);
654 ME_String
*s
= pNext
->member
.run
.strText
;
662 pNext
= ME_FindItemFwd(pNext
, diRunOrStartRow
);
663 } while(pNext
&& pNext
->type
== diRun
);
665 if ((pLastRun
->member
.run
.nFlags
& MERF_ENDPARA
) == 0)
667 pNext
= ME_FindItemFwd(pNext
, diRun
);
668 if (pbCaretAtEnd
) *pbCaretAtEnd
= 1;
669 if (pOffset
) *pOffset
= 0;
672 if (pbCaretAtEnd
) *pbCaretAtEnd
= 0;
673 if (pOffset
) *pOffset
= 0;
678 static int ME_GetXForArrow(ME_TextEditor
*editor
, ME_Cursor
*pCursor
)
680 ME_DisplayItem
*pRun
= pCursor
->pRun
;
683 if (editor
->nUDArrowX
!= -1)
684 x
= editor
->nUDArrowX
;
686 if (editor
->bCaretAtEnd
)
688 pRun
= ME_FindItemBack(pRun
, diRun
);
690 x
= pRun
->member
.run
.pt
.x
+ pRun
->member
.run
.nWidth
;
693 x
= pRun
->member
.run
.pt
.x
;
694 x
+= ME_PointFromChar(editor
, &pRun
->member
.run
, pCursor
->nOffset
);
696 editor
->nUDArrowX
= x
;
701 void ME_ArrowUp(ME_TextEditor
*editor
, ME_Cursor
*pCursor
)
703 ME_DisplayItem
*pRun
= pCursor
->pRun
;
704 ME_DisplayItem
*pItem
, *pItem2
;
705 int x
= ME_GetXForArrow(editor
, pCursor
);
707 if (editor
->bCaretAtEnd
&& !pCursor
->nOffset
)
709 pRun
= ME_FindItemBack(pRun
, diRun
);
714 /* start of this row */
715 pItem
= ME_FindItemBack(pRun
, diStartRow
);
717 /* start of the previous row */
718 pItem2
= ME_FindItemBack(pItem
, diStartRow
);
719 /* no previous row = the first line of the first paragraph */
720 if (!pItem2
) /* can't go up - don't go BOL (as in MS richedit) */
723 ME_WrapTextParagraph(editor, ME_FindItemBack(pItem2, diParagraph));
725 pCursor
->pRun
= ME_FindRunInRow(editor
, pItem2
, x
, &pCursor
->nOffset
, &editor
->bCaretAtEnd
);
728 void ME_ArrowDown(ME_TextEditor
*editor
, ME_Cursor
*pCursor
)
730 ME_DisplayItem
*pRun
= pCursor
->pRun
;
731 ME_DisplayItem
*pItem
;
732 int x
= ME_GetXForArrow(editor
, pCursor
);
733 if (!pCursor
->nOffset
&& editor
->bCaretAtEnd
)
735 pRun
= ME_FindItemBack(pRun
, diRun
);
736 /* x = pRun->member.run.pt.x + pRun->member.run.nWidth; */
738 /* start of the next row */
739 pItem
= ME_FindItemFwd(pRun
, diStartRow
);
740 /* FIXME If diParagraph is before diStartRow, wrap the next paragraph?
744 /* next row not found - ignore */
747 pCursor
->pRun
= ME_FindRunInRow(editor
, pItem
, x
, &pCursor
->nOffset
, &editor
->bCaretAtEnd
);
748 assert(pCursor
->pRun
);
749 assert(pCursor
->pRun
->type
== diRun
);
752 void ME_ArrowPageUp(ME_TextEditor
*editor
, ME_Cursor
*pCursor
)
754 ME_DisplayItem
*pRun
= pCursor
->pRun
;
755 ME_DisplayItem
*pLast
, *p
;
756 int x
, y
, ys
, yd
, yp
, yprev
;
757 ME_Cursor tmp_curs
= *pCursor
;
759 x
= ME_GetXForArrow(editor
, pCursor
);
760 if (!pCursor
->nOffset
&& editor
->bCaretAtEnd
)
761 pRun
= ME_FindItemBack(pRun
, diRun
);
763 p
= ME_FindItemBack(pRun
, diStartRowOrParagraph
);
764 assert(p
->type
== diStartRow
);
765 yp
= ME_FindItemBack(p
, diParagraph
)->member
.para
.nYPos
;
766 yprev
= ys
= y
= yp
+ p
->member
.row
.nYPos
;
767 yd
= y
- editor
->sizeWindow
.cy
;
771 p
= ME_FindItemBack(p
, diStartRowOrParagraph
);
774 if (p
->type
== diParagraph
) { /* crossing paragraphs */
775 if (p
->member
.para
.prev_para
== NULL
)
777 yp
= p
->member
.para
.prev_para
->member
.para
.nYPos
;
780 y
= yp
+ p
->member
.row
.nYPos
;
787 pCursor
->pRun
= ME_FindRunInRow(editor
, pLast
, x
, &pCursor
->nOffset
, &editor
->bCaretAtEnd
);
788 ME_UpdateSelection(editor
, &tmp_curs
);
789 if (yprev
< editor
->sizeWindow
.cy
)
791 ME_EnsureVisible(editor
, ME_FindItemFwd(editor
->pBuffer
->pFirst
, diRun
));
795 ME_Scroll(editor
, 0, ys
-yprev
);
798 assert(pCursor
->pRun
);
799 assert(pCursor
->pRun
->type
== diRun
);
802 /* FIXME: in the original RICHEDIT, PageDown always scrolls by the same amount
803 of pixels, even if it makes the scroll bar position exceed its normal maximum.
804 In such a situation, clicking the scrollbar restores its position back to the
805 normal range (ie. sets it to (doclength-screenheight)). */
807 void ME_ArrowPageDown(ME_TextEditor
*editor
, ME_Cursor
*pCursor
)
809 ME_DisplayItem
*pRun
= pCursor
->pRun
;
810 ME_DisplayItem
*pLast
, *p
;
811 int x
, y
, ys
, yd
, yp
, yprev
;
812 ME_Cursor tmp_curs
= *pCursor
;
814 x
= ME_GetXForArrow(editor
, pCursor
);
815 if (!pCursor
->nOffset
&& editor
->bCaretAtEnd
)
816 pRun
= ME_FindItemBack(pRun
, diRun
);
818 p
= ME_FindItemBack(pRun
, diStartRowOrParagraph
);
819 assert(p
->type
== diStartRow
);
820 yp
= ME_FindItemBack(p
, diParagraph
)->member
.para
.nYPos
;
821 yprev
= ys
= y
= yp
+ p
->member
.row
.nYPos
;
822 yd
= y
+ editor
->sizeWindow
.cy
;
826 p
= ME_FindItemFwd(p
, diStartRowOrParagraph
);
829 if (p
->type
== diParagraph
) {
830 yp
= p
->member
.para
.nYPos
;
833 y
= yp
+ p
->member
.row
.nYPos
;
840 pCursor
->pRun
= ME_FindRunInRow(editor
, pLast
, x
, &pCursor
->nOffset
, &editor
->bCaretAtEnd
);
841 ME_UpdateSelection(editor
, &tmp_curs
);
842 if (yprev
>= editor
->nTotalLength
-editor
->sizeWindow
.cy
)
844 ME_EnsureVisible(editor
, ME_FindItemBack(editor
->pBuffer
->pLast
, diRun
));
848 ME_Scroll(editor
, 0, ys
-yprev
);
851 assert(pCursor
->pRun
);
852 assert(pCursor
->pRun
->type
== diRun
);
855 void ME_ArrowHome(ME_TextEditor
*editor
, ME_Cursor
*pCursor
)
857 ME_DisplayItem
*pRow
= ME_FindItemBack(pCursor
->pRun
, diStartRow
);
859 ME_DisplayItem
*pRun
;
860 if (editor
->bCaretAtEnd
&& !pCursor
->nOffset
) {
861 pRow
= ME_FindItemBack(pRow
, diStartRow
);
865 pRun
= ME_FindItemFwd(pRow
, diRun
);
867 pCursor
->pRun
= pRun
;
868 pCursor
->nOffset
= 0;
871 editor
->bCaretAtEnd
= FALSE
;
874 void ME_ArrowCtrlHome(ME_TextEditor
*editor
, ME_Cursor
*pCursor
)
876 ME_DisplayItem
*pRow
= ME_FindItemBack(pCursor
->pRun
, diTextStart
);
878 ME_DisplayItem
*pRun
= ME_FindItemFwd(pRow
, diRun
);
880 pCursor
->pRun
= pRun
;
881 pCursor
->nOffset
= 0;
886 void ME_ArrowEnd(ME_TextEditor
*editor
, ME_Cursor
*pCursor
)
888 ME_DisplayItem
*pRow
;
890 if (editor
->bCaretAtEnd
&& !pCursor
->nOffset
)
893 pRow
= ME_FindItemFwd(pCursor
->pRun
, diStartRowOrParagraphOrEnd
);
895 if (pRow
->type
== diStartRow
) {
896 /* FIXME WTF was I thinking about here ? */
897 ME_DisplayItem
*pRun
= ME_FindItemFwd(pRow
, diRun
);
899 pCursor
->pRun
= pRun
;
900 pCursor
->nOffset
= 0;
901 editor
->bCaretAtEnd
= 1;
904 pCursor
->pRun
= ME_FindItemBack(pRow
, diRun
);
905 assert(pCursor
->pRun
&& pCursor
->pRun
->member
.run
.nFlags
& MERF_ENDPARA
);
906 pCursor
->nOffset
= 0;
907 editor
->bCaretAtEnd
= FALSE
;
910 void ME_ArrowCtrlEnd(ME_TextEditor
*editor
, ME_Cursor
*pCursor
)
912 ME_DisplayItem
*p
= ME_FindItemFwd(pCursor
->pRun
, diTextEnd
);
914 p
= ME_FindItemBack(p
, diRun
);
916 assert(p
->member
.run
.nFlags
& MERF_ENDPARA
);
918 pCursor
->nOffset
= 0;
919 editor
->bCaretAtEnd
= FALSE
;
922 BOOL
ME_IsSelection(ME_TextEditor
*editor
)
924 return memcmp(&editor
->pCursors
[0], &editor
->pCursors
[1], sizeof(ME_Cursor
))!=0;
927 int ME_GetSelCursor(ME_TextEditor
*editor
, int dir
)
929 int cdir
= ME_GetCursorOfs(editor
, 0) - ME_GetCursorOfs(editor
, 1);
937 BOOL
ME_CancelSelection(ME_TextEditor
*editor
, int dir
)
941 if (GetKeyState(VK_SHIFT
)<0)
943 if (!memcmp(&editor
->pCursors
[0], &editor
->pCursors
[1], sizeof(ME_Cursor
)))
946 cdir
= ME_GetCursorOfs(editor
, 0) - ME_GetCursorOfs(editor
, 1);
949 editor
->pCursors
[1] = editor
->pCursors
[0];
951 editor
->pCursors
[0] = editor
->pCursors
[1];
956 BOOL
ME_UpdateSelection(ME_TextEditor
*editor
, ME_Cursor
*pTempCursor
)
958 ME_Cursor old_anchor
= editor
->pCursors
[1];
960 if (GetKeyState(VK_SHIFT
)>=0) /* cancelling selection */
962 /* any selection was present ? if so, it's no more, repaint ! */
963 editor
->pCursors
[1] = editor
->pCursors
[0];
964 if (memcmp(pTempCursor
, &old_anchor
, sizeof(ME_Cursor
))) {
971 if (!memcmp(pTempCursor
, &editor
->pCursors
[1], sizeof(ME_Cursor
))) /* starting selection */
973 editor
->pCursors
[1] = *pTempCursor
;
982 void ME_RepaintSelection(ME_TextEditor
*editor
, ME_Cursor
*pTempCursor
)
984 if (ME_UpdateSelection(editor
, pTempCursor
)) {
985 ME_EnsureVisible(editor
, editor
->pCursors
[0].pRun
);
990 void ME_DeleteSelection(ME_TextEditor
*editor
)
993 ME_GetSelection(editor
, &from
, &to
);
994 ME_DeleteTextAtCursor(editor
, ME_GetSelCursor(editor
,-1), to
-from
);
997 ME_Style
*ME_GetSelectionInsertStyle(ME_TextEditor
*editor
)
1003 ME_GetSelection(editor
, &from
, &to
);
1004 ME_CursorFromCharOfs(editor
, from
, &c
);
1006 style
= c
.pRun
->member
.run
.style
;
1007 ME_AddRefStyle(style
); /* ME_GetInsertStyle has already done that */
1010 style
= ME_GetInsertStyle(editor
, 0);
1014 void ME_SendSelChange(ME_TextEditor
*editor
)
1017 if (!(editor
->nEventMask
& ENM_SELCHANGE
))
1019 sc
.nmhdr
.hwndFrom
= editor
->hWnd
;
1020 sc
.nmhdr
.idFrom
= GetWindowLongW(editor
->hWnd
, GWLP_ID
);
1021 sc
.nmhdr
.code
= EN_SELCHANGE
;
1022 SendMessageW(editor
->hWnd
, EM_EXGETSEL
, 0, (LPARAM
)&sc
.chrg
);
1023 sc
.seltyp
= SEL_EMPTY
;
1024 if (sc
.chrg
.cpMin
!= sc
.chrg
.cpMax
)
1025 sc
.seltyp
|= SEL_TEXT
;
1026 if (sc
.chrg
.cpMin
< sc
.chrg
.cpMax
+1) /* wth were RICHEDIT authors thinking ? */
1027 sc
.seltyp
|= SEL_MULTICHAR
;
1028 SendMessageW(GetParent(editor
->hWnd
), WM_NOTIFY
, sc
.nmhdr
.idFrom
, (LPARAM
)&sc
);
1031 BOOL
ME_ArrowKey(ME_TextEditor
*editor
, int nVKey
, int nCtrl
)
1034 ME_Cursor
*p
= &editor
->pCursors
[nCursor
];
1035 ME_Cursor tmp_curs
= *p
;
1039 ME_ArrowUp(editor
, p
);
1040 ME_ClearTempStyle(editor
);
1041 ME_RepaintSelection(editor
, &tmp_curs
);
1042 ME_SendSelChange(editor
);
1045 ME_ArrowDown(editor
, p
);
1046 ME_ClearTempStyle(editor
);
1047 ME_RepaintSelection(editor
, &tmp_curs
);
1048 ME_SendSelChange(editor
);
1051 ME_ArrowPageUp(editor
, p
);
1052 ME_ClearTempStyle(editor
);
1053 ME_SendSelChange(editor
);
1056 ME_ArrowPageDown(editor
, p
);
1057 ME_ClearTempStyle(editor
);
1058 ME_SendSelChange(editor
);
1062 editor
->nUDArrowX
= -1;
1064 case VK_BACK
: { /* FIXME backspace and delete aren't the same, they act different wrt paragraph style of the merged paragraph */
1065 if (GetWindowLongW(editor
->hWnd
, GWL_STYLE
) & ES_READONLY
)
1067 if (ME_IsSelection(editor
))
1069 editor
->bCaretAtEnd
= FALSE
; /* FIXME or maybe not */
1070 ME_DeleteSelection(editor
);
1071 ME_UpdateRepaint(editor
);
1074 if (ME_ArrowLeft(editor
, p
)) {
1075 editor
->bCaretAtEnd
= FALSE
; /* FIXME or maybe not */
1076 ME_ClearTempStyle(editor
);
1077 ME_MoveCaret(editor
);
1078 ME_DeleteTextAtCursor(editor
, nCursor
, 1);
1079 ME_UpdateRepaint(editor
);
1084 if (GetWindowLongW(editor
->hWnd
, GWL_STYLE
) & ES_READONLY
)
1086 /* editor->bCaretAtEnd = 0; FIXME or maybe not */
1087 if (ME_IsSelection(editor
))
1089 ME_DeleteSelection(editor
);
1090 ME_ClearTempStyle(editor
);
1091 ME_UpdateRepaint(editor
);
1094 ME_DeleteTextAtCursor(editor
, nCursor
, 1);
1095 ME_ClearTempStyle(editor
);
1096 ME_UpdateRepaint(editor
);
1100 if (GetKeyState(VK_CONTROL
)<0)
1101 ME_ArrowCtrlHome(editor
, p
);
1103 ME_ArrowHome(editor
, p
);
1104 editor
->bCaretAtEnd
= 0;
1105 ME_ClearTempStyle(editor
);
1106 ME_RepaintSelection(editor
, &tmp_curs
);
1107 ME_SendSelChange(editor
);
1111 if (GetKeyState(VK_CONTROL
)<0)
1112 ME_ArrowCtrlEnd(editor
, p
);
1114 ME_ArrowEnd(editor
, p
);
1115 ME_ClearTempStyle(editor
);
1116 ME_RepaintSelection(editor
, &tmp_curs
);
1117 ME_SendSelChange(editor
);
1120 editor
->bCaretAtEnd
= 0;
1121 if (ME_CancelSelection(editor
, -1))
1123 ME_ArrowLeft(editor
, p
);
1124 ME_RepaintSelection(editor
, &tmp_curs
);
1125 ME_ClearTempStyle(editor
);
1126 ME_SendSelChange(editor
);
1129 editor
->bCaretAtEnd
= 0;
1130 if (ME_CancelSelection(editor
, +1))
1132 ME_ArrowRight(editor
, p
);
1133 ME_RepaintSelection(editor
, &tmp_curs
);
1134 ME_ClearTempStyle(editor
);
1135 ME_SendSelChange(editor
);