caret.c 48.2 KB
Newer Older
1 2 3 4
/*
 * RichEdit - Caret and selection functions.
 *
 * Copyright 2004 by Krzysztof Foltman
5
 * Copyright 2005 by Phil Krylov
6 7 8 9 10 11 12 13 14 15 16 17 18
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * 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 24 25 26
 */


#include "editor.h"

WINE_DEFAULT_DEBUG_CHANNEL(richedit);

27 28 29
static BOOL
ME_MoveCursorChars(ME_TextEditor *editor, ME_Cursor *pCursor, int nRelOfs);

30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
void ME_GetSelection(ME_TextEditor *editor, int *from, int *to)
{
  *from = ME_GetCursorOfs(editor, 0);
  *to =   ME_GetCursorOfs(editor, 1);
  
  if (*from > *to)
  {
    int tmp = *from;
    *from = *to;
    *to = tmp;    
  }
}

int ME_GetTextLength(ME_TextEditor *editor)
{
45 46 47
  ME_DisplayItem *pLast = editor->pBuffer->pLast;
  return ME_CharOfsFromRunOfs(editor, pLast->member.para.prev_para,
                              ME_FindItemBack(pLast, diRun), 0);
48 49
}

50

51
int ME_GetTextLengthEx(ME_TextEditor *editor, const GETTEXTLENGTHEX *how)
52 53
{
  int length;
54

55 56 57 58 59 60
  if (how->flags & GTL_PRECISE && how->flags & GTL_CLOSE)
    return E_INVALIDARG;
  if (how->flags & GTL_NUMCHARS && how->flags & GTL_NUMBYTES)
    return E_INVALIDARG;
  
  length = ME_GetTextLength(editor);
61

62
  if ((editor->styleFlags & ES_MULTILINE)
63 64
        && (how->flags & GTL_USECRLF)
        && !editor->bEmulateVersion10) /* Ignore GTL_USECRLF flag in 1.0 emulation */
65
    length += editor->nParagraphs - 1;
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
  
  if (how->flags & GTL_NUMBYTES)
  {
    CPINFO cpinfo;
    
    if (how->codepage == 1200)
      return length * 2;
    if (how->flags & GTL_PRECISE)
      FIXME("GTL_PRECISE flag unsupported. Using GTL_CLOSE\n");
    if (GetCPInfo(how->codepage, &cpinfo))
      return length * cpinfo.MaxCharSize;
    ERR("Invalid codepage %u\n", how->codepage);
    return E_INVALIDARG;
  }
  return length; 
}


84
int ME_SetSelection(ME_TextEditor *editor, int from, int to)
85
{
86 87 88 89 90 91 92 93 94 95
  int selectionEnd = 0;
  const int len = ME_GetTextLength(editor);

  /* all negative values are effectively the same */
  if (from < 0)
    from = -1;
  if (to < 0)
    to = -1;

  /* select all */
96 97
  if (from == 0 && to == -1)
  {
98 99 100 101 102
    editor->pCursors[1].pPara = editor->pBuffer->pFirst->member.para.next_para;
    editor->pCursors[1].pRun = ME_FindItemFwd(editor->pCursors[1].pPara, diRun);
    editor->pCursors[1].nOffset = 0;
    editor->pCursors[0].pPara = editor->pBuffer->pLast->member.para.prev_para;
    editor->pCursors[0].pRun = ME_FindItemBack(editor->pBuffer->pLast, diRun);
103
    editor->pCursors[0].nOffset = 0;
104
    ME_InvalidateSelection(editor);
105
    ME_ClearTempStyle(editor);
106
    return len + 1;
107
  }
108 109 110 111

  /* if both values are equal and also out of bound, that means to */
  /* put the selection at the end of the text */
  if ((from == to) && (to < 0 || to > len))
112
  {
113
    selectionEnd = 1;
114
  }
115
  else
116
  {
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
    /* if from is negative and to is positive then selection is */
    /* deselected and caret moved to end of the current selection */
    if (from < 0)
    {
      int start, end;
      ME_GetSelection(editor, &start, &end);
      editor->pCursors[1] = editor->pCursors[0];
      ME_Repaint(editor);
      ME_ClearTempStyle(editor);
      return end;
    }

    /* adjust to if it's a negative value */
    if (to < 0)
      to = len + 1;

    /* flip from and to if they are reversed */
    if (from>to)
    {
      int tmp = from;
      from = to;
      to = tmp;
    }

    /* after fiddling with the values, we find from > len && to > len */
    if (from > len)
      selectionEnd = 1;
    /* special case with to too big */
    else if (to > len)
      to = len + 1;
147
  }
148 149

  if (selectionEnd)
150
  {
151 152 153 154
    editor->pCursors[0].pPara = editor->pBuffer->pLast->member.para.prev_para;
    editor->pCursors[0].pRun = ME_FindItemBack(editor->pBuffer->pLast, diRun);
    editor->pCursors[0].nOffset = 0;
    editor->pCursors[1] = editor->pCursors[0];
155 156 157
    ME_InvalidateSelection(editor);
    ME_ClearTempStyle(editor);
    return len;
158
  }
159

160 161 162 163 164 165 166
  ME_CursorFromCharOfs(editor, from, &editor->pCursors[1]);
  ME_CursorFromCharOfs(editor, to, &editor->pCursors[0]);
  /* Selection is not allowed in the middle of an end paragraph run. */
  if (editor->pCursors[1].pRun->member.run.nFlags & MERF_ENDPARA)
    editor->pCursors[1].nOffset = 0;
  if (editor->pCursors[0].pRun->member.run.nFlags & MERF_ENDPARA)
    editor->pCursors[0].nOffset = 0;
167
  return to;
168 169 170
}


171
static void
172 173 174
ME_GetCursorCoordinates(ME_TextEditor *editor, ME_Cursor *pCursor,
                        int *x, int *y, int *height)
{
175 176
  ME_DisplayItem *row;
  ME_DisplayItem *run = pCursor->pRun;
177
  ME_DisplayItem *para = pCursor->pPara;
178 179 180
  ME_DisplayItem *pSizeRun = run;
  ME_Context c;
  SIZE sz = {0, 0};
181 182

  assert(height && x && y);
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
  assert(~para->member.para.nFlags & MEPF_REWRAP);
  assert(run && run->type == diRun);
  assert(para && para->type == diParagraph);

  row = ME_FindItemBack(run, diStartRowOrParagraph);
  assert(row && row->type == diStartRow);

  ME_InitContext(&c, editor, ITextHost_TxGetDC(editor->texthost));

  if (!pCursor->nOffset)
  {
    ME_DisplayItem *prev = ME_FindItemBack(run, diRunOrParagraph);
    assert(prev);
    if (prev->type == diRun)
      pSizeRun = prev;
  }
  if (editor->bCaretAtEnd && !pCursor->nOffset &&
      run == ME_FindItemFwd(row, diRun))
  {
    ME_DisplayItem *tmp = ME_FindItemBack(row, diRunOrParagraph);
    assert(tmp);
    if (tmp->type == diRun)
    {
      row = ME_FindItemBack(tmp, diStartRow);
      pSizeRun = run = tmp;
      assert(run);
      assert(run->type == diRun);
      sz = ME_GetRunSize(&c, &para->member.para,
211
                         &run->member.run, run->member.run.strText->nLen,
212
                         row->member.row.nLMargin);
213 214
    }
  }
215 216 217 218 219 220 221 222 223 224 225 226
  if (pCursor->nOffset) {
    sz = ME_GetRunSize(&c, &para->member.para, &run->member.run,
                       pCursor->nOffset, row->member.row.nLMargin);
  }

  *height = pSizeRun->member.run.nAscent + pSizeRun->member.run.nDescent;
  *x = c.rcView.left + run->member.run.pt.x + sz.cx - editor->horz_si.nPos;
  *y = c.rcView.top + para->member.para.pt.y + row->member.row.nBaseline
       + run->member.run.pt.y - pSizeRun->member.run.nAscent
       - editor->vert_si.nPos;
  ME_DestroyContext(&c);
  return;
227 228 229 230 231 232 233 234 235
}


void
ME_MoveCaret(ME_TextEditor *editor)
{
  int x, y, height;

  ME_GetCursorCoordinates(editor, &editor->pCursors[0], &x, &y, &height);
236
  if(editor->bHaveFocus && !ME_IsSelection(editor))
237
  {
238
    x = min(x, editor->rcFormat.right-1);
239 240
    ITextHost_TxCreateCaret(editor->texthost, NULL, 0, height);
    ITextHost_TxSetCaretPos(editor->texthost, x, y);
241
  }
242 243
}

244

245 246 247
void ME_ShowCaret(ME_TextEditor *ed)
{
  ME_MoveCaret(ed);
248
  if(ed->bHaveFocus && !ME_IsSelection(ed))
249
    ITextHost_TxShowCaret(ed->texthost, TRUE);
250 251 252 253
}

void ME_HideCaret(ME_TextEditor *ed)
{
254
  if(!ed->bHaveFocus || ME_IsSelection(ed))
255
  {
256
    ITextHost_TxShowCaret(ed->texthost, FALSE);
257 258
    DestroyCaret();
  }
259 260
}

261 262
BOOL ME_InternalDeleteText(ME_TextEditor *editor, int nOfs, int nChars,
                           BOOL bForce)
263 264 265
{
  ME_Cursor c;
  int shift = 0;
266
  int totalChars = nChars;
267 268
  ME_DisplayItem *start_para;

269 270
  /* Prevent deletion past last end of paragraph run. */
  nChars = min(nChars, ME_GetTextLength(editor) - nOfs);
271 272

  ME_CursorFromCharOfs(editor, nOfs, &c);
273
  start_para = c.pPara;
274 275 276 277 278 279 280 281

  if (!bForce)
  {
    ME_ProtectPartialTableDeletion(editor, nOfs, &nChars);
    if (nChars == 0)
      return FALSE;
  }

282 283 284
  while(nChars > 0)
  {
    ME_Run *run;
285 286 287
    ME_CursorFromCharOfs(editor, nOfs+nChars, &c);
    if (!c.nOffset &&
        nOfs+nChars == (c.pRun->member.run.nCharOfs
288
                        + c.pPara->member.para.nCharOfs))
289 290 291 292
    {
      /* We aren't deleting anything in this run, so we will go back to the
       * last run we are deleting text in. */
      c.pRun = ME_FindItemBack(c.pRun, diRun);
293
      c.pPara = ME_GetParagraph(c.pRun);
294
      c.nOffset = c.pRun->member.run.strText->nLen;
295
    }
296 297
    run = &c.pRun->member.run;
    if (run->nFlags & MERF_ENDPARA) {
298
      int eollen = c.pRun->member.run.strText->nLen;
299
      BOOL keepFirstParaFormat;
300

301 302
      if (!ME_FindItemFwd(c.pRun, diParagraph))
      {
303
        return TRUE;
304
      }
305 306
      keepFirstParaFormat = (totalChars == nChars && nChars <= eollen &&
                             run->nCharOfs);
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
      if (!editor->bEmulateVersion10) /* v4.1 */
      {
        ME_DisplayItem *next_para = ME_FindItemFwd(c.pRun, diParagraphOrEnd);
        ME_DisplayItem *this_para = next_para->member.para.prev_para;

        /* The end of paragraph before a table row is only deleted if there
         * is nothing else on the line before it. */
        if (this_para == start_para &&
            next_para->member.para.nFlags & MEPF_ROWSTART)
        {
          /* If the paragraph will be empty, then it should be deleted, however
           * it still might have text right now which would inherit the
           * MEPF_STARTROW property if we joined it right now.
           * Instead we will delete it after the preceding text is deleted. */
          if (nOfs > this_para->member.para.nCharOfs) {
            /* Skip this end of line. */
            nChars -= (eollen < nChars) ? eollen : nChars;
            continue;
          }
          keepFirstParaFormat = TRUE;
        }
      }
329
      ME_JoinParagraphs(editor, c.pPara, keepFirstParaFormat);
330 331
      /* ME_SkipAndPropagateCharOffset(p->pRun, shift); */
      ME_CheckCharOffsets(editor);
332
      nChars -= (eollen < nChars) ? eollen : nChars;
333 334 335 336 337
      continue;
    }
    else
    {
      ME_Cursor cursor;
338
      int nCharsToDelete = min(nChars, c.nOffset);
339
      int i;
340 341 342

      c.nOffset -= nCharsToDelete;

343
      ME_FindItemBack(c.pRun, diParagraph)->member.para.nFlags |= MEPF_REWRAP;
344

345 346
      cursor = c;
      /* nChars is the number of characters that should be deleted from the
347
         PRECEDING runs (these BEFORE cursor.pRun)
348
         nCharsToDelete is a number of chars to delete from THIS run */
349
      nChars -= nCharsToDelete;
350
      shift -= nCharsToDelete;
351 352
      TRACE("Deleting %d (remaning %d) chars at %d in '%s' (%d)\n",
        nCharsToDelete, nChars, c.nOffset,
353 354
        debugstr_w(run->strText->szData), run->strText->nLen);

355
      if (!c.nOffset && run->strText->nLen == nCharsToDelete)
356 357 358 359 360 361
      {
        /* undo = reinsert whole run */
        /* nOfs is a character offset (from the start of the document
           to the current (deleted) run */
        ME_UndoItem *pUndo = ME_AddUndoItem(editor, diUndoInsertRun, c.pRun);
        if (pUndo)
362
          pUndo->di.member.run.nCharOfs = nOfs+nChars;
363 364 365 366 367 368 369
      }
      else
      {
        /* undo = reinsert partial run */
        ME_UndoItem *pUndo = ME_AddUndoItem(editor, diUndoInsertRun, c.pRun);
        if (pUndo) {
          ME_DestroyString(pUndo->di.member.run.strText);
370
          pUndo->di.member.run.nCharOfs = nOfs+nChars;
371 372 373 374 375 376
          pUndo->di.member.run.strText = ME_MakeStringN(run->strText->szData+c.nOffset, nCharsToDelete);
        }
      }
      TRACE("Post deletion string: %s (%d)\n", debugstr_w(run->strText->szData), run->strText->nLen);
      TRACE("Shift value: %d\n", shift);
      ME_StrDeleteV(run->strText, c.nOffset, nCharsToDelete);
377

378 379
      /* update cursors (including c) */
      for (i=-1; i<editor->nCursors; i++) {
380
        ME_Cursor *pThisCur = editor->pCursors + i;
381 382 383 384 385 386 387 388
        if (i == -1) pThisCur = &c;
        if (pThisCur->pRun == cursor.pRun) {
          if (pThisCur->nOffset > cursor.nOffset) {
            if (pThisCur->nOffset-cursor.nOffset < nCharsToDelete)
              pThisCur->nOffset = cursor.nOffset;
            else
              pThisCur->nOffset -= nCharsToDelete;
            assert(pThisCur->nOffset >= 0);
389
            assert(pThisCur->nOffset <= run->strText->nLen);
390
          }
391
          if (pThisCur->nOffset == run->strText->nLen)
392 393 394 395 396 397 398
          {
            pThisCur->pRun = ME_FindItemFwd(pThisCur->pRun, diRunOrParagraphOrEnd);
            assert(pThisCur->pRun->type == diRun);
            pThisCur->nOffset = 0;
          }
        }
      }
399

400
      /* c = updated data now */
401

402 403 404 405 406
      if (c.pRun == cursor.pRun)
        ME_SkipAndPropagateCharOffset(c.pRun, shift);
      else
        ME_PropagateCharOffset(c.pRun, shift);

407
      if (!cursor.pRun->member.run.strText->nLen)
408 409 410 411 412
      {
        TRACE("Removing useless run\n");
        ME_Remove(cursor.pRun);
        ME_DestroyDisplayItem(cursor.pRun);
      }
413

414 415 416 417 418 419 420
      shift = 0;
      /*
      ME_CheckCharOffsets(editor);
      */
      continue;
    }
  }
421
  return TRUE;
422 423
}

424
BOOL ME_DeleteTextAtCursor(ME_TextEditor *editor, int nCursor, int nChars)
425 426
{  
  assert(nCursor>=0 && nCursor<editor->nCursors);
427 428
  /* text operations set modified state */
  editor->nModifyStep = 1;
429 430
  return ME_InternalDeleteText(editor, ME_GetCursorOfs(editor, nCursor), nChars,
                               FALSE);
431 432
}

433
static ME_DisplayItem *
434 435 436 437 438 439 440 441 442 443
ME_InternalInsertTextFromCursor(ME_TextEditor *editor, int nCursor,
                                const WCHAR *str, int len, ME_Style *style,
                                int flags)
{
  ME_Cursor *p = &editor->pCursors[nCursor];

  editor->bCaretAtEnd = FALSE;
  
  assert(p->pRun->type == diRun);
  
444
  return ME_InsertRunAtCursor(editor, p, style, str, len, flags);
445 446 447
}


448
void ME_InsertOLEFromCursor(ME_TextEditor *editor, const REOBJECT* reo, int nCursor)
449
{
450 451 452
  ME_Style              *pStyle = ME_GetInsertStyle(editor, nCursor);
  ME_DisplayItem        *di;
  WCHAR                 space = ' ';
453 454 455 456 457
  
  /* FIXME no no no */
  if (ME_IsSelection(editor))
    ME_DeleteSelection(editor);

458 459 460 461
  di = ME_InternalInsertTextFromCursor(editor, nCursor, &space, 1, pStyle,
                                       MERF_GRAPHICS);
  di->member.run.ole_obj = ALLOC_OBJ(*reo);
  ME_CopyReObject(di->member.run.ole_obj, reo);
462 463 464 465
  ME_SendSelChange(editor);
}


466 467 468 469 470 471 472 473 474 475 476 477 478 479 480
void ME_InsertEndRowFromCursor(ME_TextEditor *editor, int nCursor)
{
  ME_Style              *pStyle = ME_GetInsertStyle(editor, nCursor);
  ME_DisplayItem        *di;
  WCHAR                 space = ' ';

  /* FIXME no no no */
  if (ME_IsSelection(editor))
    ME_DeleteSelection(editor);

  di = ME_InternalInsertTextFromCursor(editor, nCursor, &space, 1, pStyle,
                                       MERF_ENDROW);
  ME_SendSelChange(editor);
}

481

482 483 484 485 486
void ME_InsertTextFromCursor(ME_TextEditor *editor, int nCursor, 
  const WCHAR *str, int len, ME_Style *style)
{
  const WCHAR *pos;
  ME_Cursor *p = NULL;
487
  int oldLen;
488 489 490 491 492

  /* FIXME really HERE ? */
  if (ME_IsSelection(editor))
    ME_DeleteSelection(editor);

493 494
  /* FIXME: is this too slow? */
  /* Didn't affect performance for WM_SETTEXT (around 50sec/30K) */
495
  oldLen = ME_GetTextLength(editor);
496

497 498 499
  /* text operations set modified state */
  editor->nModifyStep = 1;

500 501 502 503 504
  assert(style);

  assert(nCursor>=0 && nCursor<editor->nCursors);
  if (len == -1)
    len = lstrlenW(str);
505 506 507 508 509

  /* grow the text limit to fit our text */
  if(editor->nTextLimit < oldLen +len)
    editor->nTextLimit = oldLen + len;

510 511
  pos = str;

512 513 514
  while (len)
  {
    /* FIXME this sucks - no respect for unicode (what else can be a line separator in unicode?) */
515
    while(pos - str < len && *pos != '\r' && *pos != '\n' && *pos != '\t')
516
      pos++;
517

518 519 520 521
    if (pos != str) { /* handle text */
      ME_InternalInsertTextFromCursor(editor, nCursor, str, pos-str, style, 0);
    } else if (*pos == '\t') { /* handle tabs */
      WCHAR tab = '\t';
522 523
      ME_InternalInsertTextFromCursor(editor, nCursor, &tab, 1, style, MERF_TAB);
      pos++;
524
    } else { /* handle EOLs */
525 526
      ME_DisplayItem *tp, *end_run;
      ME_Style *tmp_style;
527
      int eol_len = 0;
528

529 530 531 532
      /* Find number of CR and LF in end of paragraph run */
      if (*pos =='\r')
      {
        if (len > 1 && pos[1] == '\n')
533
          eol_len = 2;
534
        else if (len > 2 && pos[1] == '\r' && pos[2] == '\n')
535 536 537
          eol_len = 3;
        else
          eol_len = 1;
538
      } else {
539
        assert(*pos == '\n');
540
        eol_len = 1;
541
      }
542
      pos += eol_len;
543

544
      if (!editor->bEmulateVersion10 && eol_len == 3)
545 546 547 548 549
      {
        /* handle special \r\r\n sequence (richedit 2.x and higher only) */
        WCHAR space = ' ';
        ME_InternalInsertTextFromCursor(editor, nCursor, &space, 1, style, 0);
      } else {
550 551 552 553 554 555 556 557
        ME_String *eol_str;

        if (!editor->bEmulateVersion10) {
          WCHAR cr = '\r';
          eol_str = ME_MakeStringN(&cr, 1);
        } else {
          eol_str = ME_MakeStringN(str, eol_len);
        }
558 559 560 561 562 563 564 565

        p = &editor->pCursors[nCursor];
        if (p->nOffset) {
          ME_SplitRunSimple(editor, p->pRun, p->nOffset);
          p = &editor->pCursors[nCursor];
        }
        tmp_style = ME_GetInsertStyle(editor, nCursor);
        /* ME_SplitParagraph increases style refcount */
566
        tp = ME_SplitParagraph(editor, p->pRun, p->pRun->member.run.style, eol_str, 0);
567
        p->pRun = ME_FindItemFwd(tp, diRun);
568
        p->pPara = ME_GetParagraph(p->pRun);
569 570 571 572
        end_run = ME_FindItemBack(tp, diRun);
        ME_ReleaseStyle(end_run->member.run.style);
        end_run->member.run.style = tmp_style;
        p->nOffset = 0;
573
      }
574
    }
575 576
    len -= pos - str;
    str = pos;
577 578 579
  }
}

580 581 582

static BOOL
ME_MoveCursorChars(ME_TextEditor *editor, ME_Cursor *pCursor, int nRelOfs)
583
{
584
  ME_DisplayItem *pRun = pCursor->pRun;
585

586 587 588 589
  if (nRelOfs == -1)
  {
    if (!pCursor->nOffset)
    {
590
      ME_DisplayItem *pPara = pCursor->pPara;
591 592 593 594 595 596 597 598
      do {
        pRun = ME_FindItemBack(pRun, diRunOrParagraph);
        assert(pRun);
        switch (pRun->type)
        {
          case diRun:
            break;
          case diParagraph:
599 600
            pPara = pRun;
            if (pPara->member.para.prev_para->type == diTextStart)
601
              return FALSE;
602 603
            pRun = ME_FindItemBack(pPara, diRunOrParagraph);
            pPara = pPara->member.para.prev_para;
604 605 606 607 608 609
            /* every paragraph ought to have at least one run */
            assert(pRun && pRun->type == diRun);
            assert(pRun->member.run.nFlags & MERF_ENDPARA);
            break;
          default:
            assert(pRun->type != diRun && pRun->type != diParagraph);
610
            return FALSE;
611
        }
612 613
      } while (RUN_IS_HIDDEN(&pRun->member.run) ||
               pRun->member.run.nFlags & MERF_HIDDEN);
614
      pCursor->pPara = pPara;
615 616 617 618 619
      pCursor->pRun = pRun;
      if (pRun->member.run.nFlags & MERF_ENDPARA)
        pCursor->nOffset = 0;
      else
        pCursor->nOffset = pRun->member.run.strText->nLen;
620
    }
621

622
    if (pCursor->nOffset)
623
      pCursor->nOffset = pCursor->nOffset + nRelOfs;
624 625 626 627
    return TRUE;
  }
  else
  {
628
    if (!(pRun->member.run.nFlags & MERF_ENDPARA))
629
    {
630 631
      int new_ofs = pCursor->nOffset + nRelOfs;

632
      if (new_ofs < pRun->member.run.strText->nLen)
633 634
      {
        pCursor->nOffset = new_ofs;
635 636 637
        return TRUE;
      }
    }
638 639
    do {
      pRun = ME_FindItemFwd(pRun, diRun);
640 641
    } while (pRun && (RUN_IS_HIDDEN(&pRun->member.run) ||
                      pRun->member.run.nFlags & MERF_HIDDEN));
642
    if (pRun)
643
    {
644
      pCursor->pPara = ME_GetParagraph(pRun);
645 646
      pCursor->pRun = pRun;
      pCursor->nOffset = 0;
647 648 649
      return TRUE;
    }
  }
650
  return FALSE;
651 652
}

653

654 655 656 657 658 659 660 661 662 663 664 665 666
static BOOL
ME_MoveCursorWords(ME_TextEditor *editor, ME_Cursor *cursor, int nRelOfs)
{
  ME_DisplayItem *pRun = cursor->pRun, *pOtherRun;
  int nOffset = cursor->nOffset;
  
  if (nRelOfs == -1)
  {
    /* Backward movement */
    while (TRUE)
    {
      nOffset = ME_CallWordBreakProc(editor, pRun->member.run.strText,
                                     nOffset, WB_MOVEWORDLEFT);
667
      if (nOffset)
668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686
        break;
      pOtherRun = ME_FindItemBack(pRun, diRunOrParagraph);
      if (pOtherRun->type == diRun)
      {
        if (ME_CallWordBreakProc(editor, pOtherRun->member.run.strText,
                                 pOtherRun->member.run.strText->nLen - 1,
                                 WB_ISDELIMITER)
            && !(pRun->member.run.nFlags & MERF_ENDPARA)
            && !(cursor->pRun == pRun && cursor->nOffset == 0)
            && !ME_CallWordBreakProc(editor, pRun->member.run.strText, 0,
                                     WB_ISDELIMITER))
          break;
        pRun = pOtherRun;
        nOffset = pOtherRun->member.run.strText->nLen;
      }
      else if (pOtherRun->type == diParagraph)
      {
        if (cursor->pRun == pRun && cursor->nOffset == 0)
        {
687 688 689
          /* Skip empty start of table row paragraph */
          if (pOtherRun->member.para.prev_para->member.para.nFlags & MEPF_ROWSTART)
            pOtherRun = pOtherRun->member.para.prev_para;
690 691 692
          /* Paragraph breaks are treated as separate words */
          if (pOtherRun->member.para.prev_para->type == diTextStart)
            return FALSE;
693

694
          pRun = ME_FindItemBack(pOtherRun, diRun);
695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723
        }
        break;
      }
    }
  }
  else
  {
    /* Forward movement */
    BOOL last_delim = FALSE;
    
    while (TRUE)
    {
      if (last_delim && !ME_CallWordBreakProc(editor, pRun->member.run.strText,
                                              nOffset, WB_ISDELIMITER))
        break;
      nOffset = ME_CallWordBreakProc(editor, pRun->member.run.strText,
                                     nOffset, WB_MOVEWORDRIGHT);
      if (nOffset < pRun->member.run.strText->nLen)
        break;
      pOtherRun = ME_FindItemFwd(pRun, diRunOrParagraphOrEnd);
      if (pOtherRun->type == diRun)
      {
        last_delim = ME_CallWordBreakProc(editor, pRun->member.run.strText,
                                          nOffset - 1, WB_ISDELIMITER);
        pRun = pOtherRun;
        nOffset = 0;
      }
      else if (pOtherRun->type == diParagraph)
      {
724 725
        if (pOtherRun->member.para.nFlags & MEPF_ROWSTART)
            pOtherRun = pOtherRun->member.para.next_para;
726 727 728 729 730 731 732 733 734 735 736 737 738 739
        if (cursor->pRun == pRun)
          pRun = ME_FindItemFwd(pOtherRun, diRun);
        nOffset = 0;
        break;
      }
      else /* diTextEnd */
      {
        if (cursor->pRun == pRun)
          return FALSE;
        nOffset = 0;
        break;
      }
    }
  }
740
  cursor->pPara = ME_GetParagraph(pRun);
741 742 743 744
  cursor->pRun = pRun;
  cursor->nOffset = nOffset;
  return TRUE;
}
745

746

747
static void
748
ME_SelectByType(ME_TextEditor *editor, ME_SelectionType selectionType)
749
{
750 751
  /* pCursor[0] is the end of the selection
   * pCursor[1] is the start of the selection (or the position selection anchor)
752 753 754 755 756 757 758 759 760 761
   * pCursor[2] and [3] are the selection anchors that are backed up
   * so they are kept when the selection changes for drag selection.
   */

  editor->nSelectionType = selectionType;
  switch(selectionType)
  {
    case stPosition:
      break;
    case stWord:
762 763 764
      ME_MoveCursorWords(editor, &editor->pCursors[0], +1);
      editor->pCursors[1] = editor->pCursors[0];
      ME_MoveCursorWords(editor, &editor->pCursors[1], -1);
765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780
      break;
    case stLine:
    case stParagraph:
    {
      ME_DisplayItem *pItem;
      ME_DIType fwdSearchType, backSearchType;
      if (selectionType == stParagraph) {
          backSearchType = diParagraph;
          fwdSearchType = diParagraphOrEnd;
      } else {
          backSearchType = diStartRow;
          fwdSearchType = diStartRowOrParagraphOrEnd;
      }
      pItem = ME_FindItemFwd(editor->pCursors[0].pRun, fwdSearchType);
      assert(pItem);
      if (pItem->type == diTextEnd)
781
          editor->pCursors[0].pRun = ME_FindItemBack(pItem, diRun);
782
      else
783
          editor->pCursors[0].pRun = ME_FindItemFwd(pItem, diRun);
784
      editor->pCursors[0].pPara = ME_GetParagraph(editor->pCursors[0].pRun);
785 786 787 788
      editor->pCursors[0].nOffset = 0;

      pItem = ME_FindItemBack(pItem, backSearchType);
      editor->pCursors[1].pRun = ME_FindItemFwd(pItem, diRun);
789
      editor->pCursors[1].pPara = ME_GetParagraph(editor->pCursors[1].pRun);
790 791 792
      editor->pCursors[1].nOffset = 0;
      break;
    }
793 794 795
    case stDocument:
      /* Select everything with cursor anchored from the start of the text */
      editor->nSelectionType = stDocument;
796 797
      editor->pCursors[1].pPara = editor->pBuffer->pFirst->member.para.next_para;
      editor->pCursors[1].pRun = ME_FindItemFwd(editor->pCursors[1].pPara, diRun);
798
      editor->pCursors[1].nOffset = 0;
799
      editor->pCursors[0].pPara = editor->pBuffer->pLast->member.para.prev_para;
800 801 802
      editor->pCursors[0].pRun = ME_FindItemBack(editor->pBuffer->pLast, diRun);
      editor->pCursors[0].nOffset = 0;
      break;
803 804 805 806 807
    default: assert(0);
  }
  /* Store the anchor positions for extending the selection. */
  editor->pCursors[2] = editor->pCursors[0];
  editor->pCursors[3] = editor->pCursors[1];
808 809
}

810 811 812
int ME_GetCursorOfs(ME_TextEditor *editor, int nCursor)
{
  ME_Cursor *pCursor = &editor->pCursors[nCursor];
813 814
  return pCursor->pPara->member.para.nCharOfs
         + pCursor->pRun->member.run.nCharOfs + pCursor->nOffset;
815 816
}

817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855
/* Helper function for ME_FindPixelPos to find paragraph within tables */
static ME_DisplayItem* ME_FindPixelPosInTableRow(int x, int y,
                                                 ME_DisplayItem *para)
{
  ME_DisplayItem *cell, *next_cell;
  assert(para->member.para.nFlags & MEPF_ROWSTART);
  cell = para->member.para.next_para->member.para.pCell;
  assert(cell);

  /* find the cell we are in */
  while ((next_cell = cell->member.cell.next_cell) != NULL) {
    if (x < next_cell->member.cell.pt.x)
    {
      para = ME_FindItemFwd(cell, diParagraph);
      /* Found the cell, but there might be multiple paragraphs in
       * the cell, so need to search down the cell for the paragraph. */
      while (cell == para->member.para.pCell) {
        if (y < para->member.para.pt.y + para->member.para.nHeight)
        {
          if (para->member.para.nFlags & MEPF_ROWSTART)
            return ME_FindPixelPosInTableRow(x, y, para);
          else
            return para;
        }
        para = para->member.para.next_para;
      }
      /* Past the end of the cell, so go back to the last cell paragraph */
      return para->member.para.prev_para;
    }
    cell = next_cell;
  }
  /* Return table row delimiter */
  para = ME_FindItemFwd(cell, diParagraph);
  assert(para->member.para.nFlags & MEPF_ROWEND);
  assert(para->member.para.pFmt->dwMask & PFM_TABLEROWDELIMITER);
  assert(para->member.para.pFmt->wEffects & PFE_TABLEROWDELIMITER);
  return para;
}

856 857 858 859 860 861 862 863 864 865 866 867 868 869
static BOOL ME_ReturnFoundPos(ME_TextEditor *editor, ME_DisplayItem *found,
                               ME_Cursor *result, int rx, BOOL isExact)
{
  assert(found);
  assert(found->type == diRun);
  if ((found->member.run.nFlags & MERF_ENDPARA) || rx < 0)
    rx = 0;
  result->pRun = found;
  result->nOffset = ME_CharFromPointCursor(editor, rx, &found->member.run);
  if (editor->pCursors[0].nOffset == found->member.run.strText->nLen && rx)
  {
    result->pRun = ME_FindItemFwd(editor->pCursors[0].pRun, diRun);
    result->nOffset = 0;
  }
870
  result->pPara = ME_GetParagraph(result->pRun);
871 872 873
  return isExact;
}

874 875 876 877 878 879 880 881 882 883
/* Finds the run and offset from the pixel position.
 *
 * x & y are pixel positions in virtual coordinates into the rich edit control,
 * so client coordinates must first be adjusted by the scroll position.
 *
 * returns TRUE if the result was exactly under the cursor, otherwise returns
 * FALSE, and result is set to the closest position to the coordinates.
 */
static BOOL ME_FindPixelPos(ME_TextEditor *editor, int x, int y,
                            ME_Cursor *result, BOOL *is_eol)
884 885
{
  ME_DisplayItem *p = editor->pBuffer->pFirst->member.para.next_para;
886
  ME_DisplayItem *last = NULL;
887
  int rx = 0;
888
  BOOL isExact = TRUE;
889

890 891 892
  x -= editor->rcFormat.left;
  y -= editor->rcFormat.top;

893 894 895
  if (is_eol)
    *is_eol = 0;

896 897
  /* find paragraph */
  for (; p != editor->pBuffer->pLast; p = p->member.para.next_para)
898
  {
899
    assert(p->type == diParagraph);
900
    if (y < p->member.para.pt.y + p->member.para.nHeight)
901
    {
902 903 904
      if (p->member.para.nFlags & MEPF_ROWSTART)
        p = ME_FindPixelPosInTableRow(x, y, p);
      y -= p->member.para.pt.y;
905
      p = ME_FindItemFwd(p, diStartRow);
906
      break;
907 908
    } else if (p->member.para.nFlags & MEPF_ROWSTART) {
      p = ME_GetTableRowEnd(p);
909
    }
910 911 912 913 914 915
  }
  /* find row */
  for (; p != editor->pBuffer->pLast; )
  {
    ME_DisplayItem *pp;
    assert(p->type == diStartRow);
916
    if (y < p->member.row.pt.y + p->member.row.nHeight)
917
    {
918 919 920 921 922 923 924 925
        p = ME_FindItemFwd(p, diRun);
        break;
    }
    pp = ME_FindItemFwd(p, diStartRowOrParagraphOrEnd);
    if (pp->type != diStartRow)
    {
        p = ME_FindItemFwd(p, diRun);
        break;
926
    }
927 928
    p = pp;
  }
929 930 931 932 933
  if (p == editor->pBuffer->pLast)
  {
    /* The position is below the last paragraph, so the last row will be used
     * rather than the end of the text, so the x position will be used to
     * determine the offset closest to the pixel position. */
934
    isExact = FALSE;
935 936 937 938 939 940 941 942 943
    p = ME_FindItemBack(p, diStartRow);
    if (p != NULL){
      p = ME_FindItemFwd(p, diRun);
    }
    else
    {
      p = editor->pBuffer->pLast;
    }
  }
944 945 946
  for (; p != editor->pBuffer->pLast; p = p->next)
  {
    switch (p->type)
947
    {
948
    case diRun:
949
      rx = x - p->member.run.pt.x;
950
      if (rx < p->member.run.nWidth)
951
        return ME_ReturnFoundPos(editor, p, result, rx, isExact);
952 953
      break;
    case diStartRow:
954
      isExact = FALSE;
955 956 957
      p = ME_FindItemFwd(p, diRun);
      if (is_eol) *is_eol = 1;
      rx = 0; /* FIXME not sure */
958
      return ME_ReturnFoundPos(editor, p, result, rx, isExact);
959
    case diCell:
960 961
    case diParagraph:
    case diTextEnd:
962
      isExact = FALSE;
963 964
      rx = 0; /* FIXME not sure */
      p = last;
965
      return ME_ReturnFoundPos(editor, p, result, rx, isExact);
966
    default: assert(0);
967
    }
968
    last = p;
969 970
  }
  result->pRun = ME_FindItemBack(p, diRun);
971
  result->pPara = ME_GetParagraph(result->pRun);
972 973
  result->nOffset = 0;
  assert(result->pRun->member.run.nFlags & MERF_ENDPARA);
974
  return FALSE;
975 976
}

977

978 979 980 981 982 983 984 985
/* Returns the character offset closest to the pixel position
 *
 * x & y are pixel positions in client coordinates.
 *
 * isExact will be set to TRUE if the run is directly under the pixel
 * position, FALSE if it not, unless isExact is set to NULL.
 */
int ME_CharFromPos(ME_TextEditor *editor, int x, int y, BOOL *isExact)
986 987 988
{
  ME_Cursor cursor;
  RECT rc;
989
  BOOL bResult;
990

991
  ITextHost_TxGetClientRect(editor->texthost, &rc);
992 993
  if (x < 0 || y < 0 || x >= rc.right || y >= rc.bottom) {
    if (isExact) *isExact = FALSE;
994
    return -1;
995
  }
996 997
  x += editor->horz_si.nPos;
  y += editor->vert_si.nPos;
998 999
  bResult = ME_FindPixelPos(editor, x, y, &cursor, NULL);
  if (isExact) *isExact = bResult;
1000 1001
  return cursor.pPara->member.para.nCharOfs
         + cursor.pRun->member.run.nCharOfs + cursor.nOffset;
1002 1003
}

1004 1005


1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019
/* Extends the selection with a word, line, or paragraph selection type.
 *
 * The selection is anchored by editor->pCursors[2-3] such that the text
 * between the anchors will remain selected, and one end will be extended.
 *
 * editor->pCursors[0] should have the position to extend the selection to
 * before this function is called.
 *
 * Nothing will be done if editor->nSelectionType equals stPosition.
 */
static void ME_ExtendAnchorSelection(ME_TextEditor *editor)
{
  ME_Cursor tmp_cursor;
  int curOfs, anchorStartOfs, anchorEndOfs;
1020
  if (editor->nSelectionType == stPosition || editor->nSelectionType == stDocument)
1021 1022
      return;
  curOfs = ME_GetCursorOfs(editor, 0);
1023 1024
  anchorStartOfs = ME_GetCursorOfs(editor, 3);
  anchorEndOfs = ME_GetCursorOfs(editor, 2);
1025 1026 1027 1028 1029 1030 1031

  tmp_cursor = editor->pCursors[0];
  editor->pCursors[0] = editor->pCursors[2];
  editor->pCursors[1] = editor->pCursors[3];
  if (curOfs < anchorStartOfs)
  {
      /* Extend the left side of selection */
1032
      editor->pCursors[1] = tmp_cursor;
1033
      if (editor->nSelectionType == stWord)
1034
          ME_MoveCursorWords(editor, &editor->pCursors[1], -1);
1035 1036 1037
      else
      {
          ME_DisplayItem *pItem;
1038 1039
          ME_DIType searchType = ((editor->nSelectionType == stLine) ?
                                  diStartRowOrParagraph:diParagraph);
1040 1041
          pItem = ME_FindItemBack(editor->pCursors[1].pRun, searchType);
          editor->pCursors[1].pRun = ME_FindItemFwd(pItem, diRun);
1042
          editor->pCursors[1].pPara = ME_GetParagraph(editor->pCursors[1].pRun);
1043
          editor->pCursors[1].nOffset = 0;
1044 1045 1046 1047 1048
      }
  }
  else if (curOfs >= anchorEndOfs)
  {
      /* Extend the right side of selection */
1049
      editor->pCursors[0] = tmp_cursor;
1050
      if (editor->nSelectionType == stWord)
1051
          ME_MoveCursorWords(editor, &editor->pCursors[0], +1);
1052 1053 1054
      else
      {
          ME_DisplayItem *pItem;
1055 1056
          ME_DIType searchType = ((editor->nSelectionType == stLine) ?
                                  diStartRowOrParagraphOrEnd:diParagraphOrEnd);
1057
          pItem = ME_FindItemFwd(editor->pCursors[0].pRun, searchType);
1058
          if (pItem->type == diTextEnd)
1059
              editor->pCursors[0].pRun = ME_FindItemBack(pItem, diRun);
1060
          else
1061
              editor->pCursors[0].pRun = ME_FindItemFwd(pItem, diRun);
1062
          editor->pCursors[0].pPara = ME_GetParagraph(editor->pCursors[0].pRun);
1063
          editor->pCursors[0].nOffset = 0;
1064 1065 1066
      }
  }
}
1067

1068
void ME_LButtonDown(ME_TextEditor *editor, int x, int y, int clickNum)
1069 1070 1071
{
  ME_Cursor tmp_cursor;
  int is_selection = 0;
1072
  BOOL is_shift;
1073

1074
  editor->nUDArrowX = -1;
1075 1076 1077

  x += editor->horz_si.nPos;
  y += editor->vert_si.nPos;
1078 1079 1080

  tmp_cursor = editor->pCursors[0];
  is_selection = ME_IsSelection(editor);
1081
  is_shift = GetKeyState(VK_SHIFT) < 0;
1082

1083 1084
  ME_FindPixelPos(editor, x, y, &editor->pCursors[0], &editor->bCaretAtEnd);

1085
  if (x >= editor->rcFormat.left || is_shift)
1086
  {
1087 1088 1089
    if (clickNum > 1)
    {
      editor->pCursors[1] = editor->pCursors[0];
1090
      if (is_shift) {
1091
          if (x >= editor->rcFormat.left)
1092 1093 1094 1095
              ME_SelectByType(editor, stWord);
          else
              ME_SelectByType(editor, stParagraph);
      } else if (clickNum % 2 == 0) {
1096
          ME_SelectByType(editor, stWord);
1097
      } else {
1098
          ME_SelectByType(editor, stParagraph);
1099
      }
1100
    }
1101
    else if (!is_shift)
1102
    {
1103
      editor->nSelectionType = stPosition;
1104 1105
      editor->pCursors[1] = editor->pCursors[0];
    }
1106 1107 1108
    else if (!is_selection)
    {
      editor->nSelectionType = stPosition;
1109
      editor->pCursors[1] = tmp_cursor;
1110 1111 1112 1113
    }
    else if (editor->nSelectionType != stPosition)
    {
      ME_ExtendAnchorSelection(editor);
1114
    }
1115 1116 1117
  }
  else
  {
1118 1119
    if (clickNum < 2) {
        ME_SelectByType(editor, stLine);
1120
    } else if (clickNum % 2 == 0 || is_shift) {
1121
        ME_SelectByType(editor, stParagraph);
1122 1123
    } else {
        ME_SelectByType(editor, stDocument);
1124
    }
1125
  }
1126
  ME_InvalidateSelection(editor);
1127
  ITextHost_TxShowCaret(editor->texthost, FALSE);
1128
  ME_ShowCaret(editor);
1129 1130
  ME_ClearTempStyle(editor);
  ME_SendSelChange(editor);
1131 1132 1133 1134 1135
}

void ME_MouseMove(ME_TextEditor *editor, int x, int y)
{
  ME_Cursor tmp_cursor;
1136

1137 1138
  if (editor->nSelectionType == stDocument)
      return;
1139 1140
  x += editor->horz_si.nPos;
  y += editor->vert_si.nPos;
1141 1142

  tmp_cursor = editor->pCursors[0];
1143
  /* FIXME: do something with the return value of ME_FindPixelPos */
1144
  ME_FindPixelPos(editor, x, y, &tmp_cursor, &editor->bCaretAtEnd);
1145

1146
  ME_InvalidateSelection(editor);
1147 1148 1149 1150 1151
  editor->pCursors[0] = tmp_cursor;
  ME_ExtendAnchorSelection(editor);

  if (editor->nSelectionType != stPosition &&
      memcmp(&editor->pCursors[1], &editor->pCursors[3], sizeof(ME_Cursor)))
1152
  {
1153
      /* The scroll the cursor towards the other end, since it was the one
1154
       * extended by ME_ExtendAnchorSelection */
1155
      ME_EnsureVisible(editor, &editor->pCursors[1]);
1156
  } else {
1157
      ME_EnsureVisible(editor, &editor->pCursors[0]);
1158 1159
  }

1160
  ME_InvalidateSelection(editor);
1161
  ITextHost_TxShowCaret(editor->texthost, FALSE);
1162
  ME_ShowCaret(editor);
1163 1164 1165 1166 1167 1168 1169 1170 1171 1172
  ME_SendSelChange(editor);
}

static ME_DisplayItem *ME_FindRunInRow(ME_TextEditor *editor, ME_DisplayItem *pRow, 
                                int x, int *pOffset, int *pbCaretAtEnd)
{
  ME_DisplayItem *pNext, *pLastRun;
  pNext = ME_FindItemFwd(pRow, diRunOrStartRow);
  assert(pNext->type == diRun);
  pLastRun = pNext;
1173 1174
  if (pbCaretAtEnd) *pbCaretAtEnd = FALSE;
  if (pOffset) *pOffset = 0;
1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198
  do {
    int run_x = pNext->member.run.pt.x;
    int width = pNext->member.run.nWidth;
    if (x < run_x)
    {
      return pNext;
    }
    if (x >= run_x && x < run_x+width)
    {
      int ch = ME_CharFromPointCursor(editor, x-run_x, &pNext->member.run);
      ME_String *s = pNext->member.run.strText;
      if (ch < s->nLen) {
        if (pOffset)
          *pOffset = ch;
        return pNext;          
      }
    }
    pLastRun = pNext;
    pNext = ME_FindItemFwd(pNext, diRunOrStartRow);
  } while(pNext && pNext->type == diRun);
  
  if ((pLastRun->member.run.nFlags & MERF_ENDPARA) == 0)
  {
    pNext = ME_FindItemFwd(pNext, diRun);
1199
    if (pbCaretAtEnd) *pbCaretAtEnd = TRUE;
1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228
    return pNext;
  } else {
    return pLastRun;
  }
}

static int ME_GetXForArrow(ME_TextEditor *editor, ME_Cursor *pCursor)
{
  ME_DisplayItem *pRun = pCursor->pRun;
  int x;

  if (editor->nUDArrowX != -1)
    x = editor->nUDArrowX;
  else {
    if (editor->bCaretAtEnd)
    {
      pRun = ME_FindItemBack(pRun, diRun);
      assert(pRun);
      x = pRun->member.run.pt.x + pRun->member.run.nWidth;
    }
    else {
      x = pRun->member.run.pt.x;
      x += ME_PointFromChar(editor, &pRun->member.run, pCursor->nOffset);
    }
    editor->nUDArrowX = x;
  }
  return x;
}

1229 1230 1231

static void
ME_MoveCursorLines(ME_TextEditor *editor, ME_Cursor *pCursor, int nRelOfs)
1232 1233
{
  ME_DisplayItem *pRun = pCursor->pRun;
1234
  ME_DisplayItem *pItem, *pOldPara, *pNewPara;
1235
  int x = ME_GetXForArrow(editor, pCursor);
1236

1237 1238
  if (editor->bCaretAtEnd && !pCursor->nOffset)
    pRun = ME_FindItemBack(pRun, diRun);
1239
  if (!pRun)
1240
    return;
1241
  pOldPara = ME_GetParagraph(pRun);
1242
  if (nRelOfs == -1)
1243
  {
1244 1245 1246 1247 1248
    /* start of this row */
    pItem = ME_FindItemBack(pRun, diStartRow);
    assert(pItem);
    /* start of the previous row */
    pItem = ME_FindItemBack(pItem, diStartRow);
1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270
    if (!pItem)
      return; /* row not found - ignore */
    pNewPara = ME_GetParagraph(pItem);
    if (pOldPara->member.para.nFlags & MEPF_ROWEND ||
        (pOldPara->member.para.pCell &&
         pOldPara->member.para.pCell != pNewPara->member.para.pCell))
    {
      /* Brought out of a cell */
      pNewPara = ME_GetTableRowStart(pOldPara)->member.para.prev_para;
      if (pNewPara->type == diTextStart)
        return; /* At the top, so don't go anywhere. */
      pItem = ME_FindItemFwd(pNewPara, diStartRow);
    }
    if (pNewPara->member.para.nFlags & MEPF_ROWEND)
    {
      /* Brought into a table row */
      ME_Cell *cell = &ME_FindItemBack(pNewPara, diCell)->member.cell;
      while (x < cell->pt.x && cell->prev_cell)
        cell = &cell->prev_cell->member.cell;
      if (cell->next_cell) /* else - we are still at the end of the row */
        pItem = ME_FindItemBack(cell->next_cell, diStartRow);
    }
1271 1272 1273 1274 1275
  }
  else
  {
    /* start of the next row */
    pItem = ME_FindItemFwd(pRun, diStartRow);
1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297
    if (!pItem)
      return; /* row not found - ignore */
    pNewPara = ME_GetParagraph(pItem);
    if (pOldPara->member.para.nFlags & MEPF_ROWSTART ||
        (pOldPara->member.para.pCell &&
         pOldPara->member.para.pCell != pNewPara->member.para.pCell))
    {
      /* Brought out of a cell */
      pNewPara = ME_GetTableRowEnd(pOldPara)->member.para.next_para;
      if (pNewPara->type == diTextEnd)
        return; /* At the bottom, so don't go anywhere. */
      pItem = ME_FindItemFwd(pNewPara, diStartRow);
    }
    if (pNewPara->member.para.nFlags & MEPF_ROWSTART)
    {
      /* Brought into a table row */
      ME_DisplayItem *cell = ME_FindItemFwd(pNewPara, diCell);
      while (cell->member.cell.next_cell &&
             x >= cell->member.cell.next_cell->member.cell.pt.x)
        cell = cell->member.cell.next_cell;
      pItem = ME_FindItemFwd(cell, diStartRow);
    }
1298 1299 1300
  }
  if (!pItem)
  {
1301
    /* row not found - ignore */
1302 1303 1304
    return;
  }
  pCursor->pRun = ME_FindRunInRow(editor, pItem, x, &pCursor->nOffset, &editor->bCaretAtEnd);
1305
  pCursor->pPara = ME_GetParagraph(pCursor->pRun);
1306 1307 1308 1309
  assert(pCursor->pRun);
  assert(pCursor->pRun->type == diRun);
}

1310
static void ME_ArrowPageUp(ME_TextEditor *editor, ME_Cursor *pCursor)
1311
{
1312
  ME_DisplayItem *p = ME_FindItemFwd(editor->pBuffer->pFirst, diStartRow);
1313

1314
  if (editor->vert_si.nPos < p->member.row.nHeight)
1315
  {
1316 1317
    pCursor->pPara = editor->pBuffer->pFirst->member.para.next_para;
    pCursor->pRun = ME_FindItemFwd(pCursor->pPara, diRun);
1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328
    pCursor->nOffset = 0;
    editor->bCaretAtEnd = FALSE;
    /* Native clears seems to clear this x value on page up at the top
     * of the text, but not on page down at the end of the text.
     * Doesn't make sense, but we try to be bug for bug compatible. */
    editor->nUDArrowX = -1;
  } else {
    ME_DisplayItem *pRun = pCursor->pRun;
    ME_DisplayItem *pLast;
    int x, y, ys, yd, yp, yprev;
    int yOldScrollPos = editor->vert_si.nPos;
1329

1330 1331 1332
    x = ME_GetXForArrow(editor, pCursor);
    if (!pCursor->nOffset && editor->bCaretAtEnd)
      pRun = ME_FindItemBack(pRun, diRun);
1333

1334 1335 1336 1337
    p = ME_FindItemBack(pRun, diStartRowOrParagraph);
    assert(p->type == diStartRow);
    yp = ME_FindItemBack(p, diParagraph)->member.para.pt.y;
    yprev = ys = y = yp + p->member.row.pt.y;
1338

1339 1340 1341 1342
    ME_ScrollUp(editor, editor->sizeWindow.cy);
    /* Only move the cursor by the amount scrolled. */
    yd = y + editor->vert_si.nPos - yOldScrollPos;
    pLast = p;
1343

1344 1345 1346
    do {
      p = ME_FindItemBack(p, diStartRowOrParagraph);
      if (!p)
1347
        break;
1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359
      if (p->type == diParagraph) { /* crossing paragraphs */
        if (p->member.para.prev_para == NULL)
          break;
        yp = p->member.para.prev_para->member.para.pt.y;
        continue;
      }
      y = yp + p->member.row.pt.y;
      if (y < yd)
        break;
      pLast = p;
      yprev = y;
    } while(1);
1360

1361 1362
    pCursor->pRun = ME_FindRunInRow(editor, pLast, x, &pCursor->nOffset,
                                    &editor->bCaretAtEnd);
1363
    pCursor->pPara = ME_GetParagraph(pCursor->pRun);
1364 1365 1366 1367 1368
  }
  assert(pCursor->pRun);
  assert(pCursor->pRun->type == diRun);
}

1369
static void ME_ArrowPageDown(ME_TextEditor *editor, ME_Cursor *pCursor)
1370
{
1371 1372 1373 1374 1375 1376 1377
  ME_DisplayItem *pLast;
  int x, y;

  /* Find y position of the last row */
  pLast = editor->pBuffer->pLast;
  y = pLast->member.para.prev_para->member.para.pt.y
      + ME_FindItemBack(pLast, diStartRow)->member.row.pt.y;
1378

1379
  x = ME_GetXForArrow(editor, pCursor);
1380

1381 1382
  if (editor->vert_si.nPos >= y - editor->sizeWindow.cy)
  {
1383
    pCursor->pPara = editor->pBuffer->pLast->member.para.prev_para;
1384 1385 1386 1387 1388 1389 1390 1391
    pCursor->pRun = ME_FindItemBack(editor->pBuffer->pLast, diRun);
    pCursor->nOffset = 0;
    editor->bCaretAtEnd = FALSE;
  } else {
    ME_DisplayItem *pRun = pCursor->pRun;
    ME_DisplayItem *p;
    int ys, yd, yp, yprev;
    int yOldScrollPos = editor->vert_si.nPos;
1392

1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406
    if (!pCursor->nOffset && editor->bCaretAtEnd)
      pRun = ME_FindItemBack(pRun, diRun);

    p = ME_FindItemBack(pRun, diStartRowOrParagraph);
    assert(p->type == diStartRow);
    yp = ME_FindItemBack(p, diParagraph)->member.para.pt.y;
    yprev = ys = y = yp + p->member.row.pt.y;

    /* For native richedit controls:
     * v1.0 - v3.1 can only scroll down as far as the scrollbar lets us
     * v4.1 can scroll past this position here. */
    ME_ScrollDown(editor, editor->sizeWindow.cy);
    /* Only move the cursor by the amount scrolled. */
    yd = y + editor->vert_si.nPos - yOldScrollPos;
1407
    pLast = p;
1408

1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425
    do {
      p = ME_FindItemFwd(p, diStartRowOrParagraph);
      if (!p)
        break;
      if (p->type == diParagraph) {
        yp = p->member.para.pt.y;
        continue;
      }
      y = yp + p->member.row.pt.y;
      if (y >= yd)
        break;
      pLast = p;
      yprev = y;
    } while(1);

    pCursor->pRun = ME_FindRunInRow(editor, pLast, x, &pCursor->nOffset,
                                    &editor->bCaretAtEnd);
1426
    pCursor->pPara = ME_GetParagraph(pCursor->pRun);
1427 1428 1429 1430 1431
  }
  assert(pCursor->pRun);
  assert(pCursor->pRun->type == diRun);
}

1432
static void ME_ArrowHome(ME_TextEditor *editor, ME_Cursor *pCursor)
1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444
{
  ME_DisplayItem *pRow = ME_FindItemBack(pCursor->pRun, diStartRow);
  if (pRow) {
    ME_DisplayItem *pRun;
    if (editor->bCaretAtEnd && !pCursor->nOffset) {
      pRow = ME_FindItemBack(pRow, diStartRow);
      if (!pRow)
        return;
    }
    pRun = ME_FindItemFwd(pRow, diRun);
    if (pRun) {
      pCursor->pRun = pRun;
1445
      assert(pCursor->pPara == ME_GetParagraph(pRun));
1446 1447 1448 1449 1450 1451
      pCursor->nOffset = 0;
    }
  }
  editor->bCaretAtEnd = FALSE;
}

1452
static void ME_ArrowCtrlHome(ME_TextEditor *editor, ME_Cursor *pCursor)
1453
{
1454 1455
  pCursor->pPara = editor->pBuffer->pFirst->member.para.next_para;
  pCursor->pRun = ME_FindItemFwd(pCursor->pPara, diRun);
1456 1457
  pCursor->nOffset = 0;
  editor->bCaretAtEnd = FALSE;
1458 1459
}

1460
static void ME_ArrowEnd(ME_TextEditor *editor, ME_Cursor *pCursor)
1461 1462
{
  ME_DisplayItem *pRow;
1463

1464 1465
  if (editor->bCaretAtEnd && !pCursor->nOffset)
    return;
1466

1467 1468 1469 1470 1471 1472
  pRow = ME_FindItemFwd(pCursor->pRun, diStartRowOrParagraphOrEnd);
  assert(pRow);
  if (pRow->type == diStartRow) {
    ME_DisplayItem *pRun = ME_FindItemFwd(pRow, diRun);
    assert(pRun);
    pCursor->pRun = pRun;
1473
    assert(pCursor->pPara == ME_GetParagraph(pCursor->pRun));
1474
    pCursor->nOffset = 0;
1475
    editor->bCaretAtEnd = TRUE;
1476 1477 1478 1479
    return;
  }
  pCursor->pRun = ME_FindItemBack(pRow, diRun);
  assert(pCursor->pRun && pCursor->pRun->member.run.nFlags & MERF_ENDPARA);
1480
  assert(pCursor->pPara == ME_GetParagraph(pCursor->pRun));
1481 1482 1483
  pCursor->nOffset = 0;
  editor->bCaretAtEnd = FALSE;
}
1484

1485
static void ME_ArrowCtrlEnd(ME_TextEditor *editor, ME_Cursor *pCursor)
1486
{
1487
  pCursor->pPara = editor->pBuffer->pLast->member.para.prev_para;
1488 1489
  pCursor->pRun = ME_FindItemBack(editor->pBuffer->pLast, diRun);
  assert(pCursor->pRun->member.run.nFlags & MERF_ENDPARA);
1490 1491 1492 1493 1494 1495 1496 1497 1498
  pCursor->nOffset = 0;
  editor->bCaretAtEnd = FALSE;
}

BOOL ME_IsSelection(ME_TextEditor *editor)
{
  return memcmp(&editor->pCursors[0], &editor->pCursors[1], sizeof(ME_Cursor))!=0;
}

1499
static int ME_GetSelCursor(ME_TextEditor *editor, int dir)
1500 1501 1502 1503 1504 1505 1506 1507
{
  int cdir = ME_GetCursorOfs(editor, 0) - ME_GetCursorOfs(editor, 1);
  
  if (cdir*dir>0)
    return 0;
  else
    return 1;
}
1508

1509 1510 1511 1512 1513 1514 1515
void ME_DeleteSelection(ME_TextEditor *editor)
{
  int from, to;
  ME_GetSelection(editor, &from, &to);
  ME_DeleteTextAtCursor(editor, ME_GetSelCursor(editor,-1), to-from);
}

1516 1517
ME_Style *ME_GetSelectionInsertStyle(ME_TextEditor *editor)
{
1518
  return ME_GetInsertStyle(editor, 0);
1519 1520
}

1521 1522 1523
void ME_SendSelChange(ME_TextEditor *editor)
{
  SELCHANGE sc;
1524

1525 1526
  if (!(editor->nEventMask & ENM_SELCHANGE))
    return;
1527

1528
  sc.nmhdr.code = EN_SELCHANGE;
1529
  ME_GetSelection(editor, &sc.chrg.cpMin, &sc.chrg.cpMax);
1530 1531 1532 1533 1534
  sc.seltyp = SEL_EMPTY;
  if (sc.chrg.cpMin != sc.chrg.cpMax)
    sc.seltyp |= SEL_TEXT;
  if (sc.chrg.cpMin < sc.chrg.cpMax+1) /* wth were RICHEDIT authors thinking ? */
    sc.seltyp |= SEL_MULTICHAR;
1535 1536 1537 1538 1539 1540
  TRACE("cpMin=%d cpMax=%d seltyp=%d (%s %s)\n",
    sc.chrg.cpMin, sc.chrg.cpMax, sc.seltyp,
    (sc.seltyp & SEL_TEXT) ? "SEL_TEXT" : "",
    (sc.seltyp & SEL_MULTICHAR) ? "SEL_MULTICHAR" : "");
  if (sc.chrg.cpMin != editor->notified_cr.cpMin || sc.chrg.cpMax != editor->notified_cr.cpMax)
  {
1541 1542
    ME_ClearTempStyle(editor);

1543
    editor->notified_cr = sc.chrg;
1544
    ITextHost_TxNotify(editor->texthost, sc.nmhdr.code, &sc);
1545
  }
1546 1547
}

1548
BOOL
1549
ME_ArrowKey(ME_TextEditor *editor, int nVKey, BOOL extend, BOOL ctrl)
1550 1551 1552 1553
{
  int nCursor = 0;
  ME_Cursor *p = &editor->pCursors[nCursor];
  ME_Cursor tmp_curs = *p;
1554
  BOOL success = FALSE;
1555

1556
  ME_CheckCharOffsets(editor);
1557
  switch(nVKey) {
1558 1559
    case VK_LEFT:
      editor->bCaretAtEnd = 0;
1560 1561 1562 1563
      if (ctrl)
        success = ME_MoveCursorWords(editor, &tmp_curs, -1);
      else
        success = ME_MoveCursorChars(editor, &tmp_curs, -1);
1564 1565 1566
      break;
    case VK_RIGHT:
      editor->bCaretAtEnd = 0;
1567 1568 1569 1570
      if (ctrl)
        success = ME_MoveCursorWords(editor, &tmp_curs, +1);
      else
        success = ME_MoveCursorChars(editor, &tmp_curs, +1);
1571
      break;
1572
    case VK_UP:
1573 1574
      ME_MoveCursorLines(editor, &tmp_curs, -1);
      break;
1575
    case VK_DOWN:
1576 1577
      ME_MoveCursorLines(editor, &tmp_curs, +1);
      break;
1578
    case VK_PRIOR:
1579 1580
      ME_ArrowPageUp(editor, &tmp_curs);
      break;
1581
    case VK_NEXT:
1582 1583
      ME_ArrowPageDown(editor, &tmp_curs);
      break;
1584
    case VK_HOME: {
1585
      if (ctrl)
1586
        ME_ArrowCtrlHome(editor, &tmp_curs);
1587
      else
1588
        ME_ArrowHome(editor, &tmp_curs);
1589
      editor->bCaretAtEnd = 0;
1590
      break;
1591
    }
1592
    case VK_END:
1593
      if (ctrl)
1594
        ME_ArrowCtrlEnd(editor, &tmp_curs);
1595
      else
1596 1597
        ME_ArrowEnd(editor, &tmp_curs);
      break;
1598
  }
1599

1600 1601 1602
  if (!extend)
    editor->pCursors[1] = tmp_curs;
  *p = tmp_curs;
1603

1604 1605
  ME_InvalidateSelection(editor);
  ME_Repaint(editor);
1606
  ITextHost_TxShowCaret(editor->texthost, FALSE);
1607
  ME_EnsureVisible(editor, &tmp_curs);
1608 1609 1610
  ME_ShowCaret(editor);
  ME_SendSelChange(editor);
  return success;
1611
}