/****************************************************************************** * ME_RunOfsFromCharOfs * * Find a run and relative character offset given an absolute character offset * (absolute offset being an offset relative to the start of the document). * Kind of a "global to local" offset conversion. */ void ME_RunOfsFromCharOfs(ME_TextEditor *editor, int nCharOfs, ME_DisplayItem **ppPara, ME_DisplayItem **ppRun, int *pOfs) { ME_DisplayItem *item, *next_item; nCharOfs = max(nCharOfs, 0); nCharOfs = min(nCharOfs, ME_GetTextLength(editor)); /* Find the paragraph at the offset. */ next_item = editor->pBuffer->pFirst->member.para.next_para; do { item = next_item; next_item = item->member.para.next_para; } while (next_item->member.para.nCharOfs <= nCharOfs); assert(item->type == diParagraph); nCharOfs -= item->member.para.nCharOfs; if (ppPara) *ppPara = item; /* Find the run at the offset. */ next_item = ME_FindItemFwd(item, diRun); do { item = next_item; next_item = ME_FindItemFwd(item, diRunOrParagraphOrEnd); } while (next_item->type == diRun && next_item->member.run.nCharOfs <= nCharOfs); assert(item->type == diRun); nCharOfs -= item->member.run.nCharOfs; if (ppRun) *ppRun = item; if (pOfs) *pOfs = nCharOfs; }
/* 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) { ME_DisplayItem *p = editor->pBuffer->pFirst->member.para.next_para; BOOL isExact = TRUE; x -= editor->rcFormat.left; y -= editor->rcFormat.top; if (is_eol) *is_eol = FALSE; /* find paragraph */ for (; p != editor->pBuffer->pLast; p = p->member.para.next_para) { assert(p->type == diParagraph); if (y < p->member.para.pt.y + p->member.para.nHeight) { if (p->member.para.nFlags & MEPF_ROWSTART) p = ME_FindPixelPosInTableRow(x, y, p); y -= p->member.para.pt.y; p = ME_FindItemFwd(p, diStartRow); break; } else if (p->member.para.nFlags & MEPF_ROWSTART) { p = ME_GetTableRowEnd(p); } } /* find row */ for (; p != editor->pBuffer->pLast; ) { ME_DisplayItem *pp; assert(p->type == diStartRow); if (y < p->member.row.pt.y + p->member.row.nHeight) break; pp = ME_FindItemFwd(p, diStartRow); if (!pp) break; p = pp; } 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. */ isExact = FALSE; p = ME_FindItemBack(p, diStartRow); if (!p) p = editor->pBuffer->pLast; } assert( p->type == diStartRow || p == editor->pBuffer->pLast ); if( p->type == diStartRow ) return ME_FindRunInRow( editor, p, x, result, is_eol ) && isExact; result->pRun = ME_FindItemBack(p, diRun); result->pPara = ME_GetParagraph(result->pRun); result->nOffset = 0; assert(result->pRun->member.run.nFlags & MERF_ENDPARA); return FALSE; }
static void ME_SelectByType(ME_TextEditor *editor, ME_SelectionType selectionType) { /* pCursor[0] is the end of the selection * pCursor[1] is the start of the selection (or the position selection anchor) * 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: ME_MoveCursorWords(editor, &editor->pCursors[0], +1); editor->pCursors[1] = editor->pCursors[0]; ME_MoveCursorWords(editor, &editor->pCursors[1], -1); 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) editor->pCursors[0].pRun = ME_FindItemBack(pItem, diRun); else editor->pCursors[0].pRun = ME_FindItemFwd(pItem, diRun); editor->pCursors[0].nOffset = 0; pItem = ME_FindItemBack(pItem, backSearchType); editor->pCursors[1].pRun = ME_FindItemFwd(pItem, diRun); editor->pCursors[1].nOffset = 0; break; } case stDocument: /* Select everything with cursor anchored from the start of the text */ editor->nSelectionType = stDocument; editor->pCursors[1].pRun = ME_FindItemFwd(editor->pBuffer->pFirst, diRun); editor->pCursors[1].nOffset = 0; editor->pCursors[0].pRun = ME_FindItemBack(editor->pBuffer->pLast, diRun); editor->pCursors[0].nOffset = 0; break; default: assert(0); } /* Store the anchor positions for extending the selection. */ editor->pCursors[2] = editor->pCursors[0]; editor->pCursors[3] = editor->pCursors[1]; }
static void ME_ArrowPageUp(ME_TextEditor *editor, ME_Cursor *pCursor) { ME_DisplayItem *p = ME_FindItemFwd(editor->pBuffer->pFirst, diStartRow); if (editor->vert_si.nPos < p->member.row.nHeight) { pCursor->pRun = ME_FindItemFwd(editor->pBuffer->pFirst, diRun); 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; x = ME_GetXForArrow(editor, pCursor); 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; ME_ScrollUp(editor, editor->sizeWindow.cy); /* Only move the cursor by the amount scrolled. */ yd = y + editor->vert_si.nPos - yOldScrollPos; pLast = p; do { p = ME_FindItemBack(p, diStartRowOrParagraph); if (!p) break; 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); pCursor->pRun = ME_FindRunInRow(editor, pLast, x, &pCursor->nOffset, &editor->bCaretAtEnd); } assert(pCursor->pRun); assert(pCursor->pRun->type == diRun); }
ME_DisplayItem* ME_AppendTableRow(ME_TextEditor *editor, ME_DisplayItem *table_row) { WCHAR endl = '\r', tab = '\t'; ME_DisplayItem *run; PARAFORMAT2 *pFmt; int i; assert(table_row); assert(table_row->type == diParagraph); if (!editor->bEmulateVersion10) { /* v4.1 */ ME_DisplayItem *insertedCell, *para, *cell, *prevTableEnd; cell = ME_FindItemFwd(ME_GetTableRowStart(table_row), diCell); prevTableEnd = ME_GetTableRowEnd(table_row); para = prevTableEnd->member.para.next_para; run = ME_FindItemFwd(para, diRun); editor->pCursors[0].pPara = para; editor->pCursors[0].pRun = run; editor->pCursors[0].nOffset = 0; editor->pCursors[1] = editor->pCursors[0]; para = ME_InsertTableRowStartFromCursor(editor); insertedCell = ME_FindItemFwd(para, diCell); /* Copy cell properties */ insertedCell->member.cell.nRightBoundary = cell->member.cell.nRightBoundary; insertedCell->member.cell.border = cell->member.cell.border; while (cell->member.cell.next_cell) { cell = cell->member.cell.next_cell; para = ME_InsertTableCellFromCursor(editor); insertedCell = ME_FindItemBack(para, diCell); /* Copy cell properties */ insertedCell->member.cell.nRightBoundary = cell->member.cell.nRightBoundary; insertedCell->member.cell.border = cell->member.cell.border; }; para = ME_InsertTableRowEndFromCursor(editor); *para->member.para.pFmt = *prevTableEnd->member.para.pFmt; /* return the table row start for the inserted paragraph */ return ME_FindItemFwd(cell, diParagraph)->member.para.next_para; } else { /* v1.0 - 3.0 */ run = ME_FindItemBack(table_row->member.para.next_para, diRun); pFmt = table_row->member.para.pFmt; assert(pFmt->dwMask & PFM_TABLE && pFmt->wEffects & PFE_TABLE); editor->pCursors[0].pPara = table_row; editor->pCursors[0].pRun = run; editor->pCursors[0].nOffset = 0; editor->pCursors[1] = editor->pCursors[0]; ME_InsertTextFromCursor(editor, 0, &endl, 1, run->member.run.style); run = editor->pCursors[0].pRun; for (i = 0; i < pFmt->cTabCount; i++) { ME_InsertTextFromCursor(editor, 0, &tab, 1, run->member.run.style); } return table_row->member.para.next_para; } }
/* 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; if (editor->nSelectionType == stPosition || editor->nSelectionType == stDocument) return; curOfs = ME_GetCursorOfs(&editor->pCursors[0]); anchorStartOfs = ME_GetCursorOfs(&editor->pCursors[3]); anchorEndOfs = ME_GetCursorOfs(&editor->pCursors[2]); 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 */ editor->pCursors[1] = tmp_cursor; if (editor->nSelectionType == stWord) ME_MoveCursorWords(editor, &editor->pCursors[1], -1); else { ME_DisplayItem *pItem; ME_DIType searchType = ((editor->nSelectionType == stLine) ? diStartRowOrParagraph:diParagraph); pItem = ME_FindItemBack(editor->pCursors[1].pRun, searchType); editor->pCursors[1].pRun = ME_FindItemFwd(pItem, diRun); editor->pCursors[1].pPara = ME_GetParagraph(editor->pCursors[1].pRun); editor->pCursors[1].nOffset = 0; } } else if (curOfs >= anchorEndOfs) { /* Extend the right side of selection */ editor->pCursors[0] = tmp_cursor; if (editor->nSelectionType == stWord) ME_MoveCursorWords(editor, &editor->pCursors[0], +1); else { ME_DisplayItem *pItem; ME_DIType searchType = ((editor->nSelectionType == stLine) ? diStartRowOrParagraphOrEnd:diParagraphOrEnd); pItem = ME_FindItemFwd(editor->pCursors[0].pRun, searchType); if (pItem->type == diTextEnd) editor->pCursors[0].pRun = ME_FindItemBack(pItem, diRun); else editor->pCursors[0].pRun = ME_FindItemFwd(pItem, diRun); editor->pCursors[0].pPara = ME_GetParagraph(editor->pCursors[0].pRun); editor->pCursors[0].nOffset = 0; } } }
ME_DisplayItem* ME_InsertTableRowStartAtParagraph(ME_TextEditor *editor, ME_DisplayItem *para) { ME_DisplayItem *prev_para, *end_para; ME_Cursor savedCursor = editor->pCursors[0]; ME_DisplayItem *startRowPara; editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun); editor->pCursors[0].nOffset = 0; editor->pCursors[1] = editor->pCursors[0]; startRowPara = ME_InsertTableRowStartFromCursor(editor); editor->pCursors[0] = savedCursor; editor->pCursors[1] = editor->pCursors[0]; end_para = ME_GetParagraph(editor->pCursors[0].pRun)->member.para.next_para; prev_para = startRowPara->member.para.next_para; para = prev_para->member.para.next_para; while (para != end_para) { para->member.para.pCell = prev_para->member.para.pCell; para->member.para.nFlags |= MEPF_CELL; para->member.para.nFlags &= ~(MEPF_ROWSTART|MEPF_ROWEND); para->member.para.pFmt->dwMask |= PFM_TABLE|PFM_TABLEROWDELIMITER; para->member.para.pFmt->wEffects |= PFE_TABLE; para->member.para.pFmt->wEffects &= ~PFE_TABLEROWDELIMITER; prev_para = para; para = para->member.para.next_para; } return startRowPara; }
static void ME_MoveCursorLines(ME_TextEditor *editor, ME_Cursor *pCursor, int nRelOfs) { ME_DisplayItem *pRun = pCursor->pRun; ME_DisplayItem *pItem; int x = ME_GetXForArrow(editor, pCursor); if (editor->bCaretAtEnd && !pCursor->nOffset) pRun = ME_FindItemBack(pRun, diRun); if (!pRun) return; if (nRelOfs == -1) { /* start of this row */ pItem = ME_FindItemBack(pRun, diStartRow); assert(pItem); /* start of the previous row */ pItem = ME_FindItemBack(pItem, diStartRow); } else { /* start of the next row */ pItem = ME_FindItemFwd(pRun, diStartRow); /* FIXME If diParagraph is before diStartRow, wrap the next paragraph? */ } if (!pItem) { /* row not found - ignore */ return; } pCursor->pRun = ME_FindRunInRow(editor, pItem, x, &pCursor->nOffset, &editor->bCaretAtEnd); assert(pCursor->pRun); assert(pCursor->pRun->type == diRun); }
static void ME_ArrowPageDown(ME_TextEditor *editor, ME_Cursor *pCursor) { 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; x = ME_GetXForArrow(editor, pCursor); if (editor->vert_si.nPos >= y - editor->sizeWindow.cy) { 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; 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; pLast = p; 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); } assert(pCursor->pRun); assert(pCursor->pRun->type == diRun); }
void ME_InvalidateSelection(ME_TextEditor *editor) { ME_DisplayItem *para1, *para2; int nStart, nEnd; int len = ME_GetTextLength(editor); ME_GetSelection(editor, &nStart, &nEnd); /* if both old and new selection are 0-char (= caret only), then there's no (inverted) area to be repainted, neither old nor new */ if (nStart == nEnd && editor->nLastSelStart == editor->nLastSelEnd) return; ME_WrapMarkedParagraphs(editor); ME_GetSelectionParas(editor, ¶1, ¶2); assert(para1->type == diParagraph); assert(para2->type == diParagraph); /* last selection markers aren't always updated, which means they can point past the end of the document */ if (editor->nLastSelStart > len) editor->nLastSelEnd = len; if (editor->nLastSelEnd > len) editor->nLastSelEnd = len; /* if the start part of selection is being expanded or contracted... */ if (nStart < editor->nLastSelStart) { ME_MarkForPainting(editor, para1, ME_FindItemFwd(editor->pLastSelStartPara, diParagraphOrEnd)); } else if (nStart > editor->nLastSelStart) { ME_MarkForPainting(editor, editor->pLastSelStartPara, ME_FindItemFwd(para1, diParagraphOrEnd)); } /* if the end part of selection is being contracted or expanded... */ if (nEnd < editor->nLastSelEnd) { ME_MarkForPainting(editor, para2, ME_FindItemFwd(editor->pLastSelEndPara, diParagraphOrEnd)); } else if (nEnd > editor->nLastSelEnd) { ME_MarkForPainting(editor, editor->pLastSelEndPara, ME_FindItemFwd(para2, diParagraphOrEnd)); } ME_InvalidateMarkedParagraphs(editor); /* remember the last invalidated position */ ME_GetSelection(editor, &editor->nLastSelStart, &editor->nLastSelEnd); ME_GetSelectionParas(editor, &editor->pLastSelStartPara, &editor->pLastSelEndPara); assert(editor->pLastSelStartPara->type == diParagraph); assert(editor->pLastSelEndPara->type == diParagraph); }
static void ME_GetCursorCoordinates(ME_TextEditor *editor, ME_Cursor *pCursor, int *x, int *y, int *height) { ME_DisplayItem *row; ME_DisplayItem *run = pCursor->pRun; ME_DisplayItem *para = pCursor->pPara; ME_DisplayItem *pSizeRun = run; ME_Context c; SIZE sz = {0, 0}; assert(height && x && y); 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, ¶->member.para, &run->member.run, run->member.run.len, row->member.row.nLMargin); } } if (pCursor->nOffset) { sz = ME_GetRunSize(&c, ¶->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; }
static BOOL ME_FindRunInRow(ME_TextEditor *editor, ME_DisplayItem *pRow, int x, ME_Cursor *cursor, BOOL *pbCaretAtEnd) { ME_DisplayItem *pNext, *pLastRun; ME_Row *row = &pRow->member.row; BOOL exact = TRUE; if (x < row->pt.x) { x = row->pt.x; exact = FALSE; } 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; if (x >= run_x && x < run_x+width) { cursor->nOffset = ME_CharFromPoint(editor, x-run_x, &pNext->member.run, TRUE, TRUE); cursor->pRun = pNext; cursor->pPara = ME_GetParagraph( cursor->pRun ); return exact; } pLastRun = pNext; pNext = ME_FindItemFwd(pNext, diRunOrStartRow); } while(pNext && pNext->type == diRun); if ((pLastRun->member.run.nFlags & MERF_ENDPARA) == 0) { cursor->pRun = ME_FindItemFwd(pNext, diRun); if (pbCaretAtEnd) *pbCaretAtEnd = TRUE; } else cursor->pRun = pLastRun; cursor->pPara = ME_GetParagraph( cursor->pRun ); return FALSE; }
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; *pbCaretAtEnd = FALSE; do { int run_x = pNext->member.run.pt.x; int width = pNext->member.run.nWidth; if (x < run_x) { if (pOffset) *pOffset = 0; 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); if (pbCaretAtEnd) *pbCaretAtEnd = 1; if (pOffset) *pOffset = 0; return pNext; } else { if (pbCaretAtEnd) *pbCaretAtEnd = 0; if (pOffset) *pOffset = 0; return pLastRun; } }
static void ME_ArrowCtrlHome(ME_TextEditor *editor, ME_Cursor *pCursor) { ME_DisplayItem *pRow = ME_FindItemBack(pCursor->pRun, diTextStart); if (pRow) { ME_DisplayItem *pRun = ME_FindItemFwd(pRow, diRun); if (pRun) { pCursor->pRun = pRun; pCursor->nOffset = 0; } } }
static void ME_ArrowCtrlEnd(ME_TextEditor *editor, ME_Cursor *pCursor) { ME_DisplayItem *p = ME_FindItemFwd(pCursor->pRun, diTextEnd); assert(p); p = ME_FindItemBack(p, diRun); assert(p); assert(p->member.run.nFlags & MERF_ENDPARA); pCursor->pRun = p; pCursor->nOffset = 0; editor->bCaretAtEnd = FALSE; }
/****************************************************************************** * ME_InsertRunAtCursor * * Inserts a new run with given style, flags and content at a given position, * which is passed as a cursor structure (which consists of a run and * a run-relative character offset). */ ME_DisplayItem * ME_InsertRunAtCursor(ME_TextEditor *editor, ME_Cursor *cursor, ME_Style *style, const WCHAR *str, int len, int flags) { ME_DisplayItem *pDI, *insert_before = cursor->pRun, *prev; if (cursor->nOffset) { if (cursor->nOffset == cursor->pRun->member.run.len) { insert_before = ME_FindItemFwd( cursor->pRun, diRun ); if (!insert_before) insert_before = cursor->pRun; /* Always insert before the final eop run */ } else { ME_SplitRunSimple( editor, cursor ); insert_before = cursor->pRun; } } add_undo_delete_run( editor, insert_before->member.run.para->nCharOfs + insert_before->member.run.nCharOfs, len ); pDI = ME_MakeRun(style, flags); pDI->member.run.nCharOfs = insert_before->member.run.nCharOfs; pDI->member.run.len = len; pDI->member.run.para = insert_before->member.run.para; ME_InsertString( pDI->member.run.para->text, pDI->member.run.nCharOfs, str, len ); ME_InsertBefore( insert_before, pDI ); TRACE("Shift length:%d\n", len); ME_PropagateCharOffset( insert_before, len ); insert_before->member.run.para->nFlags |= MEPF_REWRAP; /* Move any cursors that were at the end of the previous run to the end of the inserted run */ prev = ME_FindItemBack( pDI, 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].pRun = pDI; editor->pCursors[i].nOffset = len; } } } return pDI; }
BOOL ME_WrapMarkedParagraphs(ME_TextEditor *editor) { ME_DisplayItem *item; ME_Context c; BOOL bModified = FALSE; int yStart = -1; ME_InitContext(&c, editor, GetDC(editor->hWnd)); editor->nHeight = 0; item = editor->pBuffer->pFirst->next; while(item != editor->pBuffer->pLast) { BOOL bRedraw = FALSE; assert(item->type == diParagraph); editor->nHeight = max(editor->nHeight, item->member.para.nYPos); if ((item->member.para.nFlags & MEPF_REWRAP) || (item->member.para.nYPos != c.pt.y)) bRedraw = TRUE; item->member.para.nYPos = c.pt.y; ME_WrapTextParagraph(&c, item, editor->selofs); if (bRedraw) { item->member.para.nFlags |= MEPF_REPAINT; if (yStart == -1) yStart = c.pt.y; } bModified = bModified | bRedraw; c.pt.y += item->member.para.nHeight; item = item->member.para.next_para; } editor->sizeWindow.cx = c.rcView.right-c.rcView.left; editor->sizeWindow.cy = c.rcView.bottom-c.rcView.top; editor->nTotalLength = c.pt.y; ME_DestroyContext(&c, editor->hWnd); /* Each paragraph may contain multiple rows, which should be scrollable, even if the containing paragraph has nYPos == 0 */ item = editor->pBuffer->pFirst; while ((item = ME_FindItemFwd(item, diStartRow)) != NULL) { assert(item->type == diStartRow); editor->nHeight = max(editor->nHeight, item->member.row.nYPos); } if (bModified || editor->nTotalLength < editor->nLastTotalLength) ME_InvalidateMarkedParagraphs(editor); return bModified; }
/* Make sure the cursor is not in the hidden table row start paragraph * without a selection. */ void ME_MoveCursorFromTableRowStartParagraph(ME_TextEditor *editor) { ME_DisplayItem *para = ME_GetParagraph(editor->pCursors[0].pRun); if (para == ME_GetParagraph(editor->pCursors[1].pRun) && para->member.para.nFlags & MEPF_ROWSTART) { /* The cursors should not be at the hidden start row paragraph without * a selection, so the cursor is moved into the first cell. */ para = para->member.para.next_para; editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun); editor->pCursors[0].nOffset = 0; editor->pCursors[1] = editor->pCursors[0]; } }
static void ME_ArrowEnd(ME_TextEditor *editor, ME_Cursor *pCursor) { ME_DisplayItem *pRow; if (editor->bCaretAtEnd && !pCursor->nOffset) return; pRow = ME_FindItemFwd(pCursor->pRun, diStartRowOrParagraphOrEnd); assert(pRow); if (pRow->type == diStartRow) { /* FIXME WTF was I thinking about here ? */ ME_DisplayItem *pRun = ME_FindItemFwd(pRow, diRun); assert(pRun); pCursor->pRun = pRun; pCursor->nOffset = 0; editor->bCaretAtEnd = 1; return; } pCursor->pRun = ME_FindItemBack(pRow, diRun); assert(pCursor->pRun && pCursor->pRun->member.run.nFlags & MERF_ENDPARA); pCursor->nOffset = 0; editor->bCaretAtEnd = FALSE; }
/* 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; }
static void ME_ArrowPageUp(ME_TextEditor *editor, ME_Cursor *pCursor) { ME_DisplayItem *pRun = pCursor->pRun; ME_DisplayItem *pLast, *p; int x, y, ys, yd, yp, yprev; ME_Cursor tmp_curs = *pCursor; x = ME_GetXForArrow(editor, pCursor); 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.nYPos; yprev = ys = y = yp + p->member.row.nYPos; yd = y - editor->sizeWindow.cy; pLast = p; do { p = ME_FindItemBack(p, diStartRowOrParagraph); if (!p) break; if (p->type == diParagraph) { /* crossing paragraphs */ if (p->member.para.prev_para == NULL) break; yp = p->member.para.prev_para->member.para.nYPos; continue; } y = yp + p->member.row.nYPos; if (y < yd) break; pLast = p; yprev = y; } while(1); pCursor->pRun = ME_FindRunInRow(editor, pLast, x, &pCursor->nOffset, &editor->bCaretAtEnd); ME_UpdateSelection(editor, &tmp_curs); if (yprev < editor->sizeWindow.cy) { ME_EnsureVisible(editor, ME_FindItemFwd(editor->pBuffer->pFirst, diRun)); ME_Repaint(editor); } else { ME_ScrollUp(editor, ys-yprev); } assert(pCursor->pRun); assert(pCursor->pRun->type == diRun); }
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; } return isExact; }
static ME_DisplayItem* ME_InsertEndParaFromCursor(ME_TextEditor *editor, int nCursor, const WCHAR *eol_str, int eol_len, int paraFlags) { ME_Style *pStyle = ME_GetInsertStyle(editor, nCursor); ME_DisplayItem *tp; ME_Cursor* cursor = &editor->pCursors[nCursor]; if (cursor->nOffset) ME_SplitRunSimple(editor, cursor); tp = ME_SplitParagraph(editor, cursor->pRun, pStyle, eol_str, eol_len, paraFlags); ME_ReleaseStyle(pStyle); cursor->pPara = tp; cursor->pRun = ME_FindItemFwd(tp, diRun); return tp; }
ME_DisplayItem* ME_GetTableRowEnd(ME_DisplayItem *para) { ME_DisplayItem *cell; assert(para); if (para->member.para.nFlags & MEPF_ROWEND) return para; if (para->member.para.nFlags & MEPF_ROWSTART) para = para->member.para.next_para; cell = para->member.para.pCell; assert(cell && cell->type == diCell); while (cell->member.cell.next_cell) cell = cell->member.cell.next_cell; para = ME_FindItemFwd(cell, diParagraph); assert(para && para->member.para.nFlags & MEPF_ROWEND); return para; }
static ME_DisplayItem* ME_InsertEndParaFromCursor(ME_TextEditor *editor, int nCursor, int numCR, int numLF, int paraFlags) { ME_Style *pStyle = ME_GetInsertStyle(editor, nCursor); ME_DisplayItem *tp; ME_Cursor* cursor = &editor->pCursors[nCursor]; if (cursor->nOffset) { ME_SplitRunSimple(editor, cursor->pRun, cursor->nOffset); cursor = &editor->pCursors[nCursor]; } tp = ME_SplitParagraph(editor, cursor->pRun, pStyle, numCR, numLF, paraFlags); cursor->pRun = ME_FindItemFwd(tp, diRun); return tp; }
/****************************************************************************** * ME_CheckCharOffsets * * Checks if editor lists' validity and optionally dumps the document structure */ void ME_CheckCharOffsets(ME_TextEditor *editor) { ME_DisplayItem *p = editor->pBuffer->pFirst; int ofs = 0, ofsp = 0; TRACE_(richedit_check)("Checking begin\n"); if(TRACE_ON(richedit_lists)) { TRACE_(richedit_lists)("---\n"); ME_DumpDocument(editor->pBuffer); } do { p = ME_FindItemFwd(p, diRunOrParagraphOrEnd); switch(p->type) { case diTextEnd: TRACE_(richedit_check)("tend, real ofsp = %d, counted = %d\n", p->member.para.nCharOfs, ofsp+ofs); assert(ofsp+ofs == p->member.para.nCharOfs); TRACE_(richedit_check)("Checking finished\n"); return; case diParagraph: TRACE_(richedit_check)("para, real ofsp = %d, counted = %d\n", p->member.para.nCharOfs, ofsp+ofs); assert(ofsp+ofs == p->member.para.nCharOfs); ofsp = p->member.para.nCharOfs; ofs = 0; break; case diRun: TRACE_(richedit_check)("run, real ofs = %d (+ofsp = %d), counted = %d, len = %d, txt = %s, flags=%08x, fx&mask = %08x\n", p->member.run.nCharOfs, p->member.run.nCharOfs+ofsp, ofsp+ofs, p->member.run.len, debugstr_run( &p->member.run ), p->member.run.nFlags, p->member.run.style->fmt.dwMask & p->member.run.style->fmt.dwEffects); assert(ofs == p->member.run.nCharOfs); assert(p->member.run.len); ofs += p->member.run.len; break; case diCell: TRACE_(richedit_check)("cell\n"); break; default: assert(0); } } while(1); TRACE_(richedit_check)("Checking finished\n"); }
static void ME_ArrowHome(ME_TextEditor *editor, ME_Cursor *pCursor) { ME_DisplayItem *pRow = ME_FindItemBack(pCursor->pRun, diStartRow); ME_WrapMarkedParagraphs(editor); 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; pCursor->nOffset = 0; } } editor->bCaretAtEnd = FALSE; }
static void ME_ArrowHome(ME_TextEditor *editor, ME_Cursor *pCursor) { ME_DisplayItem *pRow = ME_FindItemBack(pCursor->pRun, diStartRow); /* bCaretAtEnd doesn't make sense if the cursor isn't set at the first character of the next row */ assert(!editor->bCaretAtEnd || !pCursor->nOffset); ME_WrapMarkedParagraphs(editor); 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; pCursor->nOffset = 0; } } editor->bCaretAtEnd = FALSE; }
/****************************************************************************** * ME_PropagateCharOffsets * * Shifts (increases or decreases) character offset (relative to beginning of * the document) of the part of the text starting from given place. */ void ME_PropagateCharOffset(ME_DisplayItem *p, int shift) { /* Runs in one paragraph contain character offset relative to their owning * paragraph. If we start the shifting from the run, we need to shift * all the relative offsets until the end of the paragraph */ if (p->type == diRun) /* propagate in all runs in this para */ { TRACE("PropagateCharOffset(%s, %d)\n", debugstr_run( &p->member.run ), shift); do { p->member.run.nCharOfs += shift; assert(p->member.run.nCharOfs >= 0); p = ME_FindItemFwd(p, diRunOrParagraphOrEnd); } while(p->type == diRun); } /* Runs in next paragraphs don't need their offsets updated, because they, * again, those offsets are relative to their respective paragraphs. * Instead of that, we're updating paragraphs' character offsets. */ if (p->type == diParagraph) /* propagate in all next paras */ { do { p->member.para.nCharOfs += shift; assert(p->member.para.nCharOfs >= 0); p = p->member.para.next_para; } while(p->type == diParagraph); } /* diTextEnd also has character offset in it, which makes finding text length * easier. But it needs to be up to date first. */ if (p->type == diTextEnd) { p->member.para.nCharOfs += shift; assert(p->member.para.nCharOfs >= 0); } }
BOOL ME_WrapMarkedParagraphs(ME_TextEditor *editor) { ME_DisplayItem *item; ME_Context c; int totalWidth = 0; ME_DisplayItem *repaint_start = NULL, *repaint_end = NULL; ME_InitContext(&c, editor, ITextHost_TxGetDC(editor->texthost)); c.pt.x = 0; item = editor->pBuffer->pFirst->next; while(item != editor->pBuffer->pLast) { BOOL bRedraw = FALSE; assert(item->type == diParagraph); if ((item->member.para.nFlags & MEPF_REWRAP) || (item->member.para.pt.y != c.pt.y)) bRedraw = TRUE; item->member.para.pt = c.pt; ME_WrapTextParagraph(&c, item); if (bRedraw) ME_MarkRepaintEnd(item, &repaint_start, &repaint_end); if (item->member.para.nFlags & MEPF_ROWSTART) { ME_DisplayItem *cell = ME_FindItemFwd(item, diCell); ME_DisplayItem *endRowPara; int borderWidth = 0; cell->member.cell.pt = c.pt; /* Offset the text by the largest top border width. */ while (cell->member.cell.next_cell) { borderWidth = max(borderWidth, cell->member.cell.border.top.width); cell = cell->member.cell.next_cell; } endRowPara = ME_FindItemFwd(cell, diParagraph); assert(endRowPara->member.para.nFlags & MEPF_ROWEND); if (borderWidth > 0) { borderWidth = max(ME_twips2pointsY(&c, borderWidth), 1); while (cell) { cell->member.cell.yTextOffset = borderWidth; cell = cell->member.cell.prev_cell; } c.pt.y += borderWidth; } if (endRowPara->member.para.pFmt->dxStartIndent > 0) { int dxStartIndent = endRowPara->member.para.pFmt->dxStartIndent; cell = ME_FindItemFwd(item, diCell); cell->member.cell.pt.x += ME_twips2pointsX(&c, dxStartIndent); c.pt.x = cell->member.cell.pt.x; } } else if (item->member.para.nFlags & MEPF_ROWEND) { /* Set all the cells to the height of the largest cell */ ME_DisplayItem *startRowPara; int prevHeight, nHeight, bottomBorder = 0; ME_DisplayItem *cell = ME_FindItemBack(item, diCell); item->member.para.nWidth = cell->member.cell.pt.x + cell->member.cell.nWidth; if (!(item->member.para.next_para->member.para.nFlags & MEPF_ROWSTART)) { /* Last row, the bottom border is added to the height. */ cell = cell->member.cell.prev_cell; while (cell) { bottomBorder = max(bottomBorder, cell->member.cell.border.bottom.width); cell = cell->member.cell.prev_cell; } bottomBorder = ME_twips2pointsY(&c, bottomBorder); cell = ME_FindItemBack(item, diCell); } prevHeight = cell->member.cell.nHeight; nHeight = cell->member.cell.prev_cell->member.cell.nHeight + bottomBorder; cell->member.cell.nHeight = nHeight; item->member.para.nHeight = nHeight; cell = cell->member.cell.prev_cell; cell->member.cell.nHeight = nHeight; while (cell->member.cell.prev_cell) { cell = cell->member.cell.prev_cell; cell->member.cell.nHeight = nHeight; } /* Also set the height of the start row paragraph */ startRowPara = ME_FindItemBack(cell, diParagraph); startRowPara->member.para.nHeight = nHeight; c.pt.x = startRowPara->member.para.pt.x; c.pt.y = cell->member.cell.pt.y + nHeight; if (prevHeight < nHeight) { /* The height of the cells has grown, so invalidate the bottom of * the cells. */ ME_MarkRepaintEnd(item, &repaint_start, &repaint_end); cell = ME_FindItemBack(item, diCell); while (cell) { ME_MarkRepaintEnd(ME_FindItemBack(cell, diParagraph), &repaint_start, &repaint_end); cell = cell->member.cell.prev_cell; } } } else if (item->member.para.pCell && item->member.para.pCell != item->member.para.next_para->member.para.pCell) { /* The next paragraph is in the next cell in the table row. */ ME_Cell *cell = &item->member.para.pCell->member.cell; cell->nHeight = c.pt.y + item->member.para.nHeight - cell->pt.y; /* Propagate the largest height to the end so that it can be easily * sent back to all the cells at the end of the row. */ if (cell->prev_cell) cell->nHeight = max(cell->nHeight, cell->prev_cell->member.cell.nHeight); c.pt.x = cell->pt.x + cell->nWidth; c.pt.y = cell->pt.y; cell->next_cell->member.cell.pt = c.pt; if (!(item->member.para.next_para->member.para.nFlags & MEPF_ROWEND)) c.pt.y += cell->yTextOffset; } else { if (item->member.para.pCell) { /* Next paragraph in the same cell. */ c.pt.x = item->member.para.pCell->member.cell.pt.x; } else { /* Normal paragraph */ c.pt.x = 0; } c.pt.y += item->member.para.nHeight; } totalWidth = max(totalWidth, item->member.para.nWidth); item = item->member.para.next_para; } editor->sizeWindow.cx = c.rcView.right-c.rcView.left; editor->sizeWindow.cy = c.rcView.bottom-c.rcView.top; editor->nTotalLength = c.pt.y; editor->nTotalWidth = totalWidth; editor->pBuffer->pLast->member.para.pt.x = 0; editor->pBuffer->pLast->member.para.pt.y = c.pt.y; ME_DestroyContext(&c); if (repaint_start || editor->nTotalLength < editor->nLastTotalLength) ME_InvalidateParagraphRange(editor, repaint_start, repaint_end); return !!repaint_start; }