ReactOS Fundraising Campaign 2012
 
€ 4,410 / € 30,000

Information | Donate

Home | Info | Community | Development | myReactOS | Contact Us

  1. Home
  2. Community
  3. Development
  4. myReactOS
  5. Fundraiser 2012

  1. Main Page
  2. Alphabetical List
  3. Data Structures
  4. Directories
  5. File List
  6. Data Fields
  7. Globals
  8. Related Pages

ReactOS Development > Doxygen

caret.c
Go to the documentation of this file.
00001 /*
00002  * RichEdit - Caret and selection functions.
00003  *
00004  * Copyright 2004 by Krzysztof Foltman
00005  * Copyright 2005 by Phil Krylov
00006  *
00007  * This library is free software; you can redistribute it and/or
00008  * modify it under the terms of the GNU Lesser General Public
00009  * License as published by the Free Software Foundation; either
00010  * version 2.1 of the License, or (at your option) any later version.
00011  *
00012  * This library is distributed in the hope that it will be useful,
00013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00015  * Lesser General Public License for more details.
00016  *
00017  * You should have received a copy of the GNU Lesser General Public
00018  * License along with this library; if not, write to the Free Software
00019  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
00020  */
00021 
00022 
00023 #include "editor.h"
00024 
00025 WINE_DEFAULT_DEBUG_CHANNEL(richedit);
00026 
00027 void ME_SetCursorToStart(ME_TextEditor *editor, ME_Cursor *cursor)
00028 {
00029   cursor->pPara = editor->pBuffer->pFirst->member.para.next_para;
00030   cursor->pRun = ME_FindItemFwd(cursor->pPara, diRun);
00031   cursor->nOffset = 0;
00032 }
00033 
00034 static void ME_SetCursorToEnd(ME_TextEditor *editor, ME_Cursor *cursor)
00035 {
00036   cursor->pPara = editor->pBuffer->pLast->member.para.prev_para;
00037   cursor->pRun = ME_FindItemBack(editor->pBuffer->pLast, diRun);
00038   cursor->nOffset = 0;
00039 }
00040 
00041 
00042 int ME_GetSelectionOfs(ME_TextEditor *editor, int *from, int *to)
00043 {
00044   *from = ME_GetCursorOfs(&editor->pCursors[0]);
00045   *to =   ME_GetCursorOfs(&editor->pCursors[1]);
00046 
00047   if (*from > *to)
00048   {
00049     int tmp = *from;
00050     *from = *to;
00051     *to = tmp;
00052     return 1;
00053   }
00054   return 0;
00055 }
00056 
00057 int ME_GetSelection(ME_TextEditor *editor, ME_Cursor **from, ME_Cursor **to)
00058 {
00059   if (ME_GetCursorOfs(&editor->pCursors[0]) < ME_GetCursorOfs(&editor->pCursors[1]))
00060   {
00061     *from = &editor->pCursors[0];
00062     *to = &editor->pCursors[1];
00063     return 0;
00064   } else {
00065     *from = &editor->pCursors[1];
00066     *to = &editor->pCursors[0];
00067     return 1;
00068   }
00069 }
00070 
00071 int ME_GetTextLength(ME_TextEditor *editor)
00072 {
00073   ME_Cursor cursor;
00074   ME_SetCursorToEnd(editor, &cursor);
00075   return ME_GetCursorOfs(&cursor);
00076 }
00077 
00078 
00079 int ME_GetTextLengthEx(ME_TextEditor *editor, const GETTEXTLENGTHEX *how)
00080 {
00081   int length;
00082 
00083   if (how->flags & GTL_PRECISE && how->flags & GTL_CLOSE)
00084     return E_INVALIDARG;
00085   if (how->flags & GTL_NUMCHARS && how->flags & GTL_NUMBYTES)
00086     return E_INVALIDARG;
00087   
00088   length = ME_GetTextLength(editor);
00089 
00090   if ((editor->styleFlags & ES_MULTILINE)
00091         && (how->flags & GTL_USECRLF)
00092         && !editor->bEmulateVersion10) /* Ignore GTL_USECRLF flag in 1.0 emulation */
00093     length += editor->nParagraphs - 1;
00094 
00095   if (how->flags & GTL_NUMBYTES ||
00096       (how->flags & GTL_PRECISE &&     /* GTL_PRECISE seems to imply GTL_NUMBYTES */
00097        !(how->flags & GTL_NUMCHARS)))  /* unless GTL_NUMCHARS is given */
00098   {
00099     CPINFO cpinfo;
00100     
00101     if (how->codepage == 1200)
00102       return length * 2;
00103     if (how->flags & GTL_PRECISE)
00104       FIXME("GTL_PRECISE flag unsupported. Using GTL_CLOSE\n");
00105     if (GetCPInfo(how->codepage, &cpinfo))
00106       return length * cpinfo.MaxCharSize;
00107     ERR("Invalid codepage %u\n", how->codepage);
00108     return E_INVALIDARG;
00109   }
00110   return length; 
00111 }
00112 
00113 
00114 int ME_SetSelection(ME_TextEditor *editor, int from, int to)
00115 {
00116   int selectionEnd = 0;
00117   const int len = ME_GetTextLength(editor);
00118 
00119   /* all negative values are effectively the same */
00120   if (from < 0)
00121     from = -1;
00122   if (to < 0)
00123     to = -1;
00124 
00125   /* select all */
00126   if (from == 0 && to == -1)
00127   {
00128     ME_SetCursorToStart(editor, &editor->pCursors[1]);
00129     ME_SetCursorToEnd(editor, &editor->pCursors[0]);
00130     ME_InvalidateSelection(editor);
00131     ME_ClearTempStyle(editor);
00132     return len + 1;
00133   }
00134 
00135   /* if both values are equal and also out of bound, that means to */
00136   /* put the selection at the end of the text */
00137   if ((from == to) && (to < 0 || to > len))
00138   {
00139     selectionEnd = 1;
00140   }
00141   else
00142   {
00143     /* if from is negative and to is positive then selection is */
00144     /* deselected and caret moved to end of the current selection */
00145     if (from < 0)
00146     {
00147       int start, end;
00148       ME_GetSelectionOfs(editor, &start, &end);
00149       if (start != end)
00150       {
00151           editor->pCursors[1] = editor->pCursors[0];
00152           ME_Repaint(editor);
00153       }
00154       ME_ClearTempStyle(editor);
00155       return end;
00156     }
00157 
00158     /* adjust to if it's a negative value */
00159     if (to < 0)
00160       to = len + 1;
00161 
00162     /* flip from and to if they are reversed */
00163     if (from>to)
00164     {
00165       int tmp = from;
00166       from = to;
00167       to = tmp;
00168     }
00169 
00170     /* after fiddling with the values, we find from > len && to > len */
00171     if (from > len)
00172       selectionEnd = 1;
00173     /* special case with to too big */
00174     else if (to > len)
00175       to = len + 1;
00176   }
00177 
00178   if (selectionEnd)
00179   {
00180     ME_SetCursorToEnd(editor, &editor->pCursors[0]);
00181     editor->pCursors[1] = editor->pCursors[0];
00182     ME_InvalidateSelection(editor);
00183     ME_ClearTempStyle(editor);
00184     return len;
00185   }
00186 
00187   ME_CursorFromCharOfs(editor, from, &editor->pCursors[1]);
00188   editor->pCursors[0] = editor->pCursors[1];
00189   ME_MoveCursorChars(editor, &editor->pCursors[0], to - from);
00190   /* Selection is not allowed in the middle of an end paragraph run. */
00191   if (editor->pCursors[1].pRun->member.run.nFlags & MERF_ENDPARA)
00192     editor->pCursors[1].nOffset = 0;
00193   if (editor->pCursors[0].pRun->member.run.nFlags & MERF_ENDPARA)
00194     editor->pCursors[0].nOffset = 0;
00195   return to;
00196 }
00197 
00198 
00199 static void
00200 ME_GetCursorCoordinates(ME_TextEditor *editor, ME_Cursor *pCursor,
00201                         int *x, int *y, int *height)
00202 {
00203   ME_DisplayItem *row;
00204   ME_DisplayItem *run = pCursor->pRun;
00205   ME_DisplayItem *para = pCursor->pPara;
00206   ME_DisplayItem *pSizeRun = run;
00207   ME_Context c;
00208   SIZE sz = {0, 0};
00209 
00210   assert(height && x && y);
00211   assert(~para->member.para.nFlags & MEPF_REWRAP);
00212   assert(run && run->type == diRun);
00213   assert(para && para->type == diParagraph);
00214 
00215   row = ME_FindItemBack(run, diStartRowOrParagraph);
00216   assert(row && row->type == diStartRow);
00217 
00218   ME_InitContext(&c, editor, ITextHost_TxGetDC(editor->texthost));
00219 
00220   if (!pCursor->nOffset)
00221   {
00222     ME_DisplayItem *prev = ME_FindItemBack(run, diRunOrParagraph);
00223     assert(prev);
00224     if (prev->type == diRun)
00225       pSizeRun = prev;
00226   }
00227   if (editor->bCaretAtEnd && !pCursor->nOffset &&
00228       run == ME_FindItemFwd(row, diRun))
00229   {
00230     ME_DisplayItem *tmp = ME_FindItemBack(row, diRunOrParagraph);
00231     assert(tmp);
00232     if (tmp->type == diRun)
00233     {
00234       row = ME_FindItemBack(tmp, diStartRow);
00235       pSizeRun = run = tmp;
00236       assert(run);
00237       assert(run->type == diRun);
00238       sz = ME_GetRunSize(&c, &para->member.para,
00239                          &run->member.run, run->member.run.strText->nLen,
00240                          row->member.row.nLMargin);
00241     }
00242   }
00243   if (pCursor->nOffset) {
00244     sz = ME_GetRunSize(&c, &para->member.para, &run->member.run,
00245                        pCursor->nOffset, row->member.row.nLMargin);
00246   }
00247 
00248   *height = pSizeRun->member.run.nAscent + pSizeRun->member.run.nDescent;
00249   *x = c.rcView.left + run->member.run.pt.x + sz.cx - editor->horz_si.nPos;
00250   *y = c.rcView.top + para->member.para.pt.y + row->member.row.nBaseline
00251        + run->member.run.pt.y - pSizeRun->member.run.nAscent
00252        - editor->vert_si.nPos;
00253   ME_DestroyContext(&c);
00254   return;
00255 }
00256 
00257 
00258 void
00259 ME_MoveCaret(ME_TextEditor *editor)
00260 {
00261   int x, y, height;
00262 
00263   ME_GetCursorCoordinates(editor, &editor->pCursors[0], &x, &y, &height);
00264   if(editor->bHaveFocus && !ME_IsSelection(editor))
00265   {
00266     x = min(x, editor->rcFormat.right-1);
00267     ITextHost_TxCreateCaret(editor->texthost, NULL, 0, height);
00268     ITextHost_TxSetCaretPos(editor->texthost, x, y);
00269   }
00270 }
00271 
00272 
00273 void ME_ShowCaret(ME_TextEditor *ed)
00274 {
00275   ME_MoveCaret(ed);
00276   if(ed->bHaveFocus && !ME_IsSelection(ed))
00277     ITextHost_TxShowCaret(ed->texthost, TRUE);
00278 }
00279 
00280 void ME_HideCaret(ME_TextEditor *ed)
00281 {
00282   if(!ed->bHaveFocus || ME_IsSelection(ed))
00283   {
00284     ITextHost_TxShowCaret(ed->texthost, FALSE);
00285     DestroyCaret();
00286   }
00287 }
00288 
00289 BOOL ME_InternalDeleteText(ME_TextEditor *editor, ME_Cursor *start,
00290                            int nChars, BOOL bForce)
00291 {
00292   ME_Cursor c = *start;
00293   int nOfs = ME_GetCursorOfs(start);
00294   int shift = 0;
00295   int totalChars = nChars;
00296   ME_DisplayItem *start_para;
00297 
00298   /* Prevent deletion past last end of paragraph run. */
00299   nChars = min(nChars, ME_GetTextLength(editor) - nOfs);
00300   start_para = c.pPara;
00301 
00302   if (!bForce)
00303   {
00304     ME_ProtectPartialTableDeletion(editor, &c, &nChars);
00305     if (nChars == 0)
00306       return FALSE;
00307   }
00308 
00309   while(nChars > 0)
00310   {
00311     ME_Run *run;
00312     ME_CursorFromCharOfs(editor, nOfs+nChars, &c);
00313     if (!c.nOffset &&
00314         nOfs+nChars == (c.pRun->member.run.nCharOfs
00315                         + c.pPara->member.para.nCharOfs))
00316     {
00317       /* We aren't deleting anything in this run, so we will go back to the
00318        * last run we are deleting text in. */
00319       ME_PrevRun(&c.pPara, &c.pRun);
00320       c.nOffset = c.pRun->member.run.strText->nLen;
00321     }
00322     run = &c.pRun->member.run;
00323     if (run->nFlags & MERF_ENDPARA) {
00324       int eollen = c.pRun->member.run.strText->nLen;
00325       BOOL keepFirstParaFormat;
00326 
00327       if (!ME_FindItemFwd(c.pRun, diParagraph))
00328       {
00329         return TRUE;
00330       }
00331       keepFirstParaFormat = (totalChars == nChars && nChars <= eollen &&
00332                              run->nCharOfs);
00333       if (!editor->bEmulateVersion10) /* v4.1 */
00334       {
00335         ME_DisplayItem *next_para = ME_FindItemFwd(c.pRun, diParagraphOrEnd);
00336         ME_DisplayItem *this_para = next_para->member.para.prev_para;
00337 
00338         /* The end of paragraph before a table row is only deleted if there
00339          * is nothing else on the line before it. */
00340         if (this_para == start_para &&
00341             next_para->member.para.nFlags & MEPF_ROWSTART)
00342         {
00343           /* If the paragraph will be empty, then it should be deleted, however
00344            * it still might have text right now which would inherit the
00345            * MEPF_STARTROW property if we joined it right now.
00346            * Instead we will delete it after the preceding text is deleted. */
00347           if (nOfs > this_para->member.para.nCharOfs) {
00348             /* Skip this end of line. */
00349             nChars -= (eollen < nChars) ? eollen : nChars;
00350             continue;
00351           }
00352           keepFirstParaFormat = TRUE;
00353         }
00354       }
00355       ME_JoinParagraphs(editor, c.pPara, keepFirstParaFormat);
00356       /* ME_SkipAndPropagateCharOffset(p->pRun, shift); */
00357       ME_CheckCharOffsets(editor);
00358       nChars -= (eollen < nChars) ? eollen : nChars;
00359       continue;
00360     }
00361     else
00362     {
00363       ME_Cursor cursor;
00364       int nCharsToDelete = min(nChars, c.nOffset);
00365       int i;
00366 
00367       c.nOffset -= nCharsToDelete;
00368 
00369       ME_FindItemBack(c.pRun, diParagraph)->member.para.nFlags |= MEPF_REWRAP;
00370 
00371       cursor = c;
00372       /* nChars is the number of characters that should be deleted from the
00373          PRECEDING runs (these BEFORE cursor.pRun)
00374          nCharsToDelete is a number of chars to delete from THIS run */
00375       nChars -= nCharsToDelete;
00376       shift -= nCharsToDelete;
00377       TRACE("Deleting %d (remaning %d) chars at %d in '%s' (%d)\n",
00378         nCharsToDelete, nChars, c.nOffset,
00379         debugstr_w(run->strText->szData), run->strText->nLen);
00380 
00381       if (!c.nOffset && run->strText->nLen == nCharsToDelete)
00382       {
00383         /* undo = reinsert whole run */
00384         /* nOfs is a character offset (from the start of the document
00385            to the current (deleted) run */
00386         ME_UndoItem *pUndo = ME_AddUndoItem(editor, diUndoInsertRun, c.pRun);
00387         if (pUndo)
00388           pUndo->di.member.run.nCharOfs = nOfs+nChars;
00389       }
00390       else
00391       {
00392         /* undo = reinsert partial run */
00393         ME_UndoItem *pUndo = ME_AddUndoItem(editor, diUndoInsertRun, c.pRun);
00394         if (pUndo) {
00395           ME_DestroyString(pUndo->di.member.run.strText);
00396           pUndo->di.member.run.nCharOfs = nOfs+nChars;
00397           pUndo->di.member.run.strText = ME_MakeStringN(run->strText->szData+c.nOffset, nCharsToDelete);
00398         }
00399       }
00400       TRACE("Post deletion string: %s (%d)\n", debugstr_w(run->strText->szData), run->strText->nLen);
00401       TRACE("Shift value: %d\n", shift);
00402       ME_StrDeleteV(run->strText, c.nOffset, nCharsToDelete);
00403 
00404       /* update cursors (including c) */
00405       for (i=-1; i<editor->nCursors; i++) {
00406         ME_Cursor *pThisCur = editor->pCursors + i;
00407         if (i == -1) pThisCur = &c;
00408         if (pThisCur->pRun == cursor.pRun) {
00409           if (pThisCur->nOffset > cursor.nOffset) {
00410             if (pThisCur->nOffset-cursor.nOffset < nCharsToDelete)
00411               pThisCur->nOffset = cursor.nOffset;
00412             else
00413               pThisCur->nOffset -= nCharsToDelete;
00414             assert(pThisCur->nOffset >= 0);
00415             assert(pThisCur->nOffset <= run->strText->nLen);
00416           }
00417           if (pThisCur->nOffset == run->strText->nLen)
00418           {
00419             pThisCur->pRun = ME_FindItemFwd(pThisCur->pRun, diRunOrParagraphOrEnd);
00420             assert(pThisCur->pRun->type == diRun);
00421             pThisCur->nOffset = 0;
00422           }
00423         }
00424       }
00425 
00426       /* c = updated data now */
00427 
00428       if (c.pRun == cursor.pRun)
00429         ME_SkipAndPropagateCharOffset(c.pRun, shift);
00430       else
00431         ME_PropagateCharOffset(c.pRun, shift);
00432 
00433       if (!cursor.pRun->member.run.strText->nLen)
00434       {
00435         TRACE("Removing useless run\n");
00436         ME_Remove(cursor.pRun);
00437         ME_DestroyDisplayItem(cursor.pRun);
00438       }
00439 
00440       shift = 0;
00441       /*
00442       ME_CheckCharOffsets(editor);
00443       */
00444       continue;
00445     }
00446   }
00447   return TRUE;
00448 }
00449 
00450 BOOL ME_DeleteTextAtCursor(ME_TextEditor *editor, int nCursor, int nChars)
00451 {
00452   assert(nCursor>=0 && nCursor<editor->nCursors);
00453   /* text operations set modified state */
00454   editor->nModifyStep = 1;
00455   return ME_InternalDeleteText(editor, &editor->pCursors[nCursor],
00456                                nChars, FALSE);
00457 }
00458 
00459 static ME_DisplayItem *
00460 ME_InternalInsertTextFromCursor(ME_TextEditor *editor, int nCursor,
00461                                 const WCHAR *str, int len, ME_Style *style,
00462                                 int flags)
00463 {
00464   ME_Cursor *p = &editor->pCursors[nCursor];
00465 
00466   editor->bCaretAtEnd = FALSE;
00467   
00468   assert(p->pRun->type == diRun);
00469   
00470   return ME_InsertRunAtCursor(editor, p, style, str, len, flags);
00471 }
00472 
00473 
00474 void ME_InsertOLEFromCursor(ME_TextEditor *editor, const REOBJECT* reo, int nCursor)
00475 {
00476   ME_Style              *pStyle = ME_GetInsertStyle(editor, nCursor);
00477   ME_DisplayItem        *di;
00478   WCHAR                 space = ' ';
00479   
00480   /* FIXME no no no */
00481   if (ME_IsSelection(editor))
00482     ME_DeleteSelection(editor);
00483 
00484   di = ME_InternalInsertTextFromCursor(editor, nCursor, &space, 1, pStyle,
00485                                        MERF_GRAPHICS);
00486   di->member.run.ole_obj = ALLOC_OBJ(*reo);
00487   ME_CopyReObject(di->member.run.ole_obj, reo);
00488   ME_ReleaseStyle(pStyle);
00489 }
00490 
00491 
00492 void ME_InsertEndRowFromCursor(ME_TextEditor *editor, int nCursor)
00493 {
00494   ME_Style              *pStyle = ME_GetInsertStyle(editor, nCursor);
00495   WCHAR                 space = ' ';
00496 
00497   /* FIXME no no no */
00498   if (ME_IsSelection(editor))
00499     ME_DeleteSelection(editor);
00500 
00501   ME_InternalInsertTextFromCursor(editor, nCursor, &space, 1, pStyle,
00502                                   MERF_ENDROW);
00503   ME_ReleaseStyle(pStyle);
00504 }
00505 
00506 
00507 void ME_InsertTextFromCursor(ME_TextEditor *editor, int nCursor, 
00508   const WCHAR *str, int len, ME_Style *style)
00509 {
00510   const WCHAR *pos;
00511   ME_Cursor *p = NULL;
00512   int oldLen;
00513 
00514   /* FIXME really HERE ? */
00515   if (ME_IsSelection(editor))
00516     ME_DeleteSelection(editor);
00517 
00518   /* FIXME: is this too slow? */
00519   /* Didn't affect performance for WM_SETTEXT (around 50sec/30K) */
00520   oldLen = ME_GetTextLength(editor);
00521 
00522   /* text operations set modified state */
00523   editor->nModifyStep = 1;
00524 
00525   assert(style);
00526 
00527   assert(nCursor>=0 && nCursor<editor->nCursors);
00528   if (len == -1)
00529     len = lstrlenW(str);
00530 
00531   /* grow the text limit to fit our text */
00532   if(editor->nTextLimit < oldLen +len)
00533     editor->nTextLimit = oldLen + len;
00534 
00535   pos = str;
00536 
00537   while (len)
00538   {
00539     /* FIXME this sucks - no respect for unicode (what else can be a line separator in unicode?) */
00540     while(pos - str < len && *pos != '\r' && *pos != '\n' && *pos != '\t')
00541       pos++;
00542 
00543     if (pos != str) { /* handle text */
00544       ME_InternalInsertTextFromCursor(editor, nCursor, str, pos-str, style, 0);
00545     } else if (*pos == '\t') { /* handle tabs */
00546       WCHAR tab = '\t';
00547       ME_InternalInsertTextFromCursor(editor, nCursor, &tab, 1, style, MERF_TAB);
00548       pos++;
00549     } else { /* handle EOLs */
00550       ME_DisplayItem *tp, *end_run;
00551       ME_Style *tmp_style;
00552       int eol_len = 0;
00553 
00554       /* Find number of CR and LF in end of paragraph run */
00555       if (*pos =='\r')
00556       {
00557         if (len > 1 && pos[1] == '\n')
00558           eol_len = 2;
00559         else if (len > 2 && pos[1] == '\r' && pos[2] == '\n')
00560           eol_len = 3;
00561         else
00562           eol_len = 1;
00563       } else {
00564         assert(*pos == '\n');
00565         eol_len = 1;
00566       }
00567       pos += eol_len;
00568 
00569       if (!editor->bEmulateVersion10 && eol_len == 3)
00570       {
00571         /* handle special \r\r\n sequence (richedit 2.x and higher only) */
00572         WCHAR space = ' ';
00573         ME_InternalInsertTextFromCursor(editor, nCursor, &space, 1, style, 0);
00574       } else {
00575         ME_String *eol_str;
00576 
00577         if (!editor->bEmulateVersion10) {
00578           WCHAR cr = '\r';
00579           eol_str = ME_MakeStringN(&cr, 1);
00580         } else {
00581           eol_str = ME_MakeStringN(str, eol_len);
00582         }
00583 
00584         p = &editor->pCursors[nCursor];
00585         if (p->nOffset)
00586           ME_SplitRunSimple(editor, p);
00587         tmp_style = ME_GetInsertStyle(editor, nCursor);
00588         /* ME_SplitParagraph increases style refcount */
00589         tp = ME_SplitParagraph(editor, p->pRun, p->pRun->member.run.style, eol_str, 0);
00590         p->pRun = ME_FindItemFwd(tp, diRun);
00591         p->pPara = tp;
00592         end_run = ME_FindItemBack(tp, diRun);
00593         ME_ReleaseStyle(end_run->member.run.style);
00594         end_run->member.run.style = tmp_style;
00595         p->nOffset = 0;
00596       }
00597     }
00598     len -= pos - str;
00599     str = pos;
00600   }
00601 }
00602 
00603 /* Move the cursor nRelOfs characters (either forwards or backwards)
00604  *
00605  * returns the actual number of characters moved.
00606  **/
00607 int ME_MoveCursorChars(ME_TextEditor *editor, ME_Cursor *cursor, int nRelOfs)
00608 {
00609   cursor->nOffset += nRelOfs;
00610   if (cursor->nOffset < 0)
00611   {
00612     cursor->nOffset += cursor->pRun->member.run.nCharOfs;
00613     if (cursor->nOffset >= 0)
00614     {
00615       /* new offset in the same paragraph */
00616       do {
00617         cursor->pRun = ME_FindItemBack(cursor->pRun, diRun);
00618       } while (cursor->nOffset < cursor->pRun->member.run.nCharOfs);
00619       cursor->nOffset -= cursor->pRun->member.run.nCharOfs;
00620       return nRelOfs;
00621     }
00622 
00623     cursor->nOffset += cursor->pPara->member.para.nCharOfs;
00624     if (cursor->nOffset <= 0)
00625     {
00626       /* moved to the start of the text */
00627       nRelOfs -= cursor->nOffset;
00628       ME_SetCursorToStart(editor, cursor);
00629       return nRelOfs;
00630     }
00631 
00632     /* new offset in a previous paragraph */
00633     do {
00634       cursor->pPara = cursor->pPara->member.para.prev_para;
00635     } while (cursor->nOffset < cursor->pPara->member.para.nCharOfs);
00636     cursor->nOffset -= cursor->pPara->member.para.nCharOfs;
00637 
00638     cursor->pRun = ME_FindItemBack(cursor->pPara->member.para.next_para, diRun);
00639     while (cursor->nOffset < cursor->pRun->member.run.nCharOfs) {
00640       cursor->pRun = ME_FindItemBack(cursor->pRun, diRun);
00641     }
00642     cursor->nOffset -= cursor->pRun->member.run.nCharOfs;
00643   } else if (cursor->nOffset >= cursor->pRun->member.run.strText->nLen) {
00644     ME_DisplayItem *next_para;
00645     int new_offset;
00646 
00647     new_offset = ME_GetCursorOfs(cursor);
00648     next_para = cursor->pPara->member.para.next_para;
00649     if (new_offset < next_para->member.para.nCharOfs)
00650     {
00651       /* new offset in the same paragraph */
00652       do {
00653         cursor->nOffset -= cursor->pRun->member.run.strText->nLen;
00654         cursor->pRun = ME_FindItemFwd(cursor->pRun, diRun);
00655       } while (cursor->nOffset >= cursor->pRun->member.run.strText->nLen);
00656       return nRelOfs;
00657     }
00658 
00659     if (new_offset >= ME_GetTextLength(editor))
00660     {
00661       /* new offset at the end of the text */
00662       ME_SetCursorToEnd(editor, cursor);
00663       nRelOfs -= new_offset - ME_GetTextLength(editor);
00664       return nRelOfs;
00665     }
00666 
00667     /* new offset in a following paragraph */
00668     do {
00669       cursor->pPara = next_para;
00670       next_para = next_para->member.para.next_para;
00671     } while (new_offset >= next_para->member.para.nCharOfs);
00672 
00673     cursor->nOffset = new_offset - cursor->pPara->member.para.nCharOfs;
00674     cursor->pRun = ME_FindItemFwd(cursor->pPara, diRun);
00675     while (cursor->nOffset >= cursor->pRun->member.run.strText->nLen)
00676     {
00677       cursor->nOffset -= cursor->pRun->member.run.strText->nLen;
00678       cursor->pRun = ME_FindItemFwd(cursor->pRun, diRun);
00679     }
00680   } /* else new offset is in the same run */
00681   return nRelOfs;
00682 }
00683 
00684 
00685 static BOOL
00686 ME_MoveCursorWords(ME_TextEditor *editor, ME_Cursor *cursor, int nRelOfs)
00687 {
00688   ME_DisplayItem *pRun = cursor->pRun, *pOtherRun;
00689   ME_DisplayItem *pPara = cursor->pPara;
00690   int nOffset = cursor->nOffset;
00691 
00692   if (nRelOfs == -1)
00693   {
00694     /* Backward movement */
00695     while (TRUE)
00696     {
00697       nOffset = ME_CallWordBreakProc(editor, pRun->member.run.strText,
00698                                      nOffset, WB_MOVEWORDLEFT);
00699       if (nOffset)
00700         break;
00701       pOtherRun = ME_FindItemBack(pRun, diRunOrParagraph);
00702       if (pOtherRun->type == diRun)
00703       {
00704         if (ME_CallWordBreakProc(editor, pOtherRun->member.run.strText,
00705                                  pOtherRun->member.run.strText->nLen - 1,
00706                                  WB_ISDELIMITER)
00707             && !(pRun->member.run.nFlags & MERF_ENDPARA)
00708             && !(cursor->pRun == pRun && cursor->nOffset == 0)
00709             && !ME_CallWordBreakProc(editor, pRun->member.run.strText, 0,
00710                                      WB_ISDELIMITER))
00711           break;
00712         pRun = pOtherRun;
00713         nOffset = pOtherRun->member.run.strText->nLen;
00714       }
00715       else if (pOtherRun->type == diParagraph)
00716       {
00717         if (cursor->pRun == pRun && cursor->nOffset == 0)
00718         {
00719           pPara = pOtherRun;
00720           /* Skip empty start of table row paragraph */
00721           if (pPara->member.para.prev_para->member.para.nFlags & MEPF_ROWSTART)
00722             pPara = pPara->member.para.prev_para;
00723           /* Paragraph breaks are treated as separate words */
00724           if (pPara->member.para.prev_para->type == diTextStart)
00725             return FALSE;
00726 
00727           pRun = ME_FindItemBack(pPara, diRun);
00728           pPara = pPara->member.para.prev_para;
00729         }
00730         break;
00731       }
00732     }
00733   }
00734   else
00735   {
00736     /* Forward movement */
00737     BOOL last_delim = FALSE;
00738     
00739     while (TRUE)
00740     {
00741       if (last_delim && !ME_CallWordBreakProc(editor, pRun->member.run.strText,
00742                                               nOffset, WB_ISDELIMITER))
00743         break;
00744       nOffset = ME_CallWordBreakProc(editor, pRun->member.run.strText,
00745                                      nOffset, WB_MOVEWORDRIGHT);
00746       if (nOffset < pRun->member.run.strText->nLen)
00747         break;
00748       pOtherRun = ME_FindItemFwd(pRun, diRunOrParagraphOrEnd);
00749       if (pOtherRun->type == diRun)
00750       {
00751         last_delim = ME_CallWordBreakProc(editor, pRun->member.run.strText,
00752                                           nOffset - 1, WB_ISDELIMITER);
00753         pRun = pOtherRun;
00754         nOffset = 0;
00755       }
00756       else if (pOtherRun->type == diParagraph)
00757       {
00758         if (pOtherRun->member.para.nFlags & MEPF_ROWSTART)
00759             pOtherRun = pOtherRun->member.para.next_para;
00760         if (cursor->pRun == pRun) {
00761           pPara = pOtherRun;
00762           pRun = ME_FindItemFwd(pPara, diRun);
00763         }
00764         nOffset = 0;
00765         break;
00766       }
00767       else /* diTextEnd */
00768       {
00769         if (cursor->pRun == pRun)
00770           return FALSE;
00771         nOffset = 0;
00772         break;
00773       }
00774     }
00775   }
00776   cursor->pPara = pPara;
00777   cursor->pRun = pRun;
00778   cursor->nOffset = nOffset;
00779   return TRUE;
00780 }
00781 
00782 
00783 static void
00784 ME_SelectByType(ME_TextEditor *editor, ME_SelectionType selectionType)
00785 {
00786   /* pCursor[0] is the end of the selection
00787    * pCursor[1] is the start of the selection (or the position selection anchor)
00788    * pCursor[2] and [3] are the selection anchors that are backed up
00789    * so they are kept when the selection changes for drag selection.
00790    */
00791 
00792   editor->nSelectionType = selectionType;
00793   switch(selectionType)
00794   {
00795     case stPosition:
00796       break;
00797     case stWord:
00798       ME_MoveCursorWords(editor, &editor->pCursors[0], +1);
00799       editor->pCursors[1] = editor->pCursors[0];
00800       ME_MoveCursorWords(editor, &editor->pCursors[1], -1);
00801       break;
00802     case stLine:
00803     case stParagraph:
00804     {
00805       ME_DisplayItem *pItem;
00806       ME_DIType fwdSearchType, backSearchType;
00807       if (selectionType == stParagraph) {
00808           backSearchType = diParagraph;
00809           fwdSearchType = diParagraphOrEnd;
00810       } else {
00811           backSearchType = diStartRow;
00812           fwdSearchType = diStartRowOrParagraphOrEnd;
00813       }
00814       pItem = ME_FindItemFwd(editor->pCursors[0].pRun, fwdSearchType);
00815       assert(pItem);
00816       if (pItem->type == diTextEnd)
00817           editor->pCursors[0].pRun = ME_FindItemBack(pItem, diRun);
00818       else
00819           editor->pCursors[0].pRun = ME_FindItemFwd(pItem, diRun);
00820       editor->pCursors[0].pPara = ME_GetParagraph(editor->pCursors[0].pRun);
00821       editor->pCursors[0].nOffset = 0;
00822 
00823       pItem = ME_FindItemBack(pItem, backSearchType);
00824       editor->pCursors[1].pRun = ME_FindItemFwd(pItem, diRun);
00825       editor->pCursors[1].pPara = ME_GetParagraph(editor->pCursors[1].pRun);
00826       editor->pCursors[1].nOffset = 0;
00827       break;
00828     }
00829     case stDocument:
00830       /* Select everything with cursor anchored from the start of the text */
00831       editor->nSelectionType = stDocument;
00832       ME_SetCursorToStart(editor, &editor->pCursors[1]);
00833       ME_SetCursorToEnd(editor, &editor->pCursors[0]);
00834       break;
00835     default: assert(0);
00836   }
00837   /* Store the anchor positions for extending the selection. */
00838   editor->pCursors[2] = editor->pCursors[0];
00839   editor->pCursors[3] = editor->pCursors[1];
00840 }
00841 
00842 int ME_GetCursorOfs(const ME_Cursor *cursor)
00843 {
00844   return cursor->pPara->member.para.nCharOfs
00845          + cursor->pRun->member.run.nCharOfs + cursor->nOffset;
00846 }
00847 
00848 /* Helper function for ME_FindPixelPos to find paragraph within tables */
00849 static ME_DisplayItem* ME_FindPixelPosInTableRow(int x, int y,
00850                                                  ME_DisplayItem *para)
00851 {
00852   ME_DisplayItem *cell, *next_cell;
00853   assert(para->member.para.nFlags & MEPF_ROWSTART);
00854   cell = para->member.para.next_para->member.para.pCell;
00855   assert(cell);
00856 
00857   /* find the cell we are in */
00858   while ((next_cell = cell->member.cell.next_cell) != NULL) {
00859     if (x < next_cell->member.cell.pt.x)
00860     {
00861       para = ME_FindItemFwd(cell, diParagraph);
00862       /* Found the cell, but there might be multiple paragraphs in
00863        * the cell, so need to search down the cell for the paragraph. */
00864       while (cell == para->member.para.pCell) {
00865         if (y < para->member.para.pt.y + para->member.para.nHeight)
00866         {
00867           if (para->member.para.nFlags & MEPF_ROWSTART)
00868             return ME_FindPixelPosInTableRow(x, y, para);
00869           else
00870             return para;
00871         }
00872         para = para->member.para.next_para;
00873       }
00874       /* Past the end of the cell, so go back to the last cell paragraph */
00875       return para->member.para.prev_para;
00876     }
00877     cell = next_cell;
00878   }
00879   /* Return table row delimiter */
00880   para = ME_FindItemFwd(cell, diParagraph);
00881   assert(para->member.para.nFlags & MEPF_ROWEND);
00882   assert(para->member.para.pFmt->dwMask & PFM_TABLEROWDELIMITER);
00883   assert(para->member.para.pFmt->wEffects & PFE_TABLEROWDELIMITER);
00884   return para;
00885 }
00886 
00887 static BOOL ME_ReturnFoundPos(ME_TextEditor *editor, ME_DisplayItem *found,
00888                                ME_Cursor *result, int rx, BOOL isExact)
00889 {
00890   assert(found);
00891   assert(found->type == diRun);
00892   if ((found->member.run.nFlags & MERF_ENDPARA) || rx < 0)
00893     rx = 0;
00894   result->pRun = found;
00895   result->nOffset = ME_CharFromPointCursor(editor, rx, &found->member.run);
00896   if (result->nOffset == found->member.run.strText->nLen && rx)
00897   {
00898     result->pRun = ME_FindItemFwd(result->pRun, diRun);
00899     result->nOffset = 0;
00900   }
00901   result->pPara = ME_GetParagraph(result->pRun);
00902   return isExact;
00903 }
00904 
00905 /* Finds the run and offset from the pixel position.
00906  *
00907  * x & y are pixel positions in virtual coordinates into the rich edit control,
00908  * so client coordinates must first be adjusted by the scroll position.
00909  *
00910  * returns TRUE if the result was exactly under the cursor, otherwise returns
00911  * FALSE, and result is set to the closest position to the coordinates.
00912  */
00913 static BOOL ME_FindPixelPos(ME_TextEditor *editor, int x, int y,
00914                             ME_Cursor *result, BOOL *is_eol)
00915 {
00916   ME_DisplayItem *p = editor->pBuffer->pFirst->member.para.next_para;
00917   ME_DisplayItem *last = NULL;
00918   int rx = 0;
00919   BOOL isExact = TRUE;
00920 
00921   x -= editor->rcFormat.left;
00922   y -= editor->rcFormat.top;
00923 
00924   if (is_eol)
00925     *is_eol = 0;
00926 
00927   /* find paragraph */
00928   for (; p != editor->pBuffer->pLast; p = p->member.para.next_para)
00929   {
00930     assert(p->type == diParagraph);
00931     if (y < p->member.para.pt.y + p->member.para.nHeight)
00932     {
00933       if (p->member.para.nFlags & MEPF_ROWSTART)
00934         p = ME_FindPixelPosInTableRow(x, y, p);
00935       y -= p->member.para.pt.y;
00936       p = ME_FindItemFwd(p, diStartRow);
00937       break;
00938     } else if (p->member.para.nFlags & MEPF_ROWSTART) {
00939       p = ME_GetTableRowEnd(p);
00940     }
00941   }
00942   /* find row */
00943   for (; p != editor->pBuffer->pLast; )
00944   {
00945     ME_DisplayItem *pp;
00946     assert(p->type == diStartRow);
00947     if (y < p->member.row.pt.y + p->member.row.nHeight)
00948     {
00949         p = ME_FindItemFwd(p, diRun);
00950         break;
00951     }
00952     pp = ME_FindItemFwd(p, diStartRowOrParagraphOrEnd);
00953     if (pp->type != diStartRow)
00954     {
00955         p = ME_FindItemFwd(p, diRun);
00956         break;
00957     }
00958     p = pp;
00959   }
00960   if (p == editor->pBuffer->pLast)
00961   {
00962     /* The position is below the last paragraph, so the last row will be used
00963      * rather than the end of the text, so the x position will be used to
00964      * determine the offset closest to the pixel position. */
00965     isExact = FALSE;
00966     p = ME_FindItemBack(p, diStartRow);
00967     if (p != NULL){
00968       p = ME_FindItemFwd(p, diRun);
00969     }
00970     else
00971     {
00972       p = editor->pBuffer->pLast;
00973     }
00974   }
00975   for (; p != editor->pBuffer->pLast; p = p->next)
00976   {
00977     switch (p->type)
00978     {
00979     case diRun:
00980       rx = x - p->member.run.pt.x;
00981       if (rx < p->member.run.nWidth)
00982         return ME_ReturnFoundPos(editor, p, result, rx, isExact);
00983       break;
00984     case diStartRow:
00985       isExact = FALSE;
00986       p = ME_FindItemFwd(p, diRun);
00987       if (is_eol) *is_eol = 1;
00988       rx = 0; /* FIXME not sure */
00989       return ME_ReturnFoundPos(editor, p, result, rx, isExact);
00990     case diCell:
00991     case diParagraph:
00992     case diTextEnd:
00993       isExact = FALSE;
00994       rx = 0; /* FIXME not sure */
00995       p = last;
00996       return ME_ReturnFoundPos(editor, p, result, rx, isExact);
00997     default: assert(0);
00998     }
00999     last = p;
01000   }
01001   result->pRun = ME_FindItemBack(p, diRun);
01002   result->pPara = ME_GetParagraph(result->pRun);
01003   result->nOffset = 0;
01004   assert(result->pRun->member.run.nFlags & MERF_ENDPARA);
01005   return FALSE;
01006 }
01007 
01008 
01009 /* Sets the cursor to the position closest to the pixel position
01010  *
01011  * x & y are pixel positions in client coordinates.
01012  *
01013  * isExact will be set to TRUE if the run is directly under the pixel
01014  * position, FALSE if it not, unless isExact is set to NULL.
01015  *
01016  * return FALSE if outside client area and the cursor is not set,
01017  * otherwise TRUE is returned.
01018  */
01019 BOOL ME_CharFromPos(ME_TextEditor *editor, int x, int y,
01020                     ME_Cursor *cursor, BOOL *isExact)
01021 {
01022   RECT rc;
01023   BOOL bResult;
01024 
01025   ITextHost_TxGetClientRect(editor->texthost, &rc);
01026   if (x < 0 || y < 0 || x >= rc.right || y >= rc.bottom) {
01027     if (isExact) *isExact = FALSE;
01028     return FALSE;
01029   }
01030   x += editor->horz_si.nPos;
01031   y += editor->vert_si.nPos;
01032   bResult = ME_FindPixelPos(editor, x, y, cursor, NULL);
01033   if (isExact) *isExact = bResult;
01034   return TRUE;
01035 }
01036 
01037 
01038 
01039 /* Extends the selection with a word, line, or paragraph selection type.
01040  *
01041  * The selection is anchored by editor->pCursors[2-3] such that the text
01042  * between the anchors will remain selected, and one end will be extended.
01043  *
01044  * editor->pCursors[0] should have the position to extend the selection to
01045  * before this function is called.
01046  *
01047  * Nothing will be done if editor->nSelectionType equals stPosition.
01048  */
01049 static void ME_ExtendAnchorSelection(ME_TextEditor *editor)
01050 {
01051   ME_Cursor tmp_cursor;
01052   int curOfs, anchorStartOfs, anchorEndOfs;
01053   if (editor->nSelectionType == stPosition || editor->nSelectionType == stDocument)
01054       return;
01055   curOfs = ME_GetCursorOfs(&editor->pCursors[0]);
01056   anchorStartOfs = ME_GetCursorOfs(&editor->pCursors[3]);
01057   anchorEndOfs = ME_GetCursorOfs(&editor->pCursors[2]);
01058 
01059   tmp_cursor = editor->pCursors[0];
01060   editor->pCursors[0] = editor->pCursors[2];
01061   editor->pCursors[1] = editor->pCursors[3];
01062   if (curOfs < anchorStartOfs)
01063   {
01064       /* Extend the left side of selection */
01065       editor->pCursors[1] = tmp_cursor;
01066       if (editor->nSelectionType == stWord)
01067           ME_MoveCursorWords(editor, &editor->pCursors[1], -1);
01068       else
01069       {
01070           ME_DisplayItem *pItem;
01071           ME_DIType searchType = ((editor->nSelectionType == stLine) ?
01072                                   diStartRowOrParagraph:diParagraph);
01073           pItem = ME_FindItemBack(editor->pCursors[1].pRun, searchType);
01074           editor->pCursors[1].pRun = ME_FindItemFwd(pItem, diRun);
01075           editor->pCursors[1].pPara = ME_GetParagraph(editor->pCursors[1].pRun);
01076           editor->pCursors[1].nOffset = 0;
01077       }
01078   }
01079   else if (curOfs >= anchorEndOfs)
01080   {
01081       /* Extend the right side of selection */
01082       editor->pCursors[0] = tmp_cursor;
01083       if (editor->nSelectionType == stWord)
01084           ME_MoveCursorWords(editor, &editor->pCursors[0], +1);
01085       else
01086       {
01087           ME_DisplayItem *pItem;
01088           ME_DIType searchType = ((editor->nSelectionType == stLine) ?
01089                                   diStartRowOrParagraphOrEnd:diParagraphOrEnd);
01090           pItem = ME_FindItemFwd(editor->pCursors[0].pRun, searchType);
01091           if (pItem->type == diTextEnd)
01092               editor->pCursors[0].pRun = ME_FindItemBack(pItem, diRun);
01093           else
01094               editor->pCursors[0].pRun = ME_FindItemFwd(pItem, diRun);
01095           editor->pCursors[0].pPara = ME_GetParagraph(editor->pCursors[0].pRun);
01096           editor->pCursors[0].nOffset = 0;
01097       }
01098   }
01099 }
01100 
01101 void ME_LButtonDown(ME_TextEditor *editor, int x, int y, int clickNum)
01102 {
01103   ME_Cursor tmp_cursor;
01104   int is_selection = 0;
01105   BOOL is_shift;
01106 
01107   editor->nUDArrowX = -1;
01108 
01109   x += editor->horz_si.nPos;
01110   y += editor->vert_si.nPos;
01111 
01112   tmp_cursor = editor->pCursors[0];
01113   is_selection = ME_IsSelection(editor);
01114   is_shift = GetKeyState(VK_SHIFT) < 0;
01115 
01116   ME_FindPixelPos(editor, x, y, &editor->pCursors[0], &editor->bCaretAtEnd);
01117 
01118   if (x >= editor->rcFormat.left || is_shift)
01119   {
01120     if (clickNum > 1)
01121     {
01122       editor->pCursors[1] = editor->pCursors[0];
01123       if (is_shift) {
01124           if (x >= editor->rcFormat.left)
01125               ME_SelectByType(editor, stWord);
01126           else
01127               ME_SelectByType(editor, stParagraph);
01128       } else if (clickNum % 2 == 0) {
01129           ME_SelectByType(editor, stWord);
01130       } else {
01131           ME_SelectByType(editor, stParagraph);
01132       }
01133     }
01134     else if (!is_shift)
01135     {
01136       editor->nSelectionType = stPosition;
01137       editor->pCursors[1] = editor->pCursors[0];
01138     }
01139     else if (!is_selection)
01140     {
01141       editor->nSelectionType = stPosition;
01142       editor->pCursors[1] = tmp_cursor;
01143     }
01144     else if (editor->nSelectionType != stPosition)
01145     {
01146       ME_ExtendAnchorSelection(editor);
01147     }
01148   }
01149   else
01150   {
01151     if (clickNum < 2) {
01152         ME_SelectByType(editor, stLine);
01153     } else if (clickNum % 2 == 0 || is_shift) {
01154         ME_SelectByType(editor, stParagraph);
01155     } else {
01156         ME_SelectByType(editor, stDocument);
01157     }
01158   }
01159   ME_InvalidateSelection(editor);
01160   ITextHost_TxShowCaret(editor->texthost, FALSE);
01161   ME_ShowCaret(editor);
01162   ME_ClearTempStyle(editor);
01163   ME_SendSelChange(editor);
01164 }
01165 
01166 void ME_MouseMove(ME_TextEditor *editor, int x, int y)
01167 {
01168   ME_Cursor tmp_cursor;
01169 
01170   if (editor->nSelectionType == stDocument)
01171       return;
01172   x += editor->horz_si.nPos;
01173   y += editor->vert_si.nPos;
01174 
01175   tmp_cursor = editor->pCursors[0];
01176   /* FIXME: do something with the return value of ME_FindPixelPos */
01177   ME_FindPixelPos(editor, x, y, &tmp_cursor, &editor->bCaretAtEnd);
01178 
01179   ME_InvalidateSelection(editor);
01180   editor->pCursors[0] = tmp_cursor;
01181   ME_ExtendAnchorSelection(editor);
01182 
01183   if (editor->nSelectionType != stPosition &&
01184       memcmp(&editor->pCursors[1], &editor->pCursors[3], sizeof(ME_Cursor)))
01185   {
01186       /* The scroll the cursor towards the other end, since it was the one
01187        * extended by ME_ExtendAnchorSelection */
01188       ME_EnsureVisible(editor, &editor->pCursors[1]);
01189   } else {
01190       ME_EnsureVisible(editor, &editor->pCursors[0]);
01191   }
01192 
01193   ME_InvalidateSelection(editor);
01194   ITextHost_TxShowCaret(editor->texthost, FALSE);
01195   ME_ShowCaret(editor);
01196   ME_SendSelChange(editor);
01197 }
01198 
01199 static ME_DisplayItem *ME_FindRunInRow(ME_TextEditor *editor, ME_DisplayItem *pRow, 
01200                                 int x, int *pOffset, int *pbCaretAtEnd)
01201 {
01202   ME_DisplayItem *pNext, *pLastRun;
01203   pNext = ME_FindItemFwd(pRow, diRunOrStartRow);
01204   assert(pNext->type == diRun);
01205   if (pbCaretAtEnd) *pbCaretAtEnd = FALSE;
01206   if (pOffset) *pOffset = 0;
01207   do {
01208     int run_x = pNext->member.run.pt.x;
01209     int width = pNext->member.run.nWidth;
01210     if (x < run_x)
01211     {
01212       return pNext;
01213     }
01214     if (x >= run_x && x < run_x+width)
01215     {
01216       int ch = ME_CharFromPointCursor(editor, x-run_x, &pNext->member.run);
01217       ME_String *s = pNext->member.run.strText;
01218       if (ch < s->nLen) {
01219         if (pOffset)
01220           *pOffset = ch;
01221         return pNext;          
01222       }
01223     }
01224     pLastRun = pNext;
01225     pNext = ME_FindItemFwd(pNext, diRunOrStartRow);
01226   } while(pNext && pNext->type == diRun);
01227   
01228   if ((pLastRun->member.run.nFlags & MERF_ENDPARA) == 0)
01229   {
01230     pNext = ME_FindItemFwd(pNext, diRun);
01231     if (pbCaretAtEnd) *pbCaretAtEnd = TRUE;
01232     return pNext;
01233   } else {
01234     return pLastRun;
01235   }
01236 }
01237 
01238 static int ME_GetXForArrow(ME_TextEditor *editor, ME_Cursor *pCursor)
01239 {
01240   ME_DisplayItem *pRun = pCursor->pRun;
01241   int x;
01242 
01243   if (editor->nUDArrowX != -1)
01244     x = editor->nUDArrowX;
01245   else {
01246     if (editor->bCaretAtEnd)
01247     {
01248       pRun = ME_FindItemBack(pRun, diRun);
01249       assert(pRun);
01250       x = pRun->member.run.pt.x + pRun->member.run.nWidth;
01251     }
01252     else {
01253       x = pRun->member.run.pt.x;
01254       x += ME_PointFromChar(editor, &pRun->member.run, pCursor->nOffset);
01255     }
01256     editor->nUDArrowX = x;
01257   }
01258   return x;
01259 }
01260 
01261 
01262 static void
01263 ME_MoveCursorLines(ME_TextEditor *editor, ME_Cursor *pCursor, int nRelOfs)
01264 {
01265   ME_DisplayItem *pRun = pCursor->pRun;
01266   ME_DisplayItem *pOldPara = pCursor->pPara;
01267   ME_DisplayItem *pItem, *pNewPara;
01268   int x = ME_GetXForArrow(editor, pCursor);
01269 
01270   if (editor->bCaretAtEnd && !pCursor->nOffset)
01271     if (!ME_PrevRun(&pOldPara, &pRun))
01272       return;
01273 
01274   if (nRelOfs == -1)
01275   {
01276     /* start of this row */
01277     pItem = ME_FindItemBack(pRun, diStartRow);
01278     assert(pItem);
01279     /* start of the previous row */
01280     pItem = ME_FindItemBack(pItem, diStartRow);
01281     if (!pItem)
01282       return; /* row not found - ignore */
01283     pNewPara = ME_GetParagraph(pItem);
01284     if (pOldPara->member.para.nFlags & MEPF_ROWEND ||
01285         (pOldPara->member.para.pCell &&
01286          pOldPara->member.para.pCell != pNewPara->member.para.pCell))
01287     {
01288       /* Brought out of a cell */
01289       pNewPara = ME_GetTableRowStart(pOldPara)->member.para.prev_para;
01290       if (pNewPara->type == diTextStart)
01291         return; /* At the top, so don't go anywhere. */
01292       pItem = ME_FindItemFwd(pNewPara, diStartRow);
01293     }
01294     if (pNewPara->member.para.nFlags & MEPF_ROWEND)
01295     {
01296       /* Brought into a table row */
01297       ME_Cell *cell = &ME_FindItemBack(pNewPara, diCell)->member.cell;
01298       while (x < cell->pt.x && cell->prev_cell)
01299         cell = &cell->prev_cell->member.cell;
01300       if (cell->next_cell) /* else - we are still at the end of the row */
01301         pItem = ME_FindItemBack(cell->next_cell, diStartRow);
01302     }
01303   }
01304   else
01305   {
01306     /* start of the next row */
01307     pItem = ME_FindItemFwd(pRun, diStartRow);
01308     if (!pItem)
01309       return; /* row not found - ignore */
01310     pNewPara = ME_GetParagraph(pItem);
01311     if (pOldPara->member.para.nFlags & MEPF_ROWSTART ||
01312         (pOldPara->member.para.pCell &&
01313          pOldPara->member.para.pCell != pNewPara->member.para.pCell))
01314     {
01315       /* Brought out of a cell */
01316       pNewPara = ME_GetTableRowEnd(pOldPara)->member.para.next_para;
01317       if (pNewPara->type == diTextEnd)
01318         return; /* At the bottom, so don't go anywhere. */
01319       pItem = ME_FindItemFwd(pNewPara, diStartRow);
01320     }
01321     if (pNewPara->member.para.nFlags & MEPF_ROWSTART)
01322     {
01323       /* Brought into a table row */
01324       ME_DisplayItem *cell = ME_FindItemFwd(pNewPara, diCell);
01325       while (cell->member.cell.next_cell &&
01326              x >= cell->member.cell.next_cell->member.cell.pt.x)
01327         cell = cell->member.cell.next_cell;
01328       pItem = ME_FindItemFwd(cell, diStartRow);
01329     }
01330   }
01331   if (!pItem)
01332   {
01333     /* row not found - ignore */
01334     return;
01335   }
01336   pCursor->pRun = ME_FindRunInRow(editor, pItem, x, &pCursor->nOffset, &editor->bCaretAtEnd);
01337   pCursor->pPara = ME_GetParagraph(pCursor->pRun);
01338   assert(pCursor->pRun);
01339   assert(pCursor->pRun->type == diRun);
01340 }
01341 
01342 static void ME_ArrowPageUp(ME_TextEditor *editor, ME_Cursor *pCursor)
01343 {
01344   ME_DisplayItem *p = ME_FindItemFwd(editor->pBuffer->pFirst, diStartRow);
01345 
01346   if (editor->vert_si.nPos < p->member.row.nHeight)
01347   {
01348     ME_SetCursorToStart(editor, pCursor);
01349     editor->bCaretAtEnd = FALSE;
01350     /* Native clears seems to clear this x value on page up at the top
01351      * of the text, but not on page down at the end of the text.
01352      * Doesn't make sense, but we try to be bug for bug compatible. */
01353     editor->nUDArrowX = -1;
01354   } else {
01355     ME_DisplayItem *pRun = pCursor->pRun;
01356     ME_DisplayItem *pLast;
01357     int x, y, yd, yp;
01358     int yOldScrollPos = editor->vert_si.nPos;
01359 
01360     x = ME_GetXForArrow(editor, pCursor);
01361     if (!pCursor->nOffset && editor->bCaretAtEnd)
01362       pRun = ME_FindItemBack(pRun, diRun);
01363 
01364     p = ME_FindItemBack(pRun, diStartRowOrParagraph);
01365     assert(p->type == diStartRow);
01366     yp = ME_FindItemBack(p, diParagraph)->member.para.pt.y;
01367     y = yp + p->member.row.pt.y;
01368 
01369     ME_ScrollUp(editor, editor->sizeWindow.cy);
01370     /* Only move the cursor by the amount scrolled. */
01371     yd = y + editor->vert_si.nPos - yOldScrollPos;
01372     pLast = p;
01373 
01374     do {
01375       p = ME_FindItemBack(p, diStartRowOrParagraph);
01376       if (!p)
01377         break;
01378       if (p->type == diParagraph) { /* crossing paragraphs */
01379         if (p->member.para.prev_para == NULL)
01380           break;
01381         yp = p->member.para.prev_para->member.para.pt.y;
01382         continue;
01383       }
01384       y = yp + p->member.row.pt.y;
01385       if (y < yd)
01386         break;
01387       pLast = p;
01388     } while(1);
01389 
01390     pCursor->pRun = ME_FindRunInRow(editor, pLast, x, &pCursor->nOffset,
01391                                     &editor->bCaretAtEnd);
01392     pCursor->pPara = ME_GetParagraph(pCursor->pRun);
01393   }
01394   assert(pCursor->pRun);
01395   assert(pCursor->pRun->type == diRun);
01396 }
01397 
01398 static void ME_ArrowPageDown(ME_TextEditor *editor, ME_Cursor *pCursor)
01399 {
01400   ME_DisplayItem *pLast;
01401   int x, y;
01402 
01403   /* Find y position of the last row */
01404   pLast = editor->pBuffer->pLast;
01405   y = pLast->member.para.prev_para->member.para.pt.y
01406       + ME_FindItemBack(pLast, diStartRow)->member.row.pt.y;
01407 
01408   x = ME_GetXForArrow(editor, pCursor);
01409 
01410   if (editor->vert_si.nPos >= y - editor->sizeWindow.cy)
01411   {
01412     ME_SetCursorToEnd(editor, pCursor);
01413     editor->bCaretAtEnd = FALSE;
01414   } else {
01415     ME_DisplayItem *pRun = pCursor->pRun;
01416     ME_DisplayItem *p;
01417     int yd, yp;
01418     int yOldScrollPos = editor->vert_si.nPos;
01419 
01420     if (!pCursor->nOffset && editor->bCaretAtEnd)
01421       pRun = ME_FindItemBack(pRun, diRun);
01422 
01423     p = ME_FindItemBack(pRun, diStartRowOrParagraph);
01424     assert(p->type == diStartRow);
01425     yp = ME_FindItemBack(p, diParagraph)->member.para.pt.y;
01426     y = yp + p->member.row.pt.y;
01427 
01428     /* For native richedit controls:
01429      * v1.0 - v3.1 can only scroll down as far as the scrollbar lets us
01430      * v4.1 can scroll past this position here. */
01431     ME_ScrollDown(editor, editor->sizeWindow.cy);
01432     /* Only move the cursor by the amount scrolled. */
01433     yd = y + editor->vert_si.nPos - yOldScrollPos;
01434     pLast = p;
01435 
01436     do {
01437       p = ME_FindItemFwd(p, diStartRowOrParagraph);
01438       if (!p)
01439         break;
01440       if (p->type == diParagraph) {
01441         yp = p->member.para.pt.y;
01442         continue;
01443       }
01444       y = yp + p->member.row.pt.y;
01445       if (y >= yd)
01446         break;
01447       pLast = p;
01448     } while(1);
01449 
01450     pCursor->pRun = ME_FindRunInRow(editor, pLast, x, &pCursor->nOffset,
01451                                     &editor->bCaretAtEnd);
01452     pCursor->pPara = ME_GetParagraph(pCursor->pRun);
01453   }
01454   assert(pCursor->pRun);
01455   assert(pCursor->pRun->type == diRun);
01456 }
01457 
01458 static void ME_ArrowHome(ME_TextEditor *editor, ME_Cursor *pCursor)
01459 {
01460   ME_DisplayItem *pRow = ME_FindItemBack(pCursor->pRun, diStartRow);
01461   if (pRow) {
01462     ME_DisplayItem *pRun;
01463     if (editor->bCaretAtEnd && !pCursor->nOffset) {
01464       pRow = ME_FindItemBack(pRow, diStartRow);
01465       if (!pRow)
01466         return;
01467     }
01468     pRun = ME_FindItemFwd(pRow, diRun);
01469     if (pRun) {
01470       pCursor->pRun = pRun;
01471       assert(pCursor->pPara == ME_GetParagraph(pRun));
01472       pCursor->nOffset = 0;
01473     }
01474   }
01475   editor->bCaretAtEnd = FALSE;
01476 }
01477 
01478 static void ME_ArrowCtrlHome(ME_TextEditor *editor, ME_Cursor *pCursor)
01479 {
01480   ME_SetCursorToStart(editor, pCursor);
01481   editor->bCaretAtEnd = FALSE;
01482 }
01483 
01484 static void ME_ArrowEnd(ME_TextEditor *editor, ME_Cursor *pCursor)
01485 {
01486   ME_DisplayItem *pRow;
01487 
01488   if (editor->bCaretAtEnd && !pCursor->nOffset)
01489     return;
01490 
01491   pRow = ME_FindItemFwd(pCursor->pRun, diStartRowOrParagraphOrEnd);
01492   assert(pRow);
01493   if (pRow->type == diStartRow) {
01494     ME_DisplayItem *pRun = ME_FindItemFwd(pRow, diRun);
01495     assert(pRun);
01496     pCursor->pRun = pRun;
01497     assert(pCursor->pPara == ME_GetParagraph(pCursor->pRun));
01498     pCursor->nOffset = 0;
01499     editor->bCaretAtEnd = TRUE;
01500     return;
01501   }
01502   pCursor->pRun = ME_FindItemBack(pRow, diRun);
01503   assert(pCursor->pRun && pCursor->pRun->member.run.nFlags & MERF_ENDPARA);
01504   assert(pCursor->pPara == ME_GetParagraph(pCursor->pRun));
01505   pCursor->nOffset = 0;
01506   editor->bCaretAtEnd = FALSE;
01507 }
01508 
01509 static void ME_ArrowCtrlEnd(ME_TextEditor *editor, ME_Cursor *pCursor)
01510 {
01511   ME_SetCursorToEnd(editor, pCursor);
01512   editor->bCaretAtEnd = FALSE;
01513 }
01514 
01515 BOOL ME_IsSelection(ME_TextEditor *editor)
01516 {
01517   return editor->pCursors[0].pRun != editor->pCursors[1].pRun ||
01518          editor->pCursors[0].nOffset != editor->pCursors[1].nOffset;
01519 }
01520 
01521 void ME_DeleteSelection(ME_TextEditor *editor)
01522 {
01523   int from, to;
01524   int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
01525   ME_DeleteTextAtCursor(editor, nStartCursor, to - from);
01526 }
01527 
01528 ME_Style *ME_GetSelectionInsertStyle(ME_TextEditor *editor)
01529 {
01530   return ME_GetInsertStyle(editor, 0);
01531 }
01532 
01533 void ME_SendSelChange(ME_TextEditor *editor)
01534 {
01535   SELCHANGE sc;
01536 
01537   if (!(editor->nEventMask & ENM_SELCHANGE))
01538     return;
01539 
01540   sc.nmhdr.hwndFrom = NULL;
01541   sc.nmhdr.idFrom = 0;
01542   sc.nmhdr.code = EN_SELCHANGE;
01543   ME_GetSelectionOfs(editor, &sc.chrg.cpMin, &sc.chrg.cpMax);
01544   sc.seltyp = SEL_EMPTY;
01545   if (sc.chrg.cpMin != sc.chrg.cpMax)
01546     sc.seltyp |= SEL_TEXT;
01547   if (sc.chrg.cpMin < sc.chrg.cpMax+1) /* what were RICHEDIT authors thinking ? */
01548     sc.seltyp |= SEL_MULTICHAR;
01549   TRACE("cpMin=%d cpMax=%d seltyp=%d (%s %s)\n",
01550     sc.chrg.cpMin, sc.chrg.cpMax, sc.seltyp,
01551     (sc.seltyp & SEL_TEXT) ? "SEL_TEXT" : "",
01552     (sc.seltyp & SEL_MULTICHAR) ? "SEL_MULTICHAR" : "");
01553   if (sc.chrg.cpMin != editor->notified_cr.cpMin || sc.chrg.cpMax != editor->notified_cr.cpMax)
01554   {
01555     ME_ClearTempStyle(editor);
01556 
01557     editor->notified_cr = sc.chrg;
01558     ITextHost_TxNotify(editor->texthost, sc.nmhdr.code, &sc);
01559   }
01560 }
01561 
01562 BOOL
01563 ME_ArrowKey(ME_TextEditor *editor, int nVKey, BOOL extend, BOOL ctrl)
01564 {
01565   int nCursor = 0;
01566   ME_Cursor *p = &editor->pCursors[nCursor];
01567   ME_Cursor tmp_curs = *p;
01568   BOOL success = FALSE;
01569 
01570   ME_CheckCharOffsets(editor);
01571   switch(nVKey) {
01572     case VK_LEFT:
01573       editor->bCaretAtEnd = 0;
01574       if (ctrl)
01575         success = ME_MoveCursorWords(editor, &tmp_curs, -1);
01576       else
01577         success = ME_MoveCursorChars(editor, &tmp_curs, -1);
01578       break;
01579     case VK_RIGHT:
01580       editor->bCaretAtEnd = 0;
01581       if (ctrl)
01582         success = ME_MoveCursorWords(editor, &tmp_curs, +1);
01583       else
01584         success = ME_MoveCursorChars(editor, &tmp_curs, +1);
01585       break;
01586     case VK_UP:
01587       ME_MoveCursorLines(editor, &tmp_curs, -1);
01588       break;
01589     case VK_DOWN:
01590       ME_MoveCursorLines(editor, &tmp_curs, +1);
01591       break;
01592     case VK_PRIOR:
01593       ME_ArrowPageUp(editor, &tmp_curs);
01594       break;
01595     case VK_NEXT:
01596       ME_ArrowPageDown(editor, &tmp_curs);
01597       break;
01598     case VK_HOME: {
01599       if (ctrl)
01600         ME_ArrowCtrlHome(editor, &tmp_curs);
01601       else
01602         ME_ArrowHome(editor, &tmp_curs);
01603       editor->bCaretAtEnd = 0;
01604       break;
01605     }
01606     case VK_END:
01607       if (ctrl)
01608         ME_ArrowCtrlEnd(editor, &tmp_curs);
01609       else
01610         ME_ArrowEnd(editor, &tmp_curs);
01611       break;
01612   }
01613 
01614   if (!extend)
01615     editor->pCursors[1] = tmp_curs;
01616   *p = tmp_curs;
01617 
01618   ME_InvalidateSelection(editor);
01619   ME_Repaint(editor);
01620   ITextHost_TxShowCaret(editor->texthost, FALSE);
01621   ME_EnsureVisible(editor, &tmp_curs);
01622   ME_ShowCaret(editor);
01623   ME_SendSelChange(editor);
01624   return success;
01625 }

Generated on Sun May 27 2012 04:25:57 for ReactOS by doxygen 1.7.6.1

ReactOS is a registered trademark or a trademark of ReactOS Foundation in the United States and other countries.