caret.c 47.3 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 30 31 32 33
void ME_SetCursorToStart(ME_TextEditor *editor, ME_Cursor *cursor)
{
  cursor->pPara = editor->pBuffer->pFirst->member.para.next_para;
  cursor->pRun = ME_FindItemFwd(cursor->pPara, diRun);
  cursor->nOffset = 0;
}

34
static void ME_SetCursorToEnd(ME_TextEditor *editor, ME_Cursor *cursor)
35 36 37 38 39 40 41
{
  cursor->pPara = editor->pBuffer->pLast->member.para.prev_para;
  cursor->pRun = ME_FindItemBack(editor->pBuffer->pLast, diRun);
  cursor->nOffset = 0;
}


42
int ME_GetSelectionOfs(ME_TextEditor *editor, int *from, int *to)
43
{
44 45
  *from = ME_GetCursorOfs(&editor->pCursors[0]);
  *to =   ME_GetCursorOfs(&editor->pCursors[1]);
46

47 48 49 50
  if (*from > *to)
  {
    int tmp = *from;
    *from = *to;
51 52 53 54 55 56 57 58
    *to = tmp;
    return 1;
  }
  return 0;
}

int ME_GetSelection(ME_TextEditor *editor, ME_Cursor **from, ME_Cursor **to)
{
59 60 61 62 63 64 65 66 67 68 69 70 71
  int from_ofs = ME_GetCursorOfs( &editor->pCursors[0] );
  int to_ofs   = ME_GetCursorOfs( &editor->pCursors[1] );
  BOOL swap = (from_ofs > to_ofs);

  if (from_ofs == to_ofs)
  {
      /* If cursor[0] is at the beginning of a run and cursor[1] at the end
         of the prev run then we need to swap. */
      if (editor->pCursors[0].nOffset < editor->pCursors[1].nOffset)
          swap = TRUE;
  }

  if (!swap)
72 73 74 75 76 77 78 79
  {
    *from = &editor->pCursors[0];
    *to = &editor->pCursors[1];
    return 0;
  } else {
    *from = &editor->pCursors[1];
    *to = &editor->pCursors[0];
    return 1;
80 81 82 83 84
  }
}

int ME_GetTextLength(ME_TextEditor *editor)
{
85 86 87
  ME_Cursor cursor;
  ME_SetCursorToEnd(editor, &cursor);
  return ME_GetCursorOfs(&cursor);
88 89
}

90

91
int ME_GetTextLengthEx(ME_TextEditor *editor, const GETTEXTLENGTHEX *how)
92 93
{
  int length;
94

95 96 97 98 99 100
  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);
101

102
  if ((editor->styleFlags & ES_MULTILINE)
103 104
        && (how->flags & GTL_USECRLF)
        && !editor->bEmulateVersion10) /* Ignore GTL_USECRLF flag in 1.0 emulation */
105
    length += editor->nParagraphs - 1;
106 107 108 109

  if (how->flags & GTL_NUMBYTES ||
      (how->flags & GTL_PRECISE &&     /* GTL_PRECISE seems to imply GTL_NUMBYTES */
       !(how->flags & GTL_NUMCHARS)))  /* unless GTL_NUMCHARS is given */
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
  {
    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; 
}


126
int ME_SetSelection(ME_TextEditor *editor, int from, int to)
127
{
128 129 130 131 132 133 134 135 136 137
  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 */
138 139
  if (from == 0 && to == -1)
  {
140 141
    ME_SetCursorToStart(editor, &editor->pCursors[1]);
    ME_SetCursorToEnd(editor, &editor->pCursors[0]);
142
    ME_InvalidateSelection(editor);
143
    return len + 1;
144
  }
145 146 147 148

  /* 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))
149
  {
150
    selectionEnd = 1;
151
  }
152
  else
153
  {
154 155 156 157 158
    /* 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;
159
      ME_GetSelectionOfs(editor, &start, &end);
160 161 162 163 164
      if (start != end)
      {
          editor->pCursors[1] = editor->pCursors[0];
          ME_Repaint(editor);
      }
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
      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;
186
  }
187 188

  if (selectionEnd)
189
  {
190
    ME_SetCursorToEnd(editor, &editor->pCursors[0]);
191
    editor->pCursors[1] = editor->pCursors[0];
192 193
    ME_InvalidateSelection(editor);
    return len;
194
  }
195

196
  ME_CursorFromCharOfs(editor, from, &editor->pCursors[1]);
197 198
  editor->pCursors[0] = editor->pCursors[1];
  ME_MoveCursorChars(editor, &editor->pCursors[0], to - from);
199 200 201 202 203
  /* 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;
204
  return to;
205 206 207
}


208
static void
209 210 211
ME_GetCursorCoordinates(ME_TextEditor *editor, ME_Cursor *pCursor,
                        int *x, int *y, int *height)
{
212 213
  ME_DisplayItem *row;
  ME_DisplayItem *run = pCursor->pRun;
214
  ME_DisplayItem *para = pCursor->pPara;
215 216
  ME_DisplayItem *pSizeRun = run;
  ME_Context c;
217
  int run_x;
218 219

  assert(height && x && y);
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
  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);
247 248
    }
  }
249
  run_x = ME_PointFromCharContext( &c, &run->member.run, pCursor->nOffset, TRUE );
250 251

  *height = pSizeRun->member.run.nAscent + pSizeRun->member.run.nDescent;
252
  *x = c.rcView.left + run->member.run.pt.x + run_x - editor->horz_si.nPos;
253 254 255 256 257
  *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;
258 259 260 261 262 263 264 265 266
}


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

  ME_GetCursorCoordinates(editor, &editor->pCursors[0], &x, &y, &height);
267
  if(editor->bHaveFocus && !ME_IsSelection(editor))
268
  {
269
    x = min(x, editor->rcFormat.right-1);
270 271
    ITextHost_TxCreateCaret(editor->texthost, NULL, 0, height);
    ITextHost_TxSetCaretPos(editor->texthost, x, y);
272
  }
273 274
}

275

276 277 278
void ME_ShowCaret(ME_TextEditor *ed)
{
  ME_MoveCaret(ed);
279
  if(ed->bHaveFocus && !ME_IsSelection(ed))
280
    ITextHost_TxShowCaret(ed->texthost, TRUE);
281 282 283 284
}

void ME_HideCaret(ME_TextEditor *ed)
{
285
  if(!ed->bHaveFocus || ME_IsSelection(ed))
286
  {
287
    ITextHost_TxShowCaret(ed->texthost, FALSE);
288 289
    DestroyCaret();
  }
290 291
}

292 293
BOOL ME_InternalDeleteText(ME_TextEditor *editor, ME_Cursor *start,
                           int nChars, BOOL bForce)
294
{
295
  ME_Cursor c = *start;
296
  int nOfs = ME_GetCursorOfs(start), text_len = ME_GetTextLength( editor );
297
  int shift = 0;
298
  int totalChars = nChars;
299
  ME_DisplayItem *start_para;
300
  BOOL delete_all = FALSE;
301

302
  /* Prevent deletion past last end of paragraph run. */
303 304
  nChars = min(nChars, text_len - nOfs);
  if (nChars == text_len) delete_all = TRUE;
305
  start_para = c.pPara;
306 307 308

  if (!bForce)
  {
309
    ME_ProtectPartialTableDeletion(editor, &c, &nChars);
310 311 312 313
    if (nChars == 0)
      return FALSE;
  }

314 315 316
  while(nChars > 0)
  {
    ME_Run *run;
317 318 319
    ME_CursorFromCharOfs(editor, nOfs+nChars, &c);
    if (!c.nOffset &&
        nOfs+nChars == (c.pRun->member.run.nCharOfs
320
                        + c.pPara->member.para.nCharOfs))
321 322 323
    {
      /* We aren't deleting anything in this run, so we will go back to the
       * last run we are deleting text in. */
324
      ME_PrevRun(&c.pPara, &c.pRun);
325
      c.nOffset = c.pRun->member.run.len;
326
    }
327 328
    run = &c.pRun->member.run;
    if (run->nFlags & MERF_ENDPARA) {
329
      int eollen = c.pRun->member.run.len;
330
      BOOL keepFirstParaFormat;
331

332 333
      if (!ME_FindItemFwd(c.pRun, diParagraph))
      {
334
        return TRUE;
335
      }
336 337
      keepFirstParaFormat = (totalChars == nChars && nChars <= eollen &&
                             run->nCharOfs);
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359
      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;
        }
      }
360
      ME_JoinParagraphs(editor, c.pPara, keepFirstParaFormat);
361 362
      /* ME_SkipAndPropagateCharOffset(p->pRun, shift); */
      ME_CheckCharOffsets(editor);
363
      nChars -= (eollen < nChars) ? eollen : nChars;
364 365 366 367 368
      continue;
    }
    else
    {
      ME_Cursor cursor;
369
      int nCharsToDelete = min(nChars, c.nOffset);
370
      int i;
371 372 373

      c.nOffset -= nCharsToDelete;

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

376 377
      cursor = c;
      /* nChars is the number of characters that should be deleted from the
378
         PRECEDING runs (these BEFORE cursor.pRun)
379
         nCharsToDelete is a number of chars to delete from THIS run */
380
      nChars -= nCharsToDelete;
381
      shift -= nCharsToDelete;
382
      TRACE("Deleting %d (remaning %d) chars at %d in %s (%d)\n",
383
        nCharsToDelete, nChars, c.nOffset,
384
        debugstr_run( run ), run->len);
385

386 387
      /* nOfs is a character offset (from the start of the document
         to the current (deleted) run */
388
      add_undo_insert_run( editor, nOfs + nChars, get_text( run, c.nOffset ), nCharsToDelete, run->nFlags, run->style );
389

390
      ME_StrDeleteV(run->para->text, run->nCharOfs + c.nOffset, nCharsToDelete);
391 392 393
      run->len -= nCharsToDelete;
      TRACE("Post deletion string: %s (%d)\n", debugstr_run( run ), run->len);
      TRACE("Shift value: %d\n", shift);
394

395 396
      /* update cursors (including c) */
      for (i=-1; i<editor->nCursors; i++) {
397
        ME_Cursor *pThisCur = editor->pCursors + i;
398 399 400 401 402 403 404 405
        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);
406
            assert(pThisCur->nOffset <= run->len);
407
          }
408
          if (pThisCur->nOffset == run->len)
409 410 411 412 413 414 415
          {
            pThisCur->pRun = ME_FindItemFwd(pThisCur->pRun, diRunOrParagraphOrEnd);
            assert(pThisCur->pRun->type == diRun);
            pThisCur->nOffset = 0;
          }
        }
      }
416

417
      /* c = updated data now */
418

419 420 421 422 423
      if (c.pRun == cursor.pRun)
        ME_SkipAndPropagateCharOffset(c.pRun, shift);
      else
        ME_PropagateCharOffset(c.pRun, shift);

424
      if (!cursor.pRun->member.run.len)
425
      {
426
        TRACE("Removing empty run\n");
427 428 429
        ME_Remove(cursor.pRun);
        ME_DestroyDisplayItem(cursor.pRun);
      }
430

431 432 433 434 435 436 437
      shift = 0;
      /*
      ME_CheckCharOffsets(editor);
      */
      continue;
    }
  }
438
  if (delete_all) ME_SetDefaultParaFormat( start_para->member.para.pFmt );
439
  return TRUE;
440 441
}

442
BOOL ME_DeleteTextAtCursor(ME_TextEditor *editor, int nCursor, int nChars)
443
{
444
  assert(nCursor>=0 && nCursor<editor->nCursors);
445 446
  /* text operations set modified state */
  editor->nModifyStep = 1;
447
  return ME_InternalDeleteText(editor, &editor->pCursors[nCursor],
448
                               nChars, FALSE);
449 450
}

451
static ME_DisplayItem *
452 453 454 455 456 457 458 459 460 461
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);
  
462
  return ME_InsertRunAtCursor(editor, p, style, str, len, flags);
463 464 465
}


466
void ME_InsertOLEFromCursor(ME_TextEditor *editor, const REOBJECT* reo, int nCursor)
467
{
468 469 470
  ME_Style              *pStyle = ME_GetInsertStyle(editor, nCursor);
  ME_DisplayItem        *di;
  WCHAR                 space = ' ';
471 472 473 474 475
  
  /* FIXME no no no */
  if (ME_IsSelection(editor))
    ME_DeleteSelection(editor);

476 477 478 479
  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);
480
  ME_ReleaseStyle(pStyle);
481 482 483
}


484 485 486 487 488 489 490 491 492
void ME_InsertEndRowFromCursor(ME_TextEditor *editor, int nCursor)
{
  ME_Style              *pStyle = ME_GetInsertStyle(editor, nCursor);
  WCHAR                 space = ' ';

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

493 494
  ME_InternalInsertTextFromCursor(editor, nCursor, &space, 1, pStyle,
                                  MERF_ENDROW);
495
  ME_ReleaseStyle(pStyle);
496 497
}

498

499 500 501 502 503
void ME_InsertTextFromCursor(ME_TextEditor *editor, int nCursor, 
  const WCHAR *str, int len, ME_Style *style)
{
  const WCHAR *pos;
  ME_Cursor *p = NULL;
504
  int oldLen;
505 506 507 508 509

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

510 511
  /* FIXME: is this too slow? */
  /* Didn't affect performance for WM_SETTEXT (around 50sec/30K) */
512
  oldLen = ME_GetTextLength(editor);
513

514 515 516
  /* text operations set modified state */
  editor->nModifyStep = 1;

517 518 519 520 521
  assert(style);

  assert(nCursor>=0 && nCursor<editor->nCursors);
  if (len == -1)
    len = lstrlenW(str);
522 523 524 525 526

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

527 528
  pos = str;

529 530 531
  while (len)
  {
    /* FIXME this sucks - no respect for unicode (what else can be a line separator in unicode?) */
532
    while(pos - str < len && *pos != '\r' && *pos != '\n' && *pos != '\t')
533
      pos++;
534

535 536 537 538
    if (pos != str) { /* handle text */
      ME_InternalInsertTextFromCursor(editor, nCursor, str, pos-str, style, 0);
    } else if (*pos == '\t') { /* handle tabs */
      WCHAR tab = '\t';
539 540
      ME_InternalInsertTextFromCursor(editor, nCursor, &tab, 1, style, MERF_TAB);
      pos++;
541
    } else { /* handle EOLs */
542
      ME_DisplayItem *tp, *end_run, *run, *prev;
543
      ME_Style *tmp_style;
544
      int eol_len = 0;
545

546 547 548 549
      /* Find number of CR and LF in end of paragraph run */
      if (*pos =='\r')
      {
        if (len > 1 && pos[1] == '\n')
550
          eol_len = 2;
551
        else if (len > 2 && pos[1] == '\r' && pos[2] == '\n')
552 553 554
          eol_len = 3;
        else
          eol_len = 1;
555
      } else {
556
        assert(*pos == '\n');
557
        eol_len = 1;
558
      }
559
      pos += eol_len;
560

561
      if (!editor->bEmulateVersion10 && eol_len == 3)
562 563 564 565 566
      {
        /* handle special \r\r\n sequence (richedit 2.x and higher only) */
        WCHAR space = ' ';
        ME_InternalInsertTextFromCursor(editor, nCursor, &space, 1, style, 0);
      } else {
567
        const WCHAR cr = '\r', *eol_str = str;
568

569 570 571 572
        if (!editor->bEmulateVersion10)
        {
          eol_str = &cr;
          eol_len = 1;
573
        }
574 575

        p = &editor->pCursors[nCursor];
576 577 578 579 580 581 582 583 584 585 586 587

        if (p->nOffset == p->pRun->member.run.len)
        {
           run = ME_FindItemFwd( p->pRun, diRun );
           if (!run) run = p->pRun;
        }
        else
        {
          if (p->nOffset) ME_SplitRunSimple(editor, p);
          run = p->pRun;
        }

588 589
        tmp_style = ME_GetInsertStyle(editor, nCursor);
        /* ME_SplitParagraph increases style refcount */
590 591
        tp = ME_SplitParagraph(editor, run, run->member.run.style, eol_str, eol_len, 0);

592 593 594
        end_run = ME_FindItemBack(tp, diRun);
        ME_ReleaseStyle(end_run->member.run.style);
        end_run->member.run.style = tmp_style;
595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612

        /* Move any cursors that were at the end of the previous run to the beginning of the new para */
        prev = ME_FindItemBack( end_run, diRun );
        if (prev)
        {
          int i;
          for (i = 0; i < editor->nCursors; i++)
          {
            if (editor->pCursors[i].pRun == prev &&
                editor->pCursors[i].nOffset == prev->member.run.len)
            {
              editor->pCursors[i].pPara = tp;
              editor->pCursors[i].pRun = run;
              editor->pCursors[i].nOffset = 0;
            }
          }
        }

613
      }
614
    }
615 616
    len -= pos - str;
    str = pos;
617 618 619
  }
}

620 621 622 623 624
/* Move the cursor nRelOfs characters (either forwards or backwards)
 *
 * returns the actual number of characters moved.
 **/
int ME_MoveCursorChars(ME_TextEditor *editor, ME_Cursor *cursor, int nRelOfs)
625
{
626 627
  cursor->nOffset += nRelOfs;
  if (cursor->nOffset < 0)
628
  {
629 630
    cursor->nOffset += cursor->pRun->member.run.nCharOfs;
    if (cursor->nOffset >= 0)
631
    {
632
      /* new offset in the same paragraph */
633
      do {
634 635 636 637
        cursor->pRun = ME_FindItemBack(cursor->pRun, diRun);
      } while (cursor->nOffset < cursor->pRun->member.run.nCharOfs);
      cursor->nOffset -= cursor->pRun->member.run.nCharOfs;
      return nRelOfs;
638
    }
639

640 641
    cursor->nOffset += cursor->pPara->member.para.nCharOfs;
    if (cursor->nOffset <= 0)
642
    {
643 644 645 646 647
      /* moved to the start of the text */
      nRelOfs -= cursor->nOffset;
      ME_SetCursorToStart(editor, cursor);
      return nRelOfs;
    }
648

649 650 651 652 653 654 655 656 657 658 659
    /* new offset in a previous paragraph */
    do {
      cursor->pPara = cursor->pPara->member.para.prev_para;
    } while (cursor->nOffset < cursor->pPara->member.para.nCharOfs);
    cursor->nOffset -= cursor->pPara->member.para.nCharOfs;

    cursor->pRun = ME_FindItemBack(cursor->pPara->member.para.next_para, diRun);
    while (cursor->nOffset < cursor->pRun->member.run.nCharOfs) {
      cursor->pRun = ME_FindItemBack(cursor->pRun, diRun);
    }
    cursor->nOffset -= cursor->pRun->member.run.nCharOfs;
660
  } else if (cursor->nOffset >= cursor->pRun->member.run.len) {
661 662 663 664 665 666 667 668 669
    ME_DisplayItem *next_para;
    int new_offset;

    new_offset = ME_GetCursorOfs(cursor);
    next_para = cursor->pPara->member.para.next_para;
    if (new_offset < next_para->member.para.nCharOfs)
    {
      /* new offset in the same paragraph */
      do {
670
        cursor->nOffset -= cursor->pRun->member.run.len;
671
        cursor->pRun = ME_FindItemFwd(cursor->pRun, diRun);
672
      } while (cursor->nOffset >= cursor->pRun->member.run.len);
673
      return nRelOfs;
674
    }
675 676 677 678 679 680 681 682 683 684

    if (new_offset >= ME_GetTextLength(editor))
    {
      /* new offset at the end of the text */
      ME_SetCursorToEnd(editor, cursor);
      nRelOfs -= new_offset - ME_GetTextLength(editor);
      return nRelOfs;
    }

    /* new offset in a following paragraph */
685
    do {
686 687 688 689 690 691
      cursor->pPara = next_para;
      next_para = next_para->member.para.next_para;
    } while (new_offset >= next_para->member.para.nCharOfs);

    cursor->nOffset = new_offset - cursor->pPara->member.para.nCharOfs;
    cursor->pRun = ME_FindItemFwd(cursor->pPara, diRun);
692
    while (cursor->nOffset >= cursor->pRun->member.run.len)
693
    {
694
      cursor->nOffset -= cursor->pRun->member.run.len;
695
      cursor->pRun = ME_FindItemFwd(cursor->pRun, diRun);
696
    }
697 698
  } /* else new offset is in the same run */
  return nRelOfs;
699 700
}

701

702 703 704 705
static BOOL
ME_MoveCursorWords(ME_TextEditor *editor, ME_Cursor *cursor, int nRelOfs)
{
  ME_DisplayItem *pRun = cursor->pRun, *pOtherRun;
706
  ME_DisplayItem *pPara = cursor->pPara;
707
  int nOffset = cursor->nOffset;
708

709 710 711 712 713
  if (nRelOfs == -1)
  {
    /* Backward movement */
    while (TRUE)
    {
714 715
      nOffset = ME_CallWordBreakProc(editor, get_text( &pRun->member.run, 0 ),
                                     pRun->member.run.len, nOffset, WB_MOVEWORDLEFT);
716
      if (nOffset)
717 718 719 720
        break;
      pOtherRun = ME_FindItemBack(pRun, diRunOrParagraph);
      if (pOtherRun->type == diRun)
      {
721 722
        if (ME_CallWordBreakProc(editor, get_text( &pOtherRun->member.run, 0 ),
                                 pOtherRun->member.run.len,
723
                                 pOtherRun->member.run.len - 1,
724 725 726
                                 WB_ISDELIMITER)
            && !(pRun->member.run.nFlags & MERF_ENDPARA)
            && !(cursor->pRun == pRun && cursor->nOffset == 0)
727 728
            && !ME_CallWordBreakProc(editor, get_text( &pRun->member.run, 0 ),
                                     pRun->member.run.len, 0,
729 730 731
                                     WB_ISDELIMITER))
          break;
        pRun = pOtherRun;
732
        nOffset = pOtherRun->member.run.len;
733 734 735 736 737
      }
      else if (pOtherRun->type == diParagraph)
      {
        if (cursor->pRun == pRun && cursor->nOffset == 0)
        {
738
          pPara = pOtherRun;
739
          /* Skip empty start of table row paragraph */
740 741
          if (pPara->member.para.prev_para->member.para.nFlags & MEPF_ROWSTART)
            pPara = pPara->member.para.prev_para;
742
          /* Paragraph breaks are treated as separate words */
743
          if (pPara->member.para.prev_para->type == diTextStart)
744
            return FALSE;
745

746
          pRun = ME_FindItemBack(pPara, diRun);
747
          pPara = pPara->member.para.prev_para;
748 749 750 751 752 753 754 755 756 757 758 759
        }
        break;
      }
    }
  }
  else
  {
    /* Forward movement */
    BOOL last_delim = FALSE;
    
    while (TRUE)
    {
760 761
      if (last_delim && !ME_CallWordBreakProc(editor, get_text( &pRun->member.run, 0 ),
                                              pRun->member.run.len, nOffset, WB_ISDELIMITER))
762
        break;
763 764
      nOffset = ME_CallWordBreakProc(editor, get_text( &pRun->member.run, 0 ),
                                     pRun->member.run.len, nOffset, WB_MOVEWORDRIGHT);
765
      if (nOffset < pRun->member.run.len)
766 767 768 769
        break;
      pOtherRun = ME_FindItemFwd(pRun, diRunOrParagraphOrEnd);
      if (pOtherRun->type == diRun)
      {
770 771
        last_delim = ME_CallWordBreakProc(editor, get_text( &pRun->member.run, 0 ),
                                          pRun->member.run.len, nOffset - 1, WB_ISDELIMITER);
772 773 774 775 776
        pRun = pOtherRun;
        nOffset = 0;
      }
      else if (pOtherRun->type == diParagraph)
      {
777 778
        if (pOtherRun->member.para.nFlags & MEPF_ROWSTART)
            pOtherRun = pOtherRun->member.para.next_para;
779 780 781 782
        if (cursor->pRun == pRun) {
          pPara = pOtherRun;
          pRun = ME_FindItemFwd(pPara, diRun);
        }
783 784 785 786 787 788 789 790 791 792 793 794
        nOffset = 0;
        break;
      }
      else /* diTextEnd */
      {
        if (cursor->pRun == pRun)
          return FALSE;
        nOffset = 0;
        break;
      }
    }
  }
795
  cursor->pPara = pPara;
796 797 798 799
  cursor->pRun = pRun;
  cursor->nOffset = nOffset;
  return TRUE;
}
800

801

802
static void
803
ME_SelectByType(ME_TextEditor *editor, ME_SelectionType selectionType)
804
{
805 806
  /* pCursor[0] is the end of the selection
   * pCursor[1] is the start of the selection (or the position selection anchor)
807 808 809 810 811 812 813 814 815 816
   * 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:
817 818 819
      ME_MoveCursorWords(editor, &editor->pCursors[0], +1);
      editor->pCursors[1] = editor->pCursors[0];
      ME_MoveCursorWords(editor, &editor->pCursors[1], -1);
820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835
      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)
836
          editor->pCursors[0].pRun = ME_FindItemBack(pItem, diRun);
837
      else
838
          editor->pCursors[0].pRun = ME_FindItemFwd(pItem, diRun);
839
      editor->pCursors[0].pPara = ME_GetParagraph(editor->pCursors[0].pRun);
840 841 842 843
      editor->pCursors[0].nOffset = 0;

      pItem = ME_FindItemBack(pItem, backSearchType);
      editor->pCursors[1].pRun = ME_FindItemFwd(pItem, diRun);
844
      editor->pCursors[1].pPara = ME_GetParagraph(editor->pCursors[1].pRun);
845 846 847
      editor->pCursors[1].nOffset = 0;
      break;
    }
848 849 850
    case stDocument:
      /* Select everything with cursor anchored from the start of the text */
      editor->nSelectionType = stDocument;
851 852
      ME_SetCursorToStart(editor, &editor->pCursors[1]);
      ME_SetCursorToEnd(editor, &editor->pCursors[0]);
853
      break;
854 855 856 857 858
    default: assert(0);
  }
  /* Store the anchor positions for extending the selection. */
  editor->pCursors[2] = editor->pCursors[0];
  editor->pCursors[3] = editor->pCursors[1];
859 860
}

861
int ME_GetCursorOfs(const ME_Cursor *cursor)
862
{
863 864
  return cursor->pPara->member.para.nCharOfs
         + cursor->pRun->member.run.nCharOfs + cursor->nOffset;
865 866
}

867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905
/* 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;
}

906
static BOOL ME_FindRunInRow(ME_TextEditor *editor, ME_DisplayItem *pRow,
907
                            int x, ME_Cursor *cursor, BOOL *pbCaretAtEnd)
908
{
909
  ME_DisplayItem *pNext, *pLastRun;
910 911 912 913 914 915 916 917
  ME_Row *row = &pRow->member.row;
  BOOL exact = TRUE;

  if (x < row->pt.x)
  {
      x = row->pt.x;
      exact = FALSE;
  }
918 919 920 921 922 923 924
  pNext = ME_FindItemFwd(pRow, diRunOrStartRow);
  assert(pNext->type == diRun);
  if (pbCaretAtEnd) *pbCaretAtEnd = FALSE;
  cursor->nOffset = 0;
  do {
    int run_x = pNext->member.run.pt.x;
    int width = pNext->member.run.nWidth;
925

926 927
    if (x >= run_x && x < run_x+width)
    {
928
      cursor->nOffset = ME_CharFromPoint(editor, x-run_x, &pNext->member.run, TRUE, TRUE);
929 930
      cursor->pRun = pNext;
      cursor->pPara = ME_GetParagraph( cursor->pRun );
931
      return exact;
932 933 934 935 936 937
    }
    pLastRun = pNext;
    pNext = ME_FindItemFwd(pNext, diRunOrStartRow);
  } while(pNext && pNext->type == diRun);

  if ((pLastRun->member.run.nFlags & MERF_ENDPARA) == 0)
938
  {
939 940
    cursor->pRun = ME_FindItemFwd(pNext, diRun);
    if (pbCaretAtEnd) *pbCaretAtEnd = TRUE;
941
  }
942 943 944 945 946
  else
    cursor->pRun = pLastRun;

  cursor->pPara = ME_GetParagraph( cursor->pRun );
  return FALSE;
947 948
}

949 950 951 952 953 954 955 956 957 958
/* 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)
959 960
{
  ME_DisplayItem *p = editor->pBuffer->pFirst->member.para.next_para;
961
  BOOL isExact = TRUE;
962

963 964 965
  x -= editor->rcFormat.left;
  y -= editor->rcFormat.top;

966
  if (is_eol)
967
    *is_eol = FALSE;
968

969 970
  /* find paragraph */
  for (; p != editor->pBuffer->pLast; p = p->member.para.next_para)
971
  {
972
    assert(p->type == diParagraph);
973
    if (y < p->member.para.pt.y + p->member.para.nHeight)
974
    {
975 976 977
      if (p->member.para.nFlags & MEPF_ROWSTART)
        p = ME_FindPixelPosInTableRow(x, y, p);
      y -= p->member.para.pt.y;
978
      p = ME_FindItemFwd(p, diStartRow);
979
      break;
980 981
    } else if (p->member.para.nFlags & MEPF_ROWSTART) {
      p = ME_GetTableRowEnd(p);
982
    }
983 984 985 986 987 988
  }
  /* find row */
  for (; p != editor->pBuffer->pLast; )
  {
    ME_DisplayItem *pp;
    assert(p->type == diStartRow);
989
    if (y < p->member.row.pt.y + p->member.row.nHeight) break;
990
    pp = ME_FindItemFwd(p, diStartRow);
991
    if (!pp) break;
992 993
    p = pp;
  }
994 995 996 997 998
  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. */
999
    isExact = FALSE;
1000
    p = ME_FindItemBack(p, diStartRow);
1001
    if (!p) p = editor->pBuffer->pLast;
1002
  }
1003 1004 1005 1006 1007 1008

  assert( p->type == diStartRow || p == editor->pBuffer->pLast );

  if( p->type == diStartRow )
      return ME_FindRunInRow( editor, p, x, result, is_eol ) && isExact;

1009
  result->pRun = ME_FindItemBack(p, diRun);
1010
  result->pPara = ME_GetParagraph(result->pRun);
1011 1012
  result->nOffset = 0;
  assert(result->pRun->member.run.nFlags & MERF_ENDPARA);
1013
  return FALSE;
1014 1015
}

1016

1017
/* Sets the cursor to the position closest to the pixel position
1018 1019 1020 1021 1022
 *
 * 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.
1023 1024 1025
 *
 * return FALSE if outside client area and the cursor is not set,
 * otherwise TRUE is returned.
1026
 */
1027 1028
BOOL ME_CharFromPos(ME_TextEditor *editor, int x, int y,
                    ME_Cursor *cursor, BOOL *isExact)
1029 1030
{
  RECT rc;
1031
  BOOL bResult;
1032

1033
  ITextHost_TxGetClientRect(editor->texthost, &rc);
1034 1035
  if (x < 0 || y < 0 || x >= rc.right || y >= rc.bottom) {
    if (isExact) *isExact = FALSE;
1036
    return FALSE;
1037
  }
1038 1039
  x += editor->horz_si.nPos;
  y += editor->vert_si.nPos;
1040
  bResult = ME_FindPixelPos(editor, x, y, cursor, NULL);
1041
  if (isExact) *isExact = bResult;
1042
  return TRUE;
1043 1044
}

1045 1046


1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060
/* 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;
1061
  if (editor->nSelectionType == stPosition || editor->nSelectionType == stDocument)
1062
      return;
1063 1064 1065
  curOfs = ME_GetCursorOfs(&editor->pCursors[0]);
  anchorStartOfs = ME_GetCursorOfs(&editor->pCursors[3]);
  anchorEndOfs = ME_GetCursorOfs(&editor->pCursors[2]);
1066 1067 1068 1069 1070 1071 1072

  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 */
1073
      editor->pCursors[1] = tmp_cursor;
1074
      if (editor->nSelectionType == stWord)
1075
          ME_MoveCursorWords(editor, &editor->pCursors[1], -1);
1076 1077 1078
      else
      {
          ME_DisplayItem *pItem;
1079 1080
          ME_DIType searchType = ((editor->nSelectionType == stLine) ?
                                  diStartRowOrParagraph:diParagraph);
1081 1082
          pItem = ME_FindItemBack(editor->pCursors[1].pRun, searchType);
          editor->pCursors[1].pRun = ME_FindItemFwd(pItem, diRun);
1083
          editor->pCursors[1].pPara = ME_GetParagraph(editor->pCursors[1].pRun);
1084
          editor->pCursors[1].nOffset = 0;
1085 1086 1087 1088 1089
      }
  }
  else if (curOfs >= anchorEndOfs)
  {
      /* Extend the right side of selection */
1090
      editor->pCursors[0] = tmp_cursor;
1091
      if (editor->nSelectionType == stWord)
1092
          ME_MoveCursorWords(editor, &editor->pCursors[0], +1);
1093 1094 1095
      else
      {
          ME_DisplayItem *pItem;
1096 1097
          ME_DIType searchType = ((editor->nSelectionType == stLine) ?
                                  diStartRowOrParagraphOrEnd:diParagraphOrEnd);
1098
          pItem = ME_FindItemFwd(editor->pCursors[0].pRun, searchType);
1099
          if (pItem->type == diTextEnd)
1100
              editor->pCursors[0].pRun = ME_FindItemBack(pItem, diRun);
1101
          else
1102
              editor->pCursors[0].pRun = ME_FindItemFwd(pItem, diRun);
1103
          editor->pCursors[0].pPara = ME_GetParagraph(editor->pCursors[0].pRun);
1104
          editor->pCursors[0].nOffset = 0;
1105 1106 1107
      }
  }
}
1108

1109
void ME_LButtonDown(ME_TextEditor *editor, int x, int y, int clickNum)
1110 1111
{
  ME_Cursor tmp_cursor;
1112
  BOOL is_selection = FALSE, is_shift;
1113

1114
  editor->nUDArrowX = -1;
1115 1116 1117

  x += editor->horz_si.nPos;
  y += editor->vert_si.nPos;
1118 1119 1120

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

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

1125
  if (x >= editor->rcFormat.left || is_shift)
1126
  {
1127 1128 1129
    if (clickNum > 1)
    {
      editor->pCursors[1] = editor->pCursors[0];
1130
      if (is_shift) {
1131
          if (x >= editor->rcFormat.left)
1132 1133 1134 1135
              ME_SelectByType(editor, stWord);
          else
              ME_SelectByType(editor, stParagraph);
      } else if (clickNum % 2 == 0) {
1136
          ME_SelectByType(editor, stWord);
1137
      } else {
1138
          ME_SelectByType(editor, stParagraph);
1139
      }
1140
    }
1141
    else if (!is_shift)
1142
    {
1143
      editor->nSelectionType = stPosition;
1144 1145
      editor->pCursors[1] = editor->pCursors[0];
    }
1146 1147 1148
    else if (!is_selection)
    {
      editor->nSelectionType = stPosition;
1149
      editor->pCursors[1] = tmp_cursor;
1150 1151 1152 1153
    }
    else if (editor->nSelectionType != stPosition)
    {
      ME_ExtendAnchorSelection(editor);
1154
    }
1155 1156 1157
  }
  else
  {
1158 1159
    if (clickNum < 2) {
        ME_SelectByType(editor, stLine);
1160
    } else if (clickNum % 2 == 0 || is_shift) {
1161
        ME_SelectByType(editor, stParagraph);
1162 1163
    } else {
        ME_SelectByType(editor, stDocument);
1164
    }
1165
  }
1166
  ME_InvalidateSelection(editor);
1167
  ITextHost_TxShowCaret(editor->texthost, FALSE);
1168
  ME_ShowCaret(editor);
1169
  ME_SendSelChange(editor);
1170 1171 1172 1173 1174
}

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

1176 1177
  if (editor->nSelectionType == stDocument)
      return;
1178 1179
  x += editor->horz_si.nPos;
  y += editor->vert_si.nPos;
1180 1181

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

1185
  ME_InvalidateSelection(editor);
1186 1187 1188 1189 1190
  editor->pCursors[0] = tmp_cursor;
  ME_ExtendAnchorSelection(editor);

  if (editor->nSelectionType != stPosition &&
      memcmp(&editor->pCursors[1], &editor->pCursors[3], sizeof(ME_Cursor)))
1191
  {
1192
      /* The scroll the cursor towards the other end, since it was the one
1193
       * extended by ME_ExtendAnchorSelection */
1194
      ME_EnsureVisible(editor, &editor->pCursors[1]);
1195
  } else {
1196
      ME_EnsureVisible(editor, &editor->pCursors[0]);
1197 1198
  }

1199
  ME_InvalidateSelection(editor);
1200
  ITextHost_TxShowCaret(editor->texthost, FALSE);
1201
  ME_ShowCaret(editor);
1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220
  ME_SendSelChange(editor);
}

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;
1221
      x += ME_PointFromChar(editor, &pRun->member.run, pCursor->nOffset, TRUE);
1222 1223 1224 1225 1226 1227
    }
    editor->nUDArrowX = x;
  }
  return x;
}

1228 1229 1230

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

1237
  if (editor->bCaretAtEnd && !pCursor->nOffset)
1238 1239 1240
    if (!ME_PrevRun(&pOldPara, &pRun))
      return;

1241
  if (nRelOfs == -1)
1242
  {
1243 1244 1245 1246 1247
    /* start of this row */
    pItem = ME_FindItemBack(pRun, diStartRow);
    assert(pItem);
    /* start of the previous row */
    pItem = ME_FindItemBack(pItem, diStartRow);
1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269
    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);
    }
1270 1271 1272 1273 1274
  }
  else
  {
    /* start of the next row */
    pItem = ME_FindItemFwd(pRun, diStartRow);
1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296
    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);
    }
1297 1298 1299
  }
  if (!pItem)
  {
1300
    /* row not found - ignore */
1301 1302
    return;
  }
1303
  ME_FindRunInRow(editor, pItem, x, pCursor, &editor->bCaretAtEnd);
1304 1305 1306 1307
  assert(pCursor->pRun);
  assert(pCursor->pRun->type == diRun);
}

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

1312
  if (editor->vert_si.nPos < p->member.row.nHeight)
1313
  {
1314
    ME_SetCursorToStart(editor, pCursor);
1315 1316 1317 1318 1319 1320 1321 1322
    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;
1323
    int x, y, yd, yp;
1324
    int yOldScrollPos = editor->vert_si.nPos;
1325

1326 1327 1328
    x = ME_GetXForArrow(editor, pCursor);
    if (!pCursor->nOffset && editor->bCaretAtEnd)
      pRun = ME_FindItemBack(pRun, diRun);
1329

1330 1331 1332
    p = ME_FindItemBack(pRun, diStartRowOrParagraph);
    assert(p->type == diStartRow);
    yp = ME_FindItemBack(p, diParagraph)->member.para.pt.y;
1333
    y = yp + p->member.row.pt.y;
1334

1335 1336 1337 1338
    ME_ScrollUp(editor, editor->sizeWindow.cy);
    /* Only move the cursor by the amount scrolled. */
    yd = y + editor->vert_si.nPos - yOldScrollPos;
    pLast = p;
1339

1340 1341 1342
    do {
      p = ME_FindItemBack(p, diStartRowOrParagraph);
      if (!p)
1343
        break;
1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354
      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;
    } while(1);
1355

1356
    ME_FindRunInRow(editor, pLast, x, pCursor, &editor->bCaretAtEnd);
1357 1358 1359 1360 1361
  }
  assert(pCursor->pRun);
  assert(pCursor->pRun->type == diRun);
}

1362
static void ME_ArrowPageDown(ME_TextEditor *editor, ME_Cursor *pCursor)
1363
{
1364 1365 1366 1367 1368 1369 1370
  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;
1371

1372
  x = ME_GetXForArrow(editor, pCursor);
1373

1374 1375
  if (editor->vert_si.nPos >= y - editor->sizeWindow.cy)
  {
1376
    ME_SetCursorToEnd(editor, pCursor);
1377 1378 1379 1380
    editor->bCaretAtEnd = FALSE;
  } else {
    ME_DisplayItem *pRun = pCursor->pRun;
    ME_DisplayItem *p;
1381
    int yd, yp;
1382
    int yOldScrollPos = editor->vert_si.nPos;
1383

1384 1385 1386 1387 1388 1389
    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;
1390
    y = yp + p->member.row.pt.y;
1391 1392 1393 1394 1395 1396 1397

    /* 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;
1398
    pLast = p;
1399

1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413
    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;
    } while(1);

1414
    ME_FindRunInRow(editor, pLast, x, pCursor, &editor->bCaretAtEnd);
1415 1416 1417 1418 1419
  }
  assert(pCursor->pRun);
  assert(pCursor->pRun->type == diRun);
}

1420
static void ME_ArrowHome(ME_TextEditor *editor, ME_Cursor *pCursor)
1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432
{
  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;
1433
      assert(pCursor->pPara == ME_GetParagraph(pRun));
1434 1435 1436 1437 1438 1439
      pCursor->nOffset = 0;
    }
  }
  editor->bCaretAtEnd = FALSE;
}

1440
static void ME_ArrowCtrlHome(ME_TextEditor *editor, ME_Cursor *pCursor)
1441
{
1442
  ME_SetCursorToStart(editor, pCursor);
1443
  editor->bCaretAtEnd = FALSE;
1444 1445
}

1446
static void ME_ArrowEnd(ME_TextEditor *editor, ME_Cursor *pCursor)
1447 1448
{
  ME_DisplayItem *pRow;
1449

1450 1451
  if (editor->bCaretAtEnd && !pCursor->nOffset)
    return;
1452

1453 1454 1455 1456 1457 1458
  pRow = ME_FindItemFwd(pCursor->pRun, diStartRowOrParagraphOrEnd);
  assert(pRow);
  if (pRow->type == diStartRow) {
    ME_DisplayItem *pRun = ME_FindItemFwd(pRow, diRun);
    assert(pRun);
    pCursor->pRun = pRun;
1459
    assert(pCursor->pPara == ME_GetParagraph(pCursor->pRun));
1460
    pCursor->nOffset = 0;
1461
    editor->bCaretAtEnd = TRUE;
1462 1463 1464 1465
    return;
  }
  pCursor->pRun = ME_FindItemBack(pRow, diRun);
  assert(pCursor->pRun && pCursor->pRun->member.run.nFlags & MERF_ENDPARA);
1466
  assert(pCursor->pPara == ME_GetParagraph(pCursor->pRun));
1467 1468 1469
  pCursor->nOffset = 0;
  editor->bCaretAtEnd = FALSE;
}
1470

1471
static void ME_ArrowCtrlEnd(ME_TextEditor *editor, ME_Cursor *pCursor)
1472
{
1473
  ME_SetCursorToEnd(editor, pCursor);
1474 1475 1476 1477 1478
  editor->bCaretAtEnd = FALSE;
}

BOOL ME_IsSelection(ME_TextEditor *editor)
{
1479 1480
  return editor->pCursors[0].pRun != editor->pCursors[1].pRun ||
         editor->pCursors[0].nOffset != editor->pCursors[1].nOffset;
1481 1482 1483 1484 1485
}

void ME_DeleteSelection(ME_TextEditor *editor)
{
  int from, to;
1486
  int nStartCursor = ME_GetSelectionOfs(editor, &from, &to);
1487
  int nEndCursor = nStartCursor ^ 1;
1488
  ME_DeleteTextAtCursor(editor, nStartCursor, to - from);
1489
  editor->pCursors[nEndCursor] = editor->pCursors[nStartCursor];
1490 1491
}

1492 1493
ME_Style *ME_GetSelectionInsertStyle(ME_TextEditor *editor)
{
1494
  return ME_GetInsertStyle(editor, 0);
1495 1496
}

1497 1498 1499
void ME_SendSelChange(ME_TextEditor *editor)
{
  SELCHANGE sc;
1500

1501 1502
  if (!(editor->nEventMask & ENM_SELCHANGE))
    return;
1503

1504 1505
  sc.nmhdr.hwndFrom = NULL;
  sc.nmhdr.idFrom = 0;
1506
  sc.nmhdr.code = EN_SELCHANGE;
1507
  ME_GetSelectionOfs(editor, &sc.chrg.cpMin, &sc.chrg.cpMax);
1508 1509 1510
  sc.seltyp = SEL_EMPTY;
  if (sc.chrg.cpMin != sc.chrg.cpMax)
    sc.seltyp |= SEL_TEXT;
1511
  if (sc.chrg.cpMin < sc.chrg.cpMax+1) /* what were RICHEDIT authors thinking ? */
1512
    sc.seltyp |= SEL_MULTICHAR;
1513 1514 1515 1516 1517 1518
  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)
  {
1519 1520
    ME_ClearTempStyle(editor);

1521
    editor->notified_cr = sc.chrg;
1522
    ITextHost_TxNotify(editor->texthost, sc.nmhdr.code, &sc);
1523
  }
1524 1525
}

1526
BOOL
1527
ME_ArrowKey(ME_TextEditor *editor, int nVKey, BOOL extend, BOOL ctrl)
1528 1529 1530 1531
{
  int nCursor = 0;
  ME_Cursor *p = &editor->pCursors[nCursor];
  ME_Cursor tmp_curs = *p;
1532
  BOOL success = FALSE;
1533

1534
  ME_CheckCharOffsets(editor);
1535
  switch(nVKey) {
1536
    case VK_LEFT:
1537
      editor->bCaretAtEnd = FALSE;
1538 1539 1540 1541
      if (ctrl)
        success = ME_MoveCursorWords(editor, &tmp_curs, -1);
      else
        success = ME_MoveCursorChars(editor, &tmp_curs, -1);
1542 1543
      break;
    case VK_RIGHT:
1544
      editor->bCaretAtEnd = FALSE;
1545 1546 1547 1548
      if (ctrl)
        success = ME_MoveCursorWords(editor, &tmp_curs, +1);
      else
        success = ME_MoveCursorChars(editor, &tmp_curs, +1);
1549
      break;
1550
    case VK_UP:
1551 1552
      ME_MoveCursorLines(editor, &tmp_curs, -1);
      break;
1553
    case VK_DOWN:
1554 1555
      ME_MoveCursorLines(editor, &tmp_curs, +1);
      break;
1556
    case VK_PRIOR:
1557 1558
      ME_ArrowPageUp(editor, &tmp_curs);
      break;
1559
    case VK_NEXT:
1560 1561
      ME_ArrowPageDown(editor, &tmp_curs);
      break;
1562
    case VK_HOME: {
1563
      if (ctrl)
1564
        ME_ArrowCtrlHome(editor, &tmp_curs);
1565
      else
1566
        ME_ArrowHome(editor, &tmp_curs);
1567
      editor->bCaretAtEnd = FALSE;
1568
      break;
1569
    }
1570
    case VK_END:
1571
      if (ctrl)
1572
        ME_ArrowCtrlEnd(editor, &tmp_curs);
1573
      else
1574 1575
        ME_ArrowEnd(editor, &tmp_curs);
      break;
1576
  }
1577

1578 1579 1580
  if (!extend)
    editor->pCursors[1] = tmp_curs;
  *p = tmp_curs;
1581

1582 1583
  ME_InvalidateSelection(editor);
  ME_Repaint(editor);
1584
  ITextHost_TxShowCaret(editor->texthost, FALSE);
1585
  ME_EnsureVisible(editor, &tmp_curs);
1586 1587 1588
  ME_ShowCaret(editor);
  ME_SendSelChange(editor);
  return success;
1589
}