quartz: Add tests for avi splitter.
[wine] / dlls / riched20 / caret.c
1 /*
2  * RichEdit - Caret and selection functions.
3  *
4  * Copyright 2004 by Krzysztof Foltman
5  * Copyright 2005 by Phil Krylov
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21
22
23 #include "editor.h"
24
25 WINE_DEFAULT_DEBUG_CHANNEL(richedit);
26
27 static BOOL
28 ME_MoveCursorChars(ME_TextEditor *editor, ME_Cursor *pCursor, int nRelOfs);
29
30 void ME_GetSelection(ME_TextEditor *editor, int *from, int *to)
31 {
32   *from = ME_GetCursorOfs(editor, 0);
33   *to =   ME_GetCursorOfs(editor, 1);
34   
35   if (*from > *to)
36   {
37     int tmp = *from;
38     *from = *to;
39     *to = tmp;    
40   }
41 }
42
43 int ME_GetTextLength(ME_TextEditor *editor)
44 {
45   return ME_CharOfsFromRunOfs(editor, ME_FindItemBack(editor->pBuffer->pLast, diRun), 0);   
46 }
47
48
49 int ME_GetTextLengthEx(ME_TextEditor *editor, const GETTEXTLENGTHEX *how)
50 {
51   int length;
52
53   if (how->flags & GTL_PRECISE && how->flags & GTL_CLOSE)
54     return E_INVALIDARG;
55   if (how->flags & GTL_NUMCHARS && how->flags & GTL_NUMBYTES)
56     return E_INVALIDARG;
57   
58   length = ME_GetTextLength(editor);
59
60   if ((GetWindowLongW(editor->hWnd, GWL_STYLE) & ES_MULTILINE)
61         && (how->flags & GTL_USECRLF)
62         && !editor->bEmulateVersion10) /* Ignore GTL_USECRLF flag in 1.0 emulation */
63     length += editor->nParagraphs - 1;
64   
65   if (how->flags & GTL_NUMBYTES)
66   {
67     CPINFO cpinfo;
68     
69     if (how->codepage == 1200)
70       return length * 2;
71     if (how->flags & GTL_PRECISE)
72       FIXME("GTL_PRECISE flag unsupported. Using GTL_CLOSE\n");
73     if (GetCPInfo(how->codepage, &cpinfo))
74       return length * cpinfo.MaxCharSize;
75     ERR("Invalid codepage %u\n", how->codepage);
76     return E_INVALIDARG;
77   }
78   return length; 
79 }
80
81
82 int ME_SetSelection(ME_TextEditor *editor, int from, int to)
83 {
84   int selectionEnd = 0;
85   const int len = ME_GetTextLength(editor);
86
87   /* all negative values are effectively the same */
88   if (from < 0)
89     from = -1;
90   if (to < 0)
91     to = -1;
92
93   /* select all */
94   if (from == 0 && to == -1)
95   {
96     editor->pCursors[1].pRun = ME_FindItemFwd(editor->pBuffer->pFirst, diRun);
97     editor->pCursors[1].nOffset = 0; 
98     editor->pCursors[0].pRun = ME_FindItemBack(editor->pBuffer->pLast, diRun); 
99     editor->pCursors[0].nOffset = 0;
100     ME_InvalidateSelection(editor);
101     ME_ClearTempStyle(editor);
102     return len + 1;
103   }
104
105   /* if both values are equal and also out of bound, that means to */
106   /* put the selection at the end of the text */
107   if ((from == to) && (to < 0 || to > len))
108   {
109     selectionEnd = 1;
110   }
111   else
112   {
113     /* if from is negative and to is positive then selection is */
114     /* deselected and caret moved to end of the current selection */
115     if (from < 0)
116     {
117       int start, end;
118       ME_GetSelection(editor, &start, &end);
119       editor->pCursors[1] = editor->pCursors[0];
120       ME_Repaint(editor);
121       ME_ClearTempStyle(editor);
122       return end;
123     }
124
125     /* adjust to if it's a negative value */
126     if (to < 0)
127       to = len + 1;
128
129     /* flip from and to if they are reversed */
130     if (from>to)
131     {
132       int tmp = from;
133       from = to;
134       to = tmp;
135     }
136
137     /* after fiddling with the values, we find from > len && to > len */
138     if (from > len)
139       selectionEnd = 1;
140     /* special case with to too big */
141     else if (to > len)
142       to = len + 1;
143   }
144
145   if (selectionEnd)
146   {
147     editor->pCursors[1].pRun = editor->pCursors[0].pRun = ME_FindItemBack(editor->pBuffer->pLast, diRun);
148     editor->pCursors[1].nOffset = editor->pCursors[0].nOffset = 0;
149     ME_InvalidateSelection(editor);
150     ME_ClearTempStyle(editor);
151     return len;
152   }
153
154   ME_RunOfsFromCharOfs(editor, from, &editor->pCursors[1].pRun, &editor->pCursors[1].nOffset);
155   ME_RunOfsFromCharOfs(editor, to, &editor->pCursors[0].pRun, &editor->pCursors[0].nOffset);
156   return to;
157 }
158
159
160 void
161 ME_GetCursorCoordinates(ME_TextEditor *editor, ME_Cursor *pCursor,
162                         int *x, int *y, int *height)
163 {
164   ME_DisplayItem *pCursorRun = pCursor->pRun;
165   ME_DisplayItem *pSizeRun = pCursor->pRun;
166
167   assert(height && x && y);
168   assert(!(ME_GetParagraph(pCursorRun)->member.para.nFlags & MEPF_REWRAP));
169   assert(pCursor->pRun);
170   assert(pCursor->pRun->type == diRun);
171   
172   if (pCursorRun->type == diRun) {
173     ME_DisplayItem *row = ME_FindItemBack(pCursorRun, diStartRowOrParagraph);
174
175     if (row) {
176       HDC hDC = GetDC(editor->hWnd);
177       ME_Context c;
178       ME_DisplayItem *run = pCursorRun;
179       ME_DisplayItem *para = NULL;
180       SIZE sz = {0, 0};
181     
182       ME_InitContext(&c, editor, hDC);
183       
184       if (!pCursor->nOffset)
185       {
186         ME_DisplayItem *prev = ME_FindItemBack(pCursorRun, diRunOrParagraph);
187         assert(prev);
188         if (prev->type == diRun)
189           pSizeRun = prev;
190       }
191       assert(row->type == diStartRow); /* paragraph -> run without start row ?*/
192       para = ME_FindItemBack(row, diParagraph);
193       assert(para);
194       assert(para->type == diParagraph);
195       if (editor->bCaretAtEnd && !pCursor->nOffset && 
196           run == ME_FindItemFwd(row, diRun))
197       {
198         ME_DisplayItem *tmp = ME_FindItemBack(row, diRunOrParagraph);
199         assert(tmp);
200         if (tmp->type == diRun)
201         {
202           row = ME_FindItemBack(tmp, diStartRow);
203           pSizeRun = run = tmp;
204           assert(run);
205           assert(run->type == diRun);
206           sz = ME_GetRunSize(&c, &para->member.para,
207                              &run->member.run, ME_StrLen(run->member.run.strText),
208                              row->member.row.nLMargin);
209         }
210       }
211       if (pCursor->nOffset) {
212         sz = ME_GetRunSize(&c, &para->member.para, &run->member.run, pCursor->nOffset,
213                            row->member.row.nLMargin);
214       }
215
216       *height = pSizeRun->member.run.nAscent + pSizeRun->member.run.nDescent;
217       *x = run->member.run.pt.x + sz.cx;
218       *y = para->member.para.nYPos + row->member.row.nBaseline + run->member.run.pt.y - pSizeRun->member.run.nAscent - ME_GetYScrollPos(editor);
219       ME_DestroyContext(&c, editor->hWnd);
220       return;
221     }
222   }
223   *height = 10; /* FIXME use global font */
224   *x = 0;
225   *y = 0;
226 }
227
228
229 void
230 ME_MoveCaret(ME_TextEditor *editor)
231 {
232   int x, y, height;
233
234   if (ME_WrapMarkedParagraphs(editor))
235     ME_UpdateScrollBar(editor);
236   ME_GetCursorCoordinates(editor, &editor->pCursors[0], &x, &y, &height);
237   if(editor->bHaveFocus && !ME_IsSelection(editor))
238   {
239     RECT rect;
240
241     GetClientRect(editor->hWnd, &rect);
242     x = min(x, rect.right-2);
243     CreateCaret(editor->hWnd, NULL, 0, height);
244     SetCaretPos(x, y);
245   } else {
246     DestroyCaret();
247   }
248 }
249
250
251 void ME_ShowCaret(ME_TextEditor *ed)
252 {
253   ME_MoveCaret(ed);
254   if(ed->bHaveFocus && !ME_IsSelection(ed))
255     ShowCaret(ed->hWnd);
256 }
257
258 void ME_HideCaret(ME_TextEditor *ed)
259 {
260   if(!ed->bHaveFocus || ME_IsSelection(ed))
261   {
262     HideCaret(ed->hWnd);
263     DestroyCaret();
264   }
265 }
266
267 void ME_InternalDeleteText(ME_TextEditor *editor, int nOfs, 
268   int nChars)
269 {
270   ME_Cursor c;
271   int shift = 0;
272   
273   while(nChars > 0)
274   {
275     ME_Run *run;
276     ME_CursorFromCharOfs(editor, nOfs, &c);
277     run = &c.pRun->member.run;
278     if (run->nFlags & MERF_ENDPARA) {
279       int eollen = run->nCR + run->nLF;
280
281       if (!ME_FindItemFwd(c.pRun, diParagraph))
282       {
283         return;
284       }
285       ME_JoinParagraphs(editor, ME_GetParagraph(c.pRun));
286       /* ME_SkipAndPropagateCharOffset(p->pRun, shift); */
287       ME_CheckCharOffsets(editor);
288       nChars -= (eollen < nChars) ? eollen : nChars;
289       continue;
290     }
291     else
292     {
293       ME_Cursor cursor;
294       int nIntendedChars = nChars;
295       int nCharsToDelete = nChars;
296       int i;
297       int loc = c.nOffset;
298       
299       ME_FindItemBack(c.pRun, diParagraph)->member.para.nFlags |= MEPF_REWRAP;
300       
301       cursor = c;
302       ME_StrRelPos(run->strText, loc, &nChars);
303       /* nChars is the number of characters that should be deleted from the
304          FOLLOWING runs (these AFTER cursor.pRun)
305          nCharsToDelete is a number of chars to delete from THIS run */
306       nCharsToDelete -= nChars;
307       shift -= nCharsToDelete;
308       TRACE("Deleting %d (intended %d-remaning %d) chars at %d in '%s' (%d)\n", 
309         nCharsToDelete, nIntendedChars, nChars, c.nOffset, 
310         debugstr_w(run->strText->szData), run->strText->nLen);
311
312       if (!c.nOffset && ME_StrVLen(run->strText) == nCharsToDelete)
313       {
314         /* undo = reinsert whole run */
315         /* nOfs is a character offset (from the start of the document
316            to the current (deleted) run */
317         ME_UndoItem *pUndo = ME_AddUndoItem(editor, diUndoInsertRun, c.pRun);
318         if (pUndo)
319           pUndo->di.member.run.nCharOfs = nOfs;
320       }
321       else
322       {
323         /* undo = reinsert partial run */
324         ME_UndoItem *pUndo = ME_AddUndoItem(editor, diUndoInsertRun, c.pRun);
325         if (pUndo) {
326           ME_DestroyString(pUndo->di.member.run.strText);
327           pUndo->di.member.run.nCharOfs = nOfs;
328           pUndo->di.member.run.strText = ME_MakeStringN(run->strText->szData+c.nOffset, nCharsToDelete);
329         }
330       }
331       TRACE("Post deletion string: %s (%d)\n", debugstr_w(run->strText->szData), run->strText->nLen);
332       TRACE("Shift value: %d\n", shift);
333       ME_StrDeleteV(run->strText, c.nOffset, nCharsToDelete);
334       
335       /* update cursors (including c) */
336       for (i=-1; i<editor->nCursors; i++) {
337         ME_Cursor *pThisCur = editor->pCursors + i; 
338         if (i == -1) pThisCur = &c;
339         if (pThisCur->pRun == cursor.pRun) {
340           if (pThisCur->nOffset > cursor.nOffset) {
341             if (pThisCur->nOffset-cursor.nOffset < nCharsToDelete)
342               pThisCur->nOffset = cursor.nOffset;
343             else
344               pThisCur->nOffset -= nCharsToDelete;
345             assert(pThisCur->nOffset >= 0);
346             assert(pThisCur->nOffset <= ME_StrVLen(run->strText));
347           }
348           if (pThisCur->nOffset == ME_StrVLen(run->strText))
349           {
350             pThisCur->pRun = ME_FindItemFwd(pThisCur->pRun, diRunOrParagraphOrEnd);
351             assert(pThisCur->pRun->type == diRun);
352             pThisCur->nOffset = 0;
353           }
354         }
355       }
356       
357       /* c = updated data now */
358       
359       if (c.pRun == cursor.pRun)
360         ME_SkipAndPropagateCharOffset(c.pRun, shift);
361       else
362         ME_PropagateCharOffset(c.pRun, shift);
363
364       if (!ME_StrVLen(cursor.pRun->member.run.strText))
365       {
366         TRACE("Removing useless run\n");
367         ME_Remove(cursor.pRun);
368         ME_DestroyDisplayItem(cursor.pRun);
369       }
370       
371       shift = 0;
372       /*
373       ME_CheckCharOffsets(editor);
374       */
375       continue;
376     }
377   }
378 }
379
380 void ME_DeleteTextAtCursor(ME_TextEditor *editor, int nCursor, 
381   int nChars)
382 {  
383   assert(nCursor>=0 && nCursor<editor->nCursors);
384   /* text operations set modified state */
385   editor->nModifyStep = 1;
386   ME_InternalDeleteText(editor, ME_GetCursorOfs(editor, nCursor), nChars);
387 }
388
389 static ME_DisplayItem *
390 ME_InternalInsertTextFromCursor(ME_TextEditor *editor, int nCursor,
391                                 const WCHAR *str, int len, ME_Style *style,
392                                 int flags)
393 {
394   ME_Cursor *p = &editor->pCursors[nCursor];
395
396   editor->bCaretAtEnd = FALSE;
397   
398   assert(p->pRun->type == diRun);
399   
400   return ME_InsertRunAtCursor(editor, p, style, str, len, flags);
401 }
402
403
404 void ME_InsertOLEFromCursor(ME_TextEditor *editor, const REOBJECT* reo, int nCursor)
405 {
406   ME_Style              *pStyle = ME_GetInsertStyle(editor, nCursor);
407   ME_DisplayItem        *di;
408   WCHAR                 space = ' ';
409   
410   /* FIXME no no no */
411   if (ME_IsSelection(editor))
412     ME_DeleteSelection(editor);
413
414   di = ME_InternalInsertTextFromCursor(editor, nCursor, &space, 1, pStyle,
415                                        MERF_GRAPHICS);
416   di->member.run.ole_obj = ALLOC_OBJ(*reo);
417   ME_CopyReObject(di->member.run.ole_obj, reo);
418   ME_SendSelChange(editor);
419 }
420
421
422 void ME_InsertEndRowFromCursor(ME_TextEditor *editor, int nCursor)
423 {
424   ME_Style              *pStyle = ME_GetInsertStyle(editor, nCursor);
425   ME_DisplayItem        *di;
426   WCHAR                 space = ' ';
427
428   /* FIXME no no no */
429   if (ME_IsSelection(editor))
430     ME_DeleteSelection(editor);
431
432   di = ME_InternalInsertTextFromCursor(editor, nCursor, &space, 1, pStyle,
433                                        MERF_ENDROW);
434   ME_SendSelChange(editor);
435 }
436
437 void
438 ME_InsertTableCellFromCursor(ME_TextEditor *editor, int nCursor)
439 {
440   WCHAR tab = '\t';
441   ME_DisplayItem *p, *run;
442   ME_Style *pStyle = ME_GetInsertStyle(editor, nCursor);
443   
444   p = ME_InternalInsertTextFromCursor(editor, nCursor, &tab, 1, pStyle,
445                                       MERF_CELL);
446   run = p;
447   while ((run = ME_FindItemBack(run, diRunOrParagraph))->type == diRun)
448   {
449     if (run->member.run.nFlags & MERF_CELL)
450     {
451       assert(run->member.run.pCell->next);
452       p->member.run.pCell = run->member.run.pCell->next;
453       return;
454     }
455   }
456   assert(run->type == diParagraph);
457   assert(run->member.para.bTable);
458   assert(run->member.para.pCells);
459   p->member.run.pCell = run->member.para.pCells;
460 }
461
462
463 void ME_InsertTextFromCursor(ME_TextEditor *editor, int nCursor, 
464   const WCHAR *str, int len, ME_Style *style)
465 {
466   const WCHAR *pos;
467   ME_Cursor *p = NULL;
468   int oldLen;
469
470   /* FIXME really HERE ? */
471   if (ME_IsSelection(editor))
472     ME_DeleteSelection(editor);
473
474   /* FIXME: is this too slow? */
475   /* Didn't affect performance for WM_SETTEXT (around 50sec/30K) */
476   oldLen = ME_GetTextLength(editor);
477
478   /* text operations set modified state */
479   editor->nModifyStep = 1;
480
481   assert(style);
482
483   assert(nCursor>=0 && nCursor<editor->nCursors);
484   if (len == -1)
485     len = lstrlenW(str);
486
487   /* grow the text limit to fit our text */
488   if(editor->nTextLimit < oldLen +len)
489     editor->nTextLimit = oldLen + len;
490
491   while (len)
492   {
493     pos = str;
494     /* FIXME this sucks - no respect for unicode (what else can be a line separator in unicode?) */
495     while(pos-str < len && *pos != '\r' && *pos != '\n' && *pos != '\t')
496       pos++;
497     if (pos-str < len && *pos == '\t') { /* handle tabs */
498       WCHAR tab = '\t';
499
500       if (pos!=str)
501         ME_InternalInsertTextFromCursor(editor, nCursor, str, pos-str, style, 0);
502     
503       ME_InternalInsertTextFromCursor(editor, nCursor, &tab, 1, style, MERF_TAB);
504  
505       pos++;
506       if(pos-str <= len) {
507         len -= pos - str;
508         str = pos;
509         continue;
510       }
511     }
512     /* handle special \r\r\n sequence (richedit 2.x and higher only) */
513     if (!editor->bEmulateVersion10 && pos-str < len-2 && pos[0] == '\r' && pos[1] == '\r' && pos[2] == '\n') {
514       WCHAR space = ' ';
515
516       if (pos!=str)
517         ME_InternalInsertTextFromCursor(editor, nCursor, str, pos-str, style, 0);
518
519       ME_InternalInsertTextFromCursor(editor, nCursor, &space, 1, style, 0);
520
521       pos+=3;
522       if(pos-str <= len) {
523         len -= pos - str;
524         str = pos;
525         continue;
526       }
527     }
528     if (pos-str < len) {   /* handle EOLs */
529       ME_DisplayItem *tp, *end_run;
530       ME_Style *tmp_style;
531       int numCR, numLF;
532
533       if (pos!=str)
534         ME_InternalInsertTextFromCursor(editor, nCursor, str, pos-str, style, 0);
535       p = &editor->pCursors[nCursor];
536       if (p->nOffset) {
537         ME_SplitRunSimple(editor, p->pRun, p->nOffset);
538         p = &editor->pCursors[nCursor];
539       }
540       tmp_style = ME_GetInsertStyle(editor, nCursor);
541       /* ME_SplitParagraph increases style refcount */
542
543       /* Encode and fill number of CR and LF according to emulation mode */
544       if (editor->bEmulateVersion10) {
545         const WCHAR * tpos;
546
547         /* We have to find out how many consecutive \r are there, and if there
548            is a \n terminating the run of \r's. */
549         numCR = 0; numLF = 0;
550         tpos = pos;
551         while (tpos-str < len && *tpos == '\r') {
552           tpos++;
553           numCR++;
554         }
555         if (tpos-str >= len) {
556           /* Reached end of text without finding anything but '\r' */
557           if (tpos != pos) {
558             pos++;
559           }
560           numCR = 1; numLF = 0;
561         } else if (*tpos == '\n') {
562           /* The entire run of \r's plus the one \n is one single line break */
563           pos = tpos + 1;
564           numLF = 1;
565         } else {
566           /* Found some other content past the run of \r's */
567           pos++;
568           numCR = 1; numLF = 0;
569         }
570       } else {
571         if(pos-str < len && *pos =='\r')
572           pos++;
573         if(pos-str < len && *pos =='\n')
574           pos++;
575         numCR = 1; numLF = 0;
576       }
577       tp = ME_SplitParagraph(editor, p->pRun, p->pRun->member.run.style, numCR, numLF);
578       p->pRun = ME_FindItemFwd(tp, diRun);
579       end_run = ME_FindItemBack(tp, diRun);
580       ME_ReleaseStyle(end_run->member.run.style);
581       end_run->member.run.style = tmp_style;
582       p->nOffset = 0;
583
584       if(pos-str <= len) {
585         len -= pos - str;
586         str = pos;
587         continue;
588       }
589     }
590     ME_InternalInsertTextFromCursor(editor, nCursor, str, len, style, 0);
591     len = 0;
592   }
593 }
594
595
596 static BOOL
597 ME_MoveCursorChars(ME_TextEditor *editor, ME_Cursor *pCursor, int nRelOfs)
598 {
599   ME_DisplayItem *pRun = pCursor->pRun;
600   
601   if (nRelOfs == -1)
602   {
603     if (!pCursor->nOffset)
604     {
605       do {
606         pRun = ME_FindItemBack(pRun, diRunOrParagraph);
607         assert(pRun);
608         switch (pRun->type)
609         {
610           case diRun:
611             break;
612           case diParagraph:
613             if (pRun->member.para.prev_para->type == diTextStart)
614               return FALSE;
615             pRun = ME_FindItemBack(pRun, diRunOrParagraph);
616             /* every paragraph ought to have at least one run */
617             assert(pRun && pRun->type == diRun);
618             assert(pRun->member.run.nFlags & MERF_ENDPARA);
619             break;
620           default:
621             assert(pRun->type != diRun && pRun->type != diParagraph);
622             return FALSE;
623         }
624       } while (RUN_IS_HIDDEN(&pRun->member.run));
625       pCursor->pRun = pRun;
626       if (pRun->member.run.nFlags & MERF_ENDPARA)
627         pCursor->nOffset = 0;
628       else
629         pCursor->nOffset = pRun->member.run.strText->nLen;
630     }
631     
632     if (pCursor->nOffset)
633       pCursor->nOffset = ME_StrRelPos2(pCursor->pRun->member.run.strText, pCursor->nOffset, nRelOfs);
634     return TRUE;
635   }
636   else
637   {
638     if (!(pRun->member.run.nFlags & MERF_ENDPARA))
639     {
640       int new_ofs = ME_StrRelPos2(pRun->member.run.strText, pCursor->nOffset, nRelOfs);
641     
642       if (new_ofs < pRun->member.run.strText->nLen)
643       {
644         pCursor->nOffset = new_ofs;
645         return TRUE;
646       }
647     }
648     do {
649       pRun = ME_FindItemFwd(pRun, diRun);
650     } while (pRun && RUN_IS_HIDDEN(&pRun->member.run));
651     if (pRun)
652     {
653       pCursor->pRun = pRun;
654       pCursor->nOffset = 0;
655       return TRUE;
656     }
657   }
658   return FALSE;
659 }
660
661
662 static BOOL
663 ME_MoveCursorWords(ME_TextEditor *editor, ME_Cursor *cursor, int nRelOfs)
664 {
665   ME_DisplayItem *pRun = cursor->pRun, *pOtherRun;
666   int nOffset = cursor->nOffset;
667   
668   if (nRelOfs == -1)
669   {
670     /* Backward movement */
671     while (TRUE)
672     {
673       nOffset = ME_CallWordBreakProc(editor, pRun->member.run.strText,
674                                      nOffset, WB_MOVEWORDLEFT);
675        if (nOffset)
676         break;
677       pOtherRun = ME_FindItemBack(pRun, diRunOrParagraph);
678       if (pOtherRun->type == diRun)
679       {
680         if (ME_CallWordBreakProc(editor, pOtherRun->member.run.strText,
681                                  pOtherRun->member.run.strText->nLen - 1,
682                                  WB_ISDELIMITER)
683             && !(pRun->member.run.nFlags & MERF_ENDPARA)
684             && !(cursor->pRun == pRun && cursor->nOffset == 0)
685             && !ME_CallWordBreakProc(editor, pRun->member.run.strText, 0,
686                                      WB_ISDELIMITER))
687           break;
688         pRun = pOtherRun;
689         nOffset = pOtherRun->member.run.strText->nLen;
690       }
691       else if (pOtherRun->type == diParagraph)
692       {
693         if (cursor->pRun == pRun && cursor->nOffset == 0)
694         {
695           /* Paragraph breaks are treated as separate words */
696           if (pOtherRun->member.para.prev_para->type == diTextStart)
697             return FALSE;
698           pRun = ME_FindItemBack(pOtherRun, diRunOrParagraph);
699         }
700         break;
701       }
702     }
703   }
704   else
705   {
706     /* Forward movement */
707     BOOL last_delim = FALSE;
708     
709     while (TRUE)
710     {
711       if (last_delim && !ME_CallWordBreakProc(editor, pRun->member.run.strText,
712                                               nOffset, WB_ISDELIMITER))
713         break;
714       nOffset = ME_CallWordBreakProc(editor, pRun->member.run.strText,
715                                      nOffset, WB_MOVEWORDRIGHT);
716       if (nOffset < pRun->member.run.strText->nLen)
717         break;
718       pOtherRun = ME_FindItemFwd(pRun, diRunOrParagraphOrEnd);
719       if (pOtherRun->type == diRun)
720       {
721         last_delim = ME_CallWordBreakProc(editor, pRun->member.run.strText,
722                                           nOffset - 1, WB_ISDELIMITER);
723         pRun = pOtherRun;
724         nOffset = 0;
725       }
726       else if (pOtherRun->type == diParagraph)
727       {
728         if (cursor->pRun == pRun)
729           pRun = ME_FindItemFwd(pOtherRun, diRun);
730         nOffset = 0;
731         break;
732       }
733       else /* diTextEnd */
734       {
735         if (cursor->pRun == pRun)
736           return FALSE;
737         nOffset = 0;
738         break;
739       }
740     }
741   }
742   cursor->pRun = pRun;
743   cursor->nOffset = nOffset;
744   return TRUE;
745 }
746
747
748 void
749 ME_SelectByType(ME_TextEditor *editor, ME_SelectionType selectionType)
750 {
751   /* pCursor[0] will be the start of the selection
752    * pCursor[1] is the other end of the selection range
753    * pCursor[2] and [3] are the selection anchors that are backed up
754    * so they are kept when the selection changes for drag selection.
755    */
756
757   editor->nSelectionType = selectionType;
758   switch(selectionType)
759   {
760     case stPosition:
761       break;
762     case stWord:
763       ME_MoveCursorWords(editor, &editor->pCursors[1], +1);
764       editor->pCursors[0] = editor->pCursors[1];
765       ME_MoveCursorWords(editor, &editor->pCursors[0], -1);
766       break;
767     case stLine:
768     case stParagraph:
769     {
770       ME_DisplayItem *pItem;
771       ME_DIType fwdSearchType, backSearchType;
772       if (selectionType == stParagraph) {
773           backSearchType = diParagraph;
774           fwdSearchType = diParagraphOrEnd;
775       } else {
776           backSearchType = diStartRow;
777           fwdSearchType = diStartRowOrParagraphOrEnd;
778       }
779       pItem = ME_FindItemBack(editor->pCursors[0].pRun, backSearchType);
780       editor->pCursors[0].pRun = ME_FindItemFwd(pItem, diRun);
781       editor->pCursors[0].nOffset = 0;
782
783       pItem = ME_FindItemFwd(editor->pCursors[0].pRun, fwdSearchType);
784       assert(pItem);
785       if (pItem->type == diTextEnd)
786           editor->pCursors[1].pRun = ME_FindItemBack(pItem, diRun);
787       else
788           editor->pCursors[1].pRun = ME_FindItemFwd(pItem, diRun);
789       editor->pCursors[1].nOffset = 0;
790       break;
791     }
792     case stDocument:
793       /* Select everything with cursor anchored from the start of the text */
794       editor->nSelectionType = stDocument;
795       editor->pCursors[1].pRun = ME_FindItemFwd(editor->pBuffer->pFirst, diRun);
796       editor->pCursors[1].nOffset = 0;
797       editor->pCursors[0].pRun = ME_FindItemBack(editor->pBuffer->pLast, diRun);
798       editor->pCursors[0].nOffset = 0;
799       break;
800     default: assert(0);
801   }
802   /* Store the anchor positions for extending the selection. */
803   editor->pCursors[2] = editor->pCursors[0];
804   editor->pCursors[3] = editor->pCursors[1];
805 }
806
807
808 int ME_GetCursorOfs(ME_TextEditor *editor, int nCursor)
809 {
810   ME_Cursor *pCursor = &editor->pCursors[nCursor];
811   return ME_GetParagraph(pCursor->pRun)->member.para.nCharOfs
812     + pCursor->pRun->member.run.nCharOfs + pCursor->nOffset;
813 }
814
815 static void ME_FindPixelPos(ME_TextEditor *editor, int x, int y, ME_Cursor *result, BOOL *is_eol)
816 {
817   ME_DisplayItem *p = editor->pBuffer->pFirst->member.para.next_para;
818   ME_DisplayItem *last = NULL;
819   int rx = 0;
820
821   if (is_eol)
822     *is_eol = 0;
823
824   /* find paragraph */
825   for (; p != editor->pBuffer->pLast; p = p->member.para.next_para)
826   {
827     assert(p->type == diParagraph);
828     if (y < p->member.para.nYPos + p->member.para.nHeight)
829     {
830       y -= p->member.para.nYPos;
831       p = ME_FindItemFwd(p, diStartRow);
832       break;
833     }
834   }
835   /* find row */
836   for (; p != editor->pBuffer->pLast; )
837   {
838     ME_DisplayItem *pp;
839     assert(p->type == diStartRow);
840     if (y < p->member.row.nYPos + p->member.row.nHeight)
841     {
842         p = ME_FindItemFwd(p, diRun);
843         break;
844     }
845     pp = ME_FindItemFwd(p, diStartRowOrParagraphOrEnd);
846     if (pp->type != diStartRow)
847     {
848         p = ME_FindItemFwd(p, diRun);
849         break;
850     }
851     p = pp;
852   }
853   if (p == editor->pBuffer->pLast)
854   {
855     /* The position is below the last paragraph, so the last row will be used
856      * rather than the end of the text, so the x position will be used to
857      * determine the offset closest to the pixel position. */
858     p = ME_FindItemBack(p, diStartRow);
859     if (p != NULL){
860       p = ME_FindItemFwd(p, diRun);
861     }
862     else
863     {
864       p = editor->pBuffer->pLast;
865     }
866   }
867   for (; p != editor->pBuffer->pLast; p = p->next)
868   {
869     switch (p->type)
870     {
871     case diRun:
872       rx = x - p->member.run.pt.x;
873       if (rx < p->member.run.nWidth)
874       {
875       found_here:
876         assert(p->type == diRun);
877         if ((p->member.run.nFlags & MERF_ENDPARA) || rx < 0)
878           rx = 0;
879         result->pRun = p;
880         result->nOffset = ME_CharFromPointCursor(editor, rx, &p->member.run);
881         if (editor->pCursors[0].nOffset == p->member.run.strText->nLen && rx)
882         {
883           result->pRun = ME_FindItemFwd(editor->pCursors[0].pRun, diRun);
884           result->nOffset = 0;
885         }
886         return;
887       }
888       break;
889     case diStartRow:
890       p = ME_FindItemFwd(p, diRun);
891       if (is_eol) *is_eol = 1;
892       rx = 0; /* FIXME not sure */
893       goto found_here;
894     case diParagraph:
895     case diTextEnd:
896       rx = 0; /* FIXME not sure */
897       p = last;
898       goto found_here;
899     default: assert(0);
900     }
901     last = p;
902   }
903   result->pRun = ME_FindItemBack(p, diRun);
904   result->nOffset = 0;
905   assert(result->pRun->member.run.nFlags & MERF_ENDPARA);
906 }
907
908
909 int
910 ME_CharFromPos(ME_TextEditor *editor, int x, int y)
911 {
912   ME_Cursor cursor;
913   RECT rc;
914
915   GetClientRect(editor->hWnd, &rc);
916   if (x < 0 || y < 0 || x >= rc.right || y >= rc.bottom)
917     return -1;
918   y += ME_GetYScrollPos(editor);
919   ME_FindPixelPos(editor, x, y, &cursor, NULL);
920   return (ME_GetParagraph(cursor.pRun)->member.para.nCharOfs
921           + cursor.pRun->member.run.nCharOfs + cursor.nOffset);
922 }
923
924 /* Extends the selection with a word, line, or paragraph selection type.
925  *
926  * The selection is anchored by editor->pCursors[2-3] such that the text
927  * between the anchors will remain selected, and one end will be extended.
928  *
929  * editor->pCursors[0] should have the position to extend the selection to
930  * before this function is called.
931  *
932  * Nothing will be done if editor->nSelectionType equals stPosition.
933  */
934 static void ME_ExtendAnchorSelection(ME_TextEditor *editor)
935 {
936   ME_Cursor tmp_cursor;
937   int curOfs, anchorStartOfs, anchorEndOfs;
938   if (editor->nSelectionType == stPosition || editor->nSelectionType == stDocument)
939       return;
940   curOfs = ME_GetCursorOfs(editor, 0);
941   anchorStartOfs = ME_GetCursorOfs(editor, 2);
942   anchorEndOfs = ME_GetCursorOfs(editor, 3);
943
944   tmp_cursor = editor->pCursors[0];
945   editor->pCursors[0] = editor->pCursors[2];
946   editor->pCursors[1] = editor->pCursors[3];
947   if (curOfs < anchorStartOfs)
948   {
949       /* Extend the left side of selection */
950       editor->pCursors[0] = tmp_cursor;
951       if (editor->nSelectionType == stWord)
952           ME_MoveCursorWords(editor, &editor->pCursors[0], -1);
953       else
954       {
955           ME_DisplayItem *pItem;
956           ME_DIType searchType = ((editor->nSelectionType == stLine) ?
957                                   diStartRowOrParagraph:diParagraph);
958           pItem = ME_FindItemBack(editor->pCursors[0].pRun, searchType);
959           editor->pCursors[0].pRun = ME_FindItemFwd(pItem, diRun);
960           editor->pCursors[0].nOffset = 0;
961       }
962   }
963   else if (curOfs >= anchorEndOfs)
964   {
965       /* Extend the right side of selection */
966       editor->pCursors[1] = tmp_cursor;
967       if (editor->nSelectionType == stWord)
968           ME_MoveCursorWords(editor, &editor->pCursors[1], +1);
969       else
970       {
971           ME_DisplayItem *pItem;
972           ME_DIType searchType = ((editor->nSelectionType == stLine) ?
973                                   diStartRowOrParagraphOrEnd:diParagraphOrEnd);
974           pItem = ME_FindItemFwd(editor->pCursors[1].pRun, searchType);
975           if (pItem->type == diTextEnd)
976               editor->pCursors[1].pRun = ME_FindItemBack(pItem, diRun);
977           else
978               editor->pCursors[1].pRun = ME_FindItemFwd(pItem, diRun);
979           editor->pCursors[1].nOffset = 0;
980       }
981   }
982 }
983
984 void ME_LButtonDown(ME_TextEditor *editor, int x, int y, int clickNum)
985 {
986   ME_Cursor tmp_cursor;
987   int is_selection = 0;
988   BOOL is_shift;
989   
990   editor->nUDArrowX = -1;
991   
992   y += ME_GetYScrollPos(editor);
993
994   tmp_cursor = editor->pCursors[0];
995   is_selection = ME_IsSelection(editor);
996   is_shift = GetKeyState(VK_SHIFT) < 0;
997
998   ME_FindPixelPos(editor, x, y, &editor->pCursors[0], &editor->bCaretAtEnd);
999
1000   if (x >= editor->selofs || is_shift)
1001   {
1002     if (clickNum > 1)
1003     {
1004       editor->pCursors[1] = editor->pCursors[0];
1005       if (is_shift) {
1006           if (x >= editor->selofs)
1007               ME_SelectByType(editor, stWord);
1008           else
1009               ME_SelectByType(editor, stParagraph);
1010       } else if (clickNum % 2 == 0) {
1011           ME_SelectByType(editor, stWord);
1012       } else {
1013           ME_SelectByType(editor, stParagraph);
1014       }
1015     }
1016     else if (!is_shift)
1017     {
1018       editor->nSelectionType = stPosition;
1019       editor->pCursors[1] = editor->pCursors[0];
1020     }
1021     else if (!is_selection)
1022     {
1023       editor->nSelectionType = stPosition;
1024       editor->pCursors[1] = tmp_cursor;
1025     }
1026     else if (editor->nSelectionType != stPosition)
1027     {
1028       ME_ExtendAnchorSelection(editor);
1029     }
1030   }
1031   else
1032   {
1033     if (clickNum < 2) {
1034         ME_SelectByType(editor, stLine);
1035     } else if (clickNum % 2 == 0 || is_shift) {
1036         ME_SelectByType(editor, stParagraph);
1037     } else {
1038         ME_SelectByType(editor, stDocument);
1039     }
1040   }
1041   ME_InvalidateSelection(editor);
1042   HideCaret(editor->hWnd);
1043   ME_ShowCaret(editor);
1044   ME_ClearTempStyle(editor);
1045   ME_SendSelChange(editor);
1046 }
1047
1048 void ME_MouseMove(ME_TextEditor *editor, int x, int y)
1049 {
1050   ME_Cursor tmp_cursor;
1051   
1052   if (editor->nSelectionType == stDocument)
1053       return;
1054   y += ME_GetYScrollPos(editor);
1055
1056   tmp_cursor = editor->pCursors[0];
1057   /* FIXME: do something with the return value of ME_FindPixelPos */
1058   ME_FindPixelPos(editor, x, y, &tmp_cursor, &editor->bCaretAtEnd);
1059
1060   ME_InvalidateSelection(editor);
1061   editor->pCursors[0] = tmp_cursor;
1062   ME_ExtendAnchorSelection(editor);
1063
1064   if (editor->nSelectionType != stPosition &&
1065       memcmp(&editor->pCursors[1], &editor->pCursors[3], sizeof(ME_Cursor)))
1066   {
1067       /* The scroll the cursor towards the other end, since it was the one
1068        * extended by ME_ExtendAnchorSelection
1069        */
1070       ME_Cursor tmpCursor = editor->pCursors[0];
1071       editor->pCursors[0] = editor->pCursors[1];
1072       editor->pCursors[1] = tmpCursor;
1073       SendMessageW(editor->hWnd, EM_SCROLLCARET, 0, 0);
1074       editor->pCursors[1] = editor->pCursors[0];
1075       editor->pCursors[0] = tmpCursor;
1076   } else {
1077       SendMessageW(editor->hWnd, EM_SCROLLCARET, 0, 0);
1078   }
1079
1080   ME_InvalidateSelection(editor);
1081   HideCaret(editor->hWnd);
1082   ME_ShowCaret(editor);
1083   ME_SendSelChange(editor);
1084 }
1085
1086 static ME_DisplayItem *ME_FindRunInRow(ME_TextEditor *editor, ME_DisplayItem *pRow, 
1087                                 int x, int *pOffset, int *pbCaretAtEnd)
1088 {
1089   ME_DisplayItem *pNext, *pLastRun;
1090   pNext = ME_FindItemFwd(pRow, diRunOrStartRow);
1091   assert(pNext->type == diRun);
1092   pLastRun = pNext;
1093   if (pbCaretAtEnd) *pbCaretAtEnd = FALSE;
1094   if (pOffset) *pOffset = 0;
1095   do {
1096     int run_x = pNext->member.run.pt.x;
1097     int width = pNext->member.run.nWidth;
1098     if (x < run_x)
1099     {
1100       return pNext;
1101     }
1102     if (x >= run_x && x < run_x+width)
1103     {
1104       int ch = ME_CharFromPointCursor(editor, x-run_x, &pNext->member.run);
1105       ME_String *s = pNext->member.run.strText;
1106       if (ch < s->nLen) {
1107         if (pOffset)
1108           *pOffset = ch;
1109         return pNext;          
1110       }
1111     }
1112     pLastRun = pNext;
1113     pNext = ME_FindItemFwd(pNext, diRunOrStartRow);
1114   } while(pNext && pNext->type == diRun);
1115   
1116   if ((pLastRun->member.run.nFlags & MERF_ENDPARA) == 0)
1117   {
1118     pNext = ME_FindItemFwd(pNext, diRun);
1119     if (pbCaretAtEnd) *pbCaretAtEnd = TRUE;
1120     return pNext;
1121   } else {
1122     return pLastRun;
1123   }
1124 }
1125
1126 static int ME_GetXForArrow(ME_TextEditor *editor, ME_Cursor *pCursor)
1127 {
1128   ME_DisplayItem *pRun = pCursor->pRun;
1129   int x;
1130
1131   if (editor->nUDArrowX != -1)
1132     x = editor->nUDArrowX;
1133   else {
1134     if (editor->bCaretAtEnd)
1135     {
1136       pRun = ME_FindItemBack(pRun, diRun);
1137       assert(pRun);
1138       x = pRun->member.run.pt.x + pRun->member.run.nWidth;
1139     }
1140     else {
1141       x = pRun->member.run.pt.x;
1142       x += ME_PointFromChar(editor, &pRun->member.run, pCursor->nOffset);
1143     }
1144     editor->nUDArrowX = x;
1145   }
1146   return x;
1147 }
1148
1149
1150 static void
1151 ME_MoveCursorLines(ME_TextEditor *editor, ME_Cursor *pCursor, int nRelOfs)
1152 {
1153   ME_DisplayItem *pRun = pCursor->pRun;
1154   ME_DisplayItem *pItem;
1155   int x = ME_GetXForArrow(editor, pCursor);
1156
1157   if (editor->bCaretAtEnd && !pCursor->nOffset)
1158     pRun = ME_FindItemBack(pRun, diRun);
1159   if (!pRun)
1160     return;
1161   if (nRelOfs == -1)
1162   {
1163     /* start of this row */
1164     pItem = ME_FindItemBack(pRun, diStartRow);
1165     assert(pItem);
1166     /* start of the previous row */
1167     pItem = ME_FindItemBack(pItem, diStartRow);
1168   }
1169   else
1170   {
1171     /* start of the next row */
1172     pItem = ME_FindItemFwd(pRun, diStartRow);
1173     /* FIXME If diParagraph is before diStartRow, wrap the next paragraph?
1174     */
1175   }
1176   if (!pItem)
1177   {
1178     /* row not found - ignore */
1179     return;
1180   }
1181   pCursor->pRun = ME_FindRunInRow(editor, pItem, x, &pCursor->nOffset, &editor->bCaretAtEnd);
1182   assert(pCursor->pRun);
1183   assert(pCursor->pRun->type == diRun);
1184 }
1185
1186
1187 static void ME_ArrowPageUp(ME_TextEditor *editor, ME_Cursor *pCursor)
1188 {
1189   ME_DisplayItem *pRun = pCursor->pRun;
1190   ME_DisplayItem *pLast, *p;
1191   int x, y, ys, yd, yp, yprev;
1192   ME_Cursor tmp_curs = *pCursor;
1193   
1194   x = ME_GetXForArrow(editor, pCursor);
1195   if (!pCursor->nOffset && editor->bCaretAtEnd)
1196     pRun = ME_FindItemBack(pRun, diRun);
1197   
1198   p = ME_FindItemBack(pRun, diStartRowOrParagraph);
1199   assert(p->type == diStartRow);
1200   yp = ME_FindItemBack(p, diParagraph)->member.para.nYPos;
1201   yprev = ys = y = yp + p->member.row.nYPos;
1202   yd = y - editor->sizeWindow.cy;
1203   pLast = p;
1204   
1205   do {
1206     p = ME_FindItemBack(p, diStartRowOrParagraph);
1207     if (!p)
1208       break;
1209     if (p->type == diParagraph) { /* crossing paragraphs */
1210       if (p->member.para.prev_para == NULL)
1211         break;
1212       yp = p->member.para.prev_para->member.para.nYPos;
1213       continue;
1214     }
1215     y = yp + p->member.row.nYPos;
1216     if (y < yd)
1217       break;
1218     pLast = p;
1219     yprev = y;
1220   } while(1);
1221   
1222   pCursor->pRun = ME_FindRunInRow(editor, pLast, x, &pCursor->nOffset, &editor->bCaretAtEnd);
1223   ME_UpdateSelection(editor, &tmp_curs);
1224   if (yprev < editor->sizeWindow.cy)
1225   {
1226     ME_EnsureVisible(editor, ME_FindItemFwd(editor->pBuffer->pFirst, diRun));
1227     ME_Repaint(editor);
1228   }
1229   else 
1230   {
1231     ME_ScrollUp(editor, ys-yprev);
1232   }
1233   assert(pCursor->pRun);
1234   assert(pCursor->pRun->type == diRun);
1235 }
1236
1237 /* FIXME: in the original RICHEDIT, PageDown always scrolls by the same amount 
1238    of pixels, even if it makes the scroll bar position exceed its normal maximum.
1239    In such a situation, clicking the scrollbar restores its position back to the
1240    normal range (ie. sets it to (doclength-screenheight)). */
1241
1242 static void ME_ArrowPageDown(ME_TextEditor *editor, ME_Cursor *pCursor)
1243 {
1244   ME_DisplayItem *pRun = pCursor->pRun;
1245   ME_DisplayItem *pLast, *p;
1246   int x, y, ys, yd, yp, yprev;
1247   ME_Cursor tmp_curs = *pCursor;
1248   
1249   x = ME_GetXForArrow(editor, pCursor);
1250   if (!pCursor->nOffset && editor->bCaretAtEnd)
1251     pRun = ME_FindItemBack(pRun, diRun);
1252   
1253   p = ME_FindItemBack(pRun, diStartRowOrParagraph);
1254   assert(p->type == diStartRow);
1255   yp = ME_FindItemBack(p, diParagraph)->member.para.nYPos;
1256   yprev = ys = y = yp + p->member.row.nYPos;
1257   yd = y + editor->sizeWindow.cy;
1258   pLast = p;
1259   
1260   do {
1261     p = ME_FindItemFwd(p, diStartRowOrParagraph);
1262     if (!p)
1263       break;
1264     if (p->type == diParagraph) {
1265       yp = p->member.para.nYPos;
1266       continue;
1267     }
1268     y = yp + p->member.row.nYPos;
1269     if (y >= yd)
1270       break;
1271     pLast = p;
1272     yprev = y;
1273   } while(1);
1274   
1275   pCursor->pRun = ME_FindRunInRow(editor, pLast, x, &pCursor->nOffset, &editor->bCaretAtEnd);
1276   ME_UpdateSelection(editor, &tmp_curs);
1277   if (yprev >= editor->nTotalLength-editor->sizeWindow.cy)
1278   {
1279     ME_EnsureVisible(editor, ME_FindItemBack(editor->pBuffer->pLast, diRun));
1280     ME_Repaint(editor);
1281   }
1282   else 
1283   {
1284     ME_ScrollUp(editor,ys-yprev);
1285   }
1286   assert(pCursor->pRun);
1287   assert(pCursor->pRun->type == diRun);
1288 }
1289
1290 static void ME_ArrowHome(ME_TextEditor *editor, ME_Cursor *pCursor)
1291 {
1292   ME_DisplayItem *pRow = ME_FindItemBack(pCursor->pRun, diStartRow);
1293   ME_WrapMarkedParagraphs(editor);
1294   if (pRow) {
1295     ME_DisplayItem *pRun;
1296     if (editor->bCaretAtEnd && !pCursor->nOffset) {
1297       pRow = ME_FindItemBack(pRow, diStartRow);
1298       if (!pRow)
1299         return;
1300     }
1301     pRun = ME_FindItemFwd(pRow, diRun);
1302     if (pRun) {
1303       pCursor->pRun = pRun;
1304       pCursor->nOffset = 0;
1305     }
1306   }
1307   editor->bCaretAtEnd = FALSE;
1308 }
1309
1310 static void ME_ArrowCtrlHome(ME_TextEditor *editor, ME_Cursor *pCursor)
1311 {
1312   ME_DisplayItem *pRow = ME_FindItemBack(pCursor->pRun, diTextStart);
1313   if (pRow) {
1314     ME_DisplayItem *pRun = ME_FindItemFwd(pRow, diRun);
1315     if (pRun) {
1316       pCursor->pRun = pRun;
1317       pCursor->nOffset = 0;
1318     }
1319   }
1320 }
1321
1322 static void ME_ArrowEnd(ME_TextEditor *editor, ME_Cursor *pCursor)
1323 {
1324   ME_DisplayItem *pRow;
1325   
1326   if (editor->bCaretAtEnd && !pCursor->nOffset)
1327     return;
1328   
1329   pRow = ME_FindItemFwd(pCursor->pRun, diStartRowOrParagraphOrEnd);
1330   assert(pRow);
1331   if (pRow->type == diStartRow) {
1332     /* FIXME WTF was I thinking about here ? */
1333     ME_DisplayItem *pRun = ME_FindItemFwd(pRow, diRun);
1334     assert(pRun);
1335     pCursor->pRun = pRun;
1336     pCursor->nOffset = 0;
1337     editor->bCaretAtEnd = 1;
1338     return;
1339   }
1340   pCursor->pRun = ME_FindItemBack(pRow, diRun);
1341   assert(pCursor->pRun && pCursor->pRun->member.run.nFlags & MERF_ENDPARA);
1342   pCursor->nOffset = 0;
1343   editor->bCaretAtEnd = FALSE;
1344 }
1345       
1346 static void ME_ArrowCtrlEnd(ME_TextEditor *editor, ME_Cursor *pCursor)
1347 {
1348   ME_DisplayItem *p = ME_FindItemFwd(pCursor->pRun, diTextEnd);
1349   assert(p);
1350   p = ME_FindItemBack(p, diRun);
1351   assert(p);
1352   assert(p->member.run.nFlags & MERF_ENDPARA);
1353   pCursor->pRun = p;
1354   pCursor->nOffset = 0;
1355   editor->bCaretAtEnd = FALSE;
1356 }
1357
1358 BOOL ME_IsSelection(ME_TextEditor *editor)
1359 {
1360   return memcmp(&editor->pCursors[0], &editor->pCursors[1], sizeof(ME_Cursor))!=0;
1361 }
1362
1363 static int ME_GetSelCursor(ME_TextEditor *editor, int dir)
1364 {
1365   int cdir = ME_GetCursorOfs(editor, 0) - ME_GetCursorOfs(editor, 1);
1366   
1367   if (cdir*dir>0)
1368     return 0;
1369   else
1370     return 1;
1371 }
1372
1373 BOOL ME_UpdateSelection(ME_TextEditor *editor, const ME_Cursor *pTempCursor)
1374 {
1375   ME_Cursor old_anchor = editor->pCursors[1];
1376   
1377   if (GetKeyState(VK_SHIFT)>=0) /* cancelling selection */
1378   {
1379     /* any selection was present ? if so, it's no more, repaint ! */
1380     editor->pCursors[1] = editor->pCursors[0];
1381     if (memcmp(pTempCursor, &old_anchor, sizeof(ME_Cursor))) {
1382       return TRUE;
1383     }
1384     return FALSE;
1385   }
1386   else
1387   {
1388     if (!memcmp(pTempCursor, &editor->pCursors[1], sizeof(ME_Cursor))) /* starting selection */
1389     {
1390       editor->pCursors[1] = *pTempCursor;
1391       return TRUE;
1392     }
1393   }
1394
1395   ME_Repaint(editor);
1396   return TRUE;
1397 }
1398
1399 void ME_DeleteSelection(ME_TextEditor *editor)
1400 {
1401   int from, to;
1402   ME_GetSelection(editor, &from, &to);
1403   ME_DeleteTextAtCursor(editor, ME_GetSelCursor(editor,-1), to-from);
1404 }
1405
1406 ME_Style *ME_GetSelectionInsertStyle(ME_TextEditor *editor)
1407 {
1408   return ME_GetInsertStyle(editor, 0);
1409 }
1410
1411 void ME_SendSelChange(ME_TextEditor *editor)
1412 {
1413   SELCHANGE sc;
1414
1415   if (!(editor->nEventMask & ENM_SELCHANGE))
1416     return;
1417   
1418   sc.nmhdr.hwndFrom = editor->hWnd;
1419   sc.nmhdr.idFrom = GetWindowLongW(editor->hWnd, GWLP_ID);
1420   sc.nmhdr.code = EN_SELCHANGE;
1421   SendMessageW(editor->hWnd, EM_EXGETSEL, 0, (LPARAM)&sc.chrg);
1422   sc.seltyp = SEL_EMPTY;
1423   if (sc.chrg.cpMin != sc.chrg.cpMax)
1424     sc.seltyp |= SEL_TEXT;
1425   if (sc.chrg.cpMin < sc.chrg.cpMax+1) /* wth were RICHEDIT authors thinking ? */
1426     sc.seltyp |= SEL_MULTICHAR;
1427   TRACE("cpMin=%d cpMax=%d seltyp=%d (%s %s)\n",
1428     sc.chrg.cpMin, sc.chrg.cpMax, sc.seltyp,
1429     (sc.seltyp & SEL_TEXT) ? "SEL_TEXT" : "",
1430     (sc.seltyp & SEL_MULTICHAR) ? "SEL_MULTICHAR" : "");
1431   if (sc.chrg.cpMin != editor->notified_cr.cpMin || sc.chrg.cpMax != editor->notified_cr.cpMax)
1432   {
1433     ME_ClearTempStyle(editor);
1434
1435     editor->notified_cr = sc.chrg;
1436     SendMessageW(GetParent(editor->hWnd), WM_NOTIFY, sc.nmhdr.idFrom, (LPARAM)&sc);
1437   }
1438 }
1439
1440 BOOL
1441 ME_ArrowKey(ME_TextEditor *editor, int nVKey, BOOL extend, BOOL ctrl)
1442 {
1443   int nCursor = 0;
1444   ME_Cursor *p = &editor->pCursors[nCursor];
1445   ME_Cursor tmp_curs = *p;
1446   BOOL success = FALSE;
1447   
1448   ME_CheckCharOffsets(editor);
1449   switch(nVKey) {
1450     case VK_LEFT:
1451       editor->bCaretAtEnd = 0;
1452       if (ctrl)
1453         success = ME_MoveCursorWords(editor, &tmp_curs, -1);
1454       else
1455         success = ME_MoveCursorChars(editor, &tmp_curs, -1);
1456       break;
1457     case VK_RIGHT:
1458       editor->bCaretAtEnd = 0;
1459       if (ctrl)
1460         success = ME_MoveCursorWords(editor, &tmp_curs, +1);
1461       else
1462         success = ME_MoveCursorChars(editor, &tmp_curs, +1);
1463       break;
1464     case VK_UP:
1465       ME_MoveCursorLines(editor, &tmp_curs, -1);
1466       break;
1467     case VK_DOWN:
1468       ME_MoveCursorLines(editor, &tmp_curs, +1);
1469       break;
1470     case VK_PRIOR:
1471       ME_ArrowPageUp(editor, &tmp_curs);
1472       break;
1473     case VK_NEXT:
1474       ME_ArrowPageDown(editor, &tmp_curs);
1475       break;
1476     case VK_HOME: {
1477       if (ctrl)
1478         ME_ArrowCtrlHome(editor, &tmp_curs);
1479       else
1480         ME_ArrowHome(editor, &tmp_curs);
1481       editor->bCaretAtEnd = 0;
1482       break;
1483     }
1484     case VK_END: 
1485       if (ctrl)
1486         ME_ArrowCtrlEnd(editor, &tmp_curs);
1487       else
1488         ME_ArrowEnd(editor, &tmp_curs);
1489       break;
1490   }
1491   
1492   if (!extend)
1493     editor->pCursors[1] = tmp_curs;
1494   *p = tmp_curs;
1495   
1496   ME_InvalidateSelection(editor);
1497   ME_Repaint(editor);
1498   HideCaret(editor->hWnd);
1499   ME_EnsureVisible(editor, tmp_curs.pRun); 
1500   ME_ShowCaret(editor);
1501   ME_SendSelChange(editor);
1502   return success;
1503 }