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].pPara = ME_GetParagraph(editor->pCursors[0].pRun); editor->pCursors[0].nOffset = 0; pItem = ME_FindItemBack(pItem, backSearchType); editor->pCursors[1].pRun = ME_FindItemFwd(pItem, diRun); editor->pCursors[1].pPara = ME_GetParagraph(editor->pCursors[1].pRun); editor->pCursors[1].nOffset = 0; break; } case stDocument: /* Select everything with cursor anchored from the start of the text */ editor->nSelectionType = stDocument; ME_SetCursorToStart(editor, &editor->pCursors[1]); ME_SetCursorToEnd(editor, &editor->pCursors[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]; }
/* 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]; } }
/* 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; }
/****************************************************************************** * ME_JoinRuns * * Merges two adjacent runs, the one given as a parameter and the next one. */ void ME_JoinRuns(ME_TextEditor *editor, ME_DisplayItem *p) { ME_DisplayItem *pNext = p->next; int i; assert(p->type == diRun && pNext->type == diRun); assert(p->member.run.nCharOfs != -1); ME_GetParagraph(p)->member.para.nFlags |= MEPF_REWRAP; /* Update all cursors so that they don't contain the soon deleted run */ for (i=0; i<editor->nCursors; i++) { if (editor->pCursors[i].pRun == pNext) { editor->pCursors[i].pRun = p; editor->pCursors[i].nOffset += p->member.run.len; } } p->member.run.len += pNext->member.run.len; ME_Remove(pNext); ME_DestroyDisplayItem(pNext); ME_UpdateRunFlags(editor, &p->member.run); if(TRACE_ON(richedit)) { TRACE("Before check after join\n"); ME_CheckCharOffsets(editor); TRACE("After check after join\n"); } }
int ME_GetCursorOfs(ME_TextEditor *editor, int nCursor) { ME_Cursor *pCursor = &editor->pCursors[nCursor]; return ME_GetParagraph(pCursor->pRun)->member.para.nCharOfs + pCursor->pRun->member.run.nCharOfs + pCursor->nOffset; }
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) { ME_SetCursorToEnd(editor, pCursor); editor->bCaretAtEnd = FALSE; } else { ME_DisplayItem *pRun = pCursor->pRun; ME_DisplayItem *p; int yd, yp; 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; 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; } while(1); pCursor->pRun = ME_FindRunInRow(editor, pLast, x, &pCursor->nOffset, &editor->bCaretAtEnd); pCursor->pPara = ME_GetParagraph(pCursor->pRun); } assert(pCursor->pRun); assert(pCursor->pRun->type == diRun); }
/* 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_WrapSizeRun(ME_WrapContext *wc, ME_DisplayItem *p) { /* FIXME compose style (out of character and paragraph styles) here */ ME_UpdateRunFlags(wc->context->editor, &p->member.run); ME_CalcRunExtent(wc->context, &ME_GetParagraph(p)->member.para, wc->nRow ? wc->nLeftMargin : wc->nFirstMargin, &p->member.run); }
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) { ME_SetCursorToStart(editor, pCursor); 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, yd, yp; 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; 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; } while(1); pCursor->pRun = ME_FindRunInRow(editor, pLast, x, &pCursor->nOffset, &editor->bCaretAtEnd); pCursor->pPara = ME_GetParagraph(pCursor->pRun); } assert(pCursor->pRun); assert(pCursor->pRun->type == diRun); }
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; }
BOOL ME_IsInTable(ME_DisplayItem *pItem) { PARAFORMAT2 *pFmt; if (!pItem) return FALSE; if (pItem->type == diRun) pItem = ME_GetParagraph(pItem); if (pItem->type != diParagraph) return FALSE; pFmt = pItem->member.para.pFmt; return pFmt->dwMask & PFM_TABLE && pFmt->wEffects & PFE_TABLE; }
void ME_GetSelectionParas(ME_TextEditor *editor, ME_DisplayItem **para, ME_DisplayItem **para_end) { ME_Cursor *pEndCursor = &editor->pCursors[1]; *para = ME_GetParagraph(editor->pCursors[0].pRun); *para_end = ME_GetParagraph(editor->pCursors[1].pRun); if ((*para_end)->member.para.nCharOfs < (*para)->member.para.nCharOfs) { ME_DisplayItem *tmp = *para; *para = *para_end; *para_end = tmp; pEndCursor = &editor->pCursors[0]; } /* selection consists of chars from nFrom up to nTo-1 */ if ((*para_end)->member.para.nCharOfs > (*para)->member.para.nCharOfs) { if (!pEndCursor->nOffset) { *para_end = ME_GetParagraph(ME_FindItemBack(pEndCursor->pRun, diRun)); } } }
int ME_CharFromPos(ME_TextEditor *editor, int x, int y) { ME_Cursor cursor; RECT rc; GetClientRect(editor->hWnd, &rc); if (x < 0 || y < 0 || x >= rc.right || y >= rc.bottom) return -1; y += ME_GetYScrollPos(editor); ME_FindPixelPos(editor, x, y, &cursor, NULL); return (ME_GetParagraph(cursor.pRun)->member.para.nCharOfs + cursor.pRun->member.run.nCharOfs + cursor.nOffset); }
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) { ME_DisplayItem *pRun = ME_FindItemFwd(pRow, diRun); assert(pRun); pCursor->pRun = pRun; assert(pCursor->pPara == ME_GetParagraph(pCursor->pRun)); pCursor->nOffset = 0; editor->bCaretAtEnd = TRUE; return; } pCursor->pRun = ME_FindItemBack(pRow, diRun); assert(pCursor->pRun && pCursor->pRun->member.run.nFlags & MERF_ENDPARA); assert(pCursor->pPara == ME_GetParagraph(pCursor->pRun)); pCursor->nOffset = 0; editor->bCaretAtEnd = FALSE; }
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 (result->nOffset == found->member.run.len && rx) { result->pRun = ME_FindItemFwd(result->pRun, diRun); result->nOffset = 0; } result->pPara = ME_GetParagraph(result->pRun); return isExact; }
/* Returns the character offset closest to the pixel position * * x & y are pixel positions in client coordinates. * * isExact will be set to TRUE if the run is directly under the pixel * position, FALSE if it not, unless isExact is set to NULL. */ int ME_CharFromPos(ME_TextEditor *editor, int x, int y, BOOL *isExact) { ME_Cursor cursor; RECT rc; BOOL bResult; GetClientRect(editor->hWnd, &rc); if (x < 0 || y < 0 || x >= rc.right || y >= rc.bottom) { if (isExact) *isExact = FALSE; return -1; } x += editor->horz_si.nPos; y += editor->vert_si.nPos; bResult = ME_FindPixelPos(editor, x, y, &cursor, NULL); if (isExact) *isExact = bResult; return (ME_GetParagraph(cursor.pRun)->member.para.nCharOfs + cursor.pRun->member.run.nCharOfs + cursor.nOffset); }
static void ME_ArrowHome(ME_TextEditor *editor, ME_Cursor *pCursor) { 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; assert(pCursor->pPara == ME_GetParagraph(pRun)); pCursor->nOffset = 0; } } editor->bCaretAtEnd = FALSE; }
static void ME_InsertRowStart(ME_WrapContext *wc, const ME_DisplayItem *pEnd) { ME_DisplayItem *p, *row, *para; int ascent = 0, descent = 0, width=0, shift = 0, align = 0; /* wrap text */ para = ME_GetParagraph(wc->pRowStart); for (p = wc->pRowStart; p!=pEnd; p = p->next) { /* ENDPARA run shouldn't affect row height, except if it's the only run in the paragraph */ if (p->type==diRun && ((p==wc->pRowStart) || !(p->member.run.nFlags & MERF_ENDPARA))) { /* FIXME add more run types */ if (p->member.run.nAscent>ascent) ascent = p->member.run.nAscent; if (p->member.run.nDescent>descent) descent = p->member.run.nDescent; if (!(p->member.run.nFlags & (MERF_ENDPARA|MERF_SKIPPED))) width += p->member.run.nWidth; } } row = ME_MakeRow(ascent+descent, ascent, width); row->member.row.nYPos = wc->pt.y; row->member.row.nLMargin = (!wc->nRow ? wc->nFirstMargin : wc->nLeftMargin); row->member.row.nRMargin = wc->nRightMargin; assert(para->member.para.pFmt->dwMask & PFM_ALIGNMENT); align = para->member.para.pFmt->wAlignment; if (align == PFA_CENTER) shift = (wc->nAvailWidth-width)/2; if (align == PFA_RIGHT) shift = wc->nAvailWidth-width; for (p = wc->pRowStart; p!=pEnd; p = p->next) { if (p->type==diRun) { /* FIXME add more run types */ p->member.run.pt.x += row->member.row.nLMargin+shift; } } ME_InsertBefore(wc->pRowStart, row); wc->nRow++; wc->pt.y += ascent+descent; ME_BeginRow(wc); }
BOOL ME_InternalDeleteText(ME_TextEditor *editor, int nOfs, int nChars, BOOL bForce) { ME_Cursor c; int shift = 0; int totalChars = nChars; ME_DisplayItem *start_para; { /* Prevent deletion past last end of paragraph run. */ ME_DisplayItem *pTextEnd = editor->pBuffer->pLast; int nMaxChars = pTextEnd->member.para.prev_para->member.para.nCharOfs; nMaxChars += ME_FindItemBack(pTextEnd, diRun)->member.run.nCharOfs; nMaxChars -= nOfs; nChars = min(nChars, nMaxChars); } ME_CursorFromCharOfs(editor, nOfs, &c); start_para = ME_GetParagraph(c.pRun); if (!bForce) { ME_ProtectPartialTableDeletion(editor, nOfs, &nChars); if (nChars == 0) return FALSE; } while(nChars > 0) { ME_Run *run; ME_CursorFromCharOfs(editor, nOfs+nChars, &c); if (!c.nOffset && nOfs+nChars == (c.pRun->member.run.nCharOfs + ME_GetParagraph(c.pRun)->member.para.nCharOfs)) { /* We aren't deleting anything in this run, so we will go back to the * last run we are deleting text in. */ c.pRun = ME_FindItemBack(c.pRun, diRun); if (c.pRun->member.run.nFlags & MERF_ENDPARA) c.nOffset = c.pRun->member.run.nCR + c.pRun->member.run.nLF; else c.nOffset = c.pRun->member.run.strText->nLen; } run = &c.pRun->member.run; if (run->nFlags & MERF_ENDPARA) { int eollen = run->nCR + run->nLF; BOOL keepFirstParaFormat; if (!ME_FindItemFwd(c.pRun, diParagraph)) { return TRUE; } keepFirstParaFormat = (totalChars == nChars && nChars <= eollen && run->nCharOfs); 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; } } ME_JoinParagraphs(editor, ME_GetParagraph(c.pRun), keepFirstParaFormat); /* ME_SkipAndPropagateCharOffset(p->pRun, shift); */ ME_CheckCharOffsets(editor); nChars -= (eollen < nChars) ? eollen : nChars; continue; } else { ME_Cursor cursor; int nCharsToDelete = min(nChars, c.nOffset); int i; c.nOffset -= nCharsToDelete; ME_FindItemBack(c.pRun, diParagraph)->member.para.nFlags |= MEPF_REWRAP; cursor = c; /* nChars is the number of characters that should be deleted from the PRECEDING runs (these BEFORE cursor.pRun) nCharsToDelete is a number of chars to delete from THIS run */ nChars -= nCharsToDelete; shift -= nCharsToDelete; TRACE("Deleting %d (remaning %d) chars at %d in '%s' (%d)\n", nCharsToDelete, nChars, c.nOffset, debugstr_w(run->strText->szData), run->strText->nLen); if (!c.nOffset && ME_StrVLen(run->strText) == nCharsToDelete) { /* undo = reinsert whole run */ /* nOfs is a character offset (from the start of the document to the current (deleted) run */ ME_UndoItem *pUndo = ME_AddUndoItem(editor, diUndoInsertRun, c.pRun); if (pUndo) pUndo->di.member.run.nCharOfs = nOfs+nChars; } else { /* undo = reinsert partial run */ ME_UndoItem *pUndo = ME_AddUndoItem(editor, diUndoInsertRun, c.pRun); if (pUndo) { ME_DestroyString(pUndo->di.member.run.strText); pUndo->di.member.run.nCharOfs = nOfs+nChars; pUndo->di.member.run.strText = ME_MakeStringN(run->strText->szData+c.nOffset, nCharsToDelete); } } TRACE("Post deletion string: %s (%d)\n", debugstr_w(run->strText->szData), run->strText->nLen); TRACE("Shift value: %d\n", shift); ME_StrDeleteV(run->strText, c.nOffset, nCharsToDelete); /* update cursors (including c) */ for (i=-1; i<editor->nCursors; i++) { ME_Cursor *pThisCur = editor->pCursors + i; 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); assert(pThisCur->nOffset <= ME_StrVLen(run->strText)); } if (pThisCur->nOffset == ME_StrVLen(run->strText)) { pThisCur->pRun = ME_FindItemFwd(pThisCur->pRun, diRunOrParagraphOrEnd); assert(pThisCur->pRun->type == diRun); pThisCur->nOffset = 0; } } } /* c = updated data now */ if (c.pRun == cursor.pRun) ME_SkipAndPropagateCharOffset(c.pRun, shift); else ME_PropagateCharOffset(c.pRun, shift); if (!ME_StrVLen(cursor.pRun->member.run.strText)) { TRACE("Removing useless run\n"); ME_Remove(cursor.pRun); ME_DestroyDisplayItem(cursor.pRun); } shift = 0; /* ME_CheckCharOffsets(editor); */ continue; } } return TRUE; }
static void ME_PlayUndoItem(ME_TextEditor *editor, ME_DisplayItem *pItem) { ME_UndoItem *pUItem = (ME_UndoItem *)pItem; if (editor->nUndoMode == umIgnore) return; TRACE("Playing undo/redo item, id=%s\n", ME_GetDITypeName(pItem->type)); switch(pItem->type) { case diUndoPotentialEndTransaction: case diUndoEndTransaction: assert(0); case diUndoSetParagraphFormat: { ME_Cursor tmp; ME_DisplayItem *para; ME_CursorFromCharOfs(editor, pItem->member.para.nCharOfs, &tmp); para = ME_FindItemBack(tmp.pRun, diParagraph); ME_AddUndoItem(editor, diUndoSetParagraphFormat, para); *para->member.para.pFmt = *pItem->member.para.pFmt; para->member.para.border = pItem->member.para.border; break; } case diUndoSetCharFormat: { ME_SetCharFormat(editor, pUItem->nStart, pUItem->nLen, &pItem->member.ustyle->fmt); break; } case diUndoInsertRun: { ME_InsertRun(editor, pItem->member.run.nCharOfs, pItem); break; } case diUndoDeleteRun: { ME_InternalDeleteText(editor, pUItem->nStart, pUItem->nLen, TRUE); break; } case diUndoJoinParagraphs: { ME_Cursor tmp; ME_CursorFromCharOfs(editor, pUItem->nStart, &tmp); /* the only thing that's needed is paragraph offset, so no need to split runs */ ME_JoinParagraphs(editor, ME_GetParagraph(tmp.pRun), TRUE); break; } case diUndoSplitParagraph: { ME_Cursor tmp; ME_DisplayItem *this_para, *new_para; BOOL bFixRowStart; int paraFlags = pItem->member.para.nFlags & (MEPF_ROWSTART|MEPF_CELL|MEPF_ROWEND); ME_CursorFromCharOfs(editor, pUItem->nStart, &tmp); if (tmp.nOffset) tmp.pRun = ME_SplitRunSimple(editor, tmp.pRun, tmp.nOffset); assert(pUItem->nCR >= 0); assert(pUItem->nLF >= 0); this_para = ME_GetParagraph(tmp.pRun); bFixRowStart = this_para->member.para.nFlags & MEPF_ROWSTART; if (bFixRowStart) { /* Re-insert the paragraph before the table, making sure the nFlag value * is correct. */ this_para->member.para.nFlags &= ~MEPF_ROWSTART; } new_para = ME_SplitParagraph(editor, tmp.pRun, tmp.pRun->member.run.style, pUItem->nCR, pUItem->nLF, paraFlags); if (bFixRowStart) new_para->member.para.nFlags |= MEPF_ROWSTART; assert(pItem->member.para.pFmt->cbSize == sizeof(PARAFORMAT2)); *new_para->member.para.pFmt = *pItem->member.para.pFmt; new_para->member.para.border = pItem->member.para.border; if (pItem->member.para.pCell) { ME_DisplayItem *pItemCell, *pCell; pItemCell = pItem->member.para.pCell; pCell = new_para->member.para.pCell; pCell->member.cell.nRightBoundary = pItemCell->member.cell.nRightBoundary; pCell->member.cell.border = pItemCell->member.cell.border; } break; } default: assert(0 == "PlayUndoItem, unexpected type"); } }
static void ME_MoveCursorLines(ME_TextEditor *editor, ME_Cursor *pCursor, int nRelOfs) { ME_DisplayItem *pRun = pCursor->pRun; ME_DisplayItem *pItem, *pOldPara, *pNewPara; int x = ME_GetXForArrow(editor, pCursor); if (editor->bCaretAtEnd && !pCursor->nOffset) pRun = ME_FindItemBack(pRun, diRun); if (!pRun) return; pOldPara = ME_GetParagraph(pRun); if (nRelOfs == -1) { /* start of this row */ pItem = ME_FindItemBack(pRun, diStartRow); assert(pItem); /* start of the previous row */ pItem = ME_FindItemBack(pItem, diStartRow); 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); } } else { /* start of the next row */ pItem = ME_FindItemFwd(pRun, diStartRow); if (!pItem) return; /* row not found - ignore */ /* FIXME If diParagraph is before diStartRow, wrap the next paragraph? */ 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); } } 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); }
void ME_GetCursorCoordinates(ME_TextEditor *editor, ME_Cursor *pCursor, int *x, int *y, int *height) { ME_DisplayItem *pCursorRun = pCursor->pRun; ME_DisplayItem *pSizeRun = pCursor->pRun; assert(height && x && y); assert(!(ME_GetParagraph(pCursorRun)->member.para.nFlags & MEPF_REWRAP)); assert(pCursor->pRun); assert(pCursor->pRun->type == diRun); if (pCursorRun->type == diRun) { ME_DisplayItem *row = ME_FindItemBack(pCursorRun, diStartRowOrParagraph); if (row) { HDC hDC = GetDC(editor->hWnd); ME_Context c; ME_DisplayItem *run = pCursorRun; ME_DisplayItem *para = NULL; SIZE sz = {0, 0}; ME_InitContext(&c, editor, hDC); if (!pCursor->nOffset) { ME_DisplayItem *prev = ME_FindItemBack(pCursorRun, diRunOrParagraph); assert(prev); if (prev->type == diRun) pSizeRun = prev; } assert(row->type == diStartRow); /* paragraph -> run without start row ?*/ para = ME_FindItemBack(row, diParagraph); assert(para); assert(para->type == diParagraph); 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, ME_StrLen(run->member.run.strText), 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, editor->hWnd); return; } } *height = 10; /* FIXME use global font */ *x = 0; *y = 0; }
/* Table rows should either be deleted completely or not at all. */ void ME_ProtectPartialTableDeletion(ME_TextEditor *editor, int nOfs,int *nChars) { ME_Cursor c, c2; ME_DisplayItem *this_para, *end_para; ME_CursorFromCharOfs(editor, nOfs, &c); this_para = ME_GetParagraph(c.pRun); ME_CursorFromCharOfs(editor, nOfs + *nChars, &c2); end_para = ME_GetParagraph(c2.pRun); if (c2.pRun->member.run.nFlags & MERF_ENDPARA) { /* End offset might be in the middle of the end paragraph run. * If this is the case, then we need to use the next paragraph as the last * paragraphs. */ int remaining = nOfs + *nChars - c2.pRun->member.run.nCharOfs - end_para->member.para.nCharOfs; if (remaining) { assert(remaining < c2.pRun->member.run.nCR + c2.pRun->member.run.nLF); end_para = end_para->member.para.next_para; } } if (!editor->bEmulateVersion10) { /* v4.1 */ if (this_para->member.para.pCell != end_para->member.para.pCell || ((this_para->member.para.nFlags|end_para->member.para.nFlags) & (MEPF_ROWSTART|MEPF_ROWEND))) { while (this_para != end_para) { ME_DisplayItem *next_para = this_para->member.para.next_para; BOOL bTruancateDeletion = FALSE; if (this_para->member.para.nFlags & MEPF_ROWSTART) { /* The following while loop assumes that next_para is MEPF_ROWSTART, * so moving back one paragraph let's it be processed as the start * of the row. */ next_para = this_para; this_para = this_para->member.para.prev_para; } else if (next_para->member.para.pCell != this_para->member.para.pCell || this_para->member.para.nFlags & MEPF_ROWEND) { /* Start of the deletion from after the start of the table row. */ bTruancateDeletion = TRUE; } while (!bTruancateDeletion && next_para->member.para.nFlags & MEPF_ROWSTART) { next_para = ME_GetTableRowEnd(next_para)->member.para.next_para; if (next_para->member.para.nCharOfs > nOfs + *nChars) { /* End of deletion is not past the end of the table row. */ next_para = this_para->member.para.next_para; /* Delete the end paragraph preceding the table row if the * preceding table row will be empty. */ if (this_para->member.para.nCharOfs >= nOfs) { next_para = next_para->member.para.next_para; } bTruancateDeletion = TRUE; } else { this_para = next_para->member.para.prev_para; } } if (bTruancateDeletion) { ME_Run *end_run = &ME_FindItemBack(next_para, diRun)->member.run; int nCharsNew = (next_para->member.para.nCharOfs - nOfs - end_run->nCR - end_run->nLF); nCharsNew = max(nCharsNew, 0); assert(nCharsNew <= *nChars); *nChars = nCharsNew; break; } this_para = next_para; } } } else { /* v1.0 - 3.0 */ ME_DisplayItem *pRun; int nCharsToBoundary; if ((this_para->member.para.nCharOfs != nOfs || this_para == end_para) && this_para->member.para.pFmt->dwMask & PFM_TABLE && this_para->member.para.pFmt->wEffects & PFE_TABLE) { pRun = c.pRun; /* Find the next tab or end paragraph to use as a delete boundary */ while (!(pRun->member.run.nFlags & (MERF_TAB|MERF_ENDPARA))) pRun = ME_FindItemFwd(pRun, diRun); nCharsToBoundary = pRun->member.run.nCharOfs - c.pRun->member.run.nCharOfs - c.nOffset; *nChars = min(*nChars, nCharsToBoundary); } else if (end_para->member.para.pFmt->dwMask & PFM_TABLE && end_para->member.para.pFmt->wEffects & PFE_TABLE) { /* The deletion starts from before the row, so don't join it with * previous non-empty paragraphs. */ pRun = NULL; if (nOfs > this_para->member.para.nCharOfs) pRun = ME_FindItemBack(end_para, diRun); if (!pRun) pRun = ME_FindItemFwd(end_para, diRun); if (pRun) { nCharsToBoundary = ME_GetParagraph(pRun)->member.para.nCharOfs + pRun->member.run.nCharOfs - nOfs; if (nCharsToBoundary >= 0) *nChars = min(*nChars, nCharsToBoundary); } } if (*nChars < 0) nChars = 0; } }
/* split paragraph at the beginning of the run */ ME_DisplayItem *ME_SplitParagraph(ME_TextEditor *editor, ME_DisplayItem *run, ME_Style *style, const WCHAR *eol_str, int eol_len, int paraFlags) { ME_DisplayItem *next_para = NULL; ME_DisplayItem *run_para = NULL; ME_DisplayItem *new_para = make_para(editor); ME_DisplayItem *end_run; int ofs, i; ME_DisplayItem *pp; int run_flags = MERF_ENDPARA; if (!editor->bEmulateVersion10) { /* v4.1 */ /* At most 1 of MEPF_CELL, MEPF_ROWSTART, or MEPF_ROWEND should be set. */ assert(!(paraFlags & ~(MEPF_CELL|MEPF_ROWSTART|MEPF_ROWEND))); assert(!(paraFlags & (paraFlags-1))); if (paraFlags == MEPF_CELL) run_flags |= MERF_ENDCELL; else if (paraFlags == MEPF_ROWSTART) run_flags |= MERF_TABLESTART|MERF_HIDDEN; } else { /* v1.0 - v3.0 */ assert(!(paraFlags & (MEPF_CELL|MEPF_ROWSTART|MEPF_ROWEND))); } assert(run->type == diRun); run_para = ME_GetParagraph(run); assert(run_para->member.para.pFmt->cbSize == sizeof(PARAFORMAT2)); new_para->member.para.text = ME_VSplitString( run_para->member.para.text, run->member.run.nCharOfs ); end_run = ME_MakeRun(style, run_flags); ofs = end_run->member.run.nCharOfs = run->member.run.nCharOfs; end_run->member.run.len = eol_len; end_run->member.run.para = run->member.run.para; ME_AppendString( run_para->member.para.text, eol_str, eol_len ); next_para = run_para->member.para.next_para; assert(next_para == ME_FindItemFwd(run_para, diParagraphOrEnd)); add_undo_join_paras( editor, run_para->member.para.nCharOfs + ofs ); /* Update selection cursors to point to the correct paragraph. */ for (i = 0; i < editor->nCursors; i++) { if (editor->pCursors[i].pPara == run_para && run->member.run.nCharOfs <= editor->pCursors[i].pRun->member.run.nCharOfs) { editor->pCursors[i].pPara = new_para; } } /* the new paragraph will have a different starting offset, so let's update its runs */ pp = run; while(pp->type == diRun) { pp->member.run.nCharOfs -= ofs; pp->member.run.para = &new_para->member.para; pp = ME_FindItemFwd(pp, diRunOrParagraphOrEnd); } new_para->member.para.nCharOfs = run_para->member.para.nCharOfs + ofs; new_para->member.para.nCharOfs += eol_len; new_para->member.para.nFlags = MEPF_REWRAP; /* FIXME initialize format style and call ME_SetParaFormat blah blah */ *new_para->member.para.pFmt = *run_para->member.para.pFmt; new_para->member.para.border = run_para->member.para.border; /* insert paragraph into paragraph double linked list */ new_para->member.para.prev_para = run_para; new_para->member.para.next_para = next_para; run_para->member.para.next_para = new_para; next_para->member.para.prev_para = new_para; /* insert end run of the old paragraph, and new paragraph, into DI double linked list */ ME_InsertBefore(run, new_para); ME_InsertBefore(new_para, end_run); if (!editor->bEmulateVersion10) { /* v4.1 */ if (paraFlags & (MEPF_ROWSTART|MEPF_CELL)) { ME_DisplayItem *cell = ME_MakeDI(diCell); ME_InsertBefore(new_para, cell); new_para->member.para.pCell = cell; cell->member.cell.next_cell = NULL; if (paraFlags & MEPF_ROWSTART) { run_para->member.para.nFlags |= MEPF_ROWSTART; cell->member.cell.prev_cell = NULL; cell->member.cell.parent_cell = run_para->member.para.pCell; if (run_para->member.para.pCell) cell->member.cell.nNestingLevel = run_para->member.para.pCell->member.cell.nNestingLevel + 1; else cell->member.cell.nNestingLevel = 1; } else { cell->member.cell.prev_cell = run_para->member.para.pCell; assert(cell->member.cell.prev_cell); cell->member.cell.prev_cell->member.cell.next_cell = cell; assert(run_para->member.para.nFlags & MEPF_CELL); assert(!(run_para->member.para.nFlags & MEPF_ROWSTART)); cell->member.cell.nNestingLevel = cell->member.cell.prev_cell->member.cell.nNestingLevel; cell->member.cell.parent_cell = cell->member.cell.prev_cell->member.cell.parent_cell; } } else if (paraFlags & MEPF_ROWEND) { run_para->member.para.nFlags |= MEPF_ROWEND; run_para->member.para.pCell = run_para->member.para.pCell->member.cell.parent_cell; new_para->member.para.pCell = run_para->member.para.pCell; assert(run_para->member.para.prev_para->member.para.nFlags & MEPF_CELL); assert(!(run_para->member.para.prev_para->member.para.nFlags & MEPF_ROWSTART)); if (new_para->member.para.pCell != new_para->member.para.next_para->member.para.pCell && new_para->member.para.next_para->member.para.pCell && !new_para->member.para.next_para->member.para.pCell->member.cell.prev_cell) { /* Row starts just after the row that was ended. */ new_para->member.para.nFlags |= MEPF_ROWSTART; } } else { new_para->member.para.pCell = run_para->member.para.pCell; } ME_UpdateTableFlags(run_para); ME_UpdateTableFlags(new_para); } /* force rewrap of the */ run_para->member.para.prev_para->member.para.nFlags |= MEPF_REWRAP; new_para->member.para.prev_para->member.para.nFlags |= MEPF_REWRAP; /* we've added the end run, so we need to modify nCharOfs in the next paragraphs */ ME_PropagateCharOffset(next_para, eol_len); editor->nParagraphs++; return new_para; }
/* Selects the next table cell or appends a new table row if at end of table */ static void ME_SelectOrInsertNextCell(ME_TextEditor *editor, ME_DisplayItem *run) { ME_DisplayItem *para = ME_GetParagraph(run); int i; assert(run && run->type == diRun); assert(ME_IsInTable(run)); if (!editor->bEmulateVersion10) { /* v4.1 */ ME_DisplayItem *cell; /* Get the initial cell */ if (para->member.para.nFlags & MEPF_ROWSTART) { cell = para->member.para.next_para->member.para.pCell; } else if (para->member.para.nFlags & MEPF_ROWEND) { cell = para->member.para.prev_para->member.para.pCell; } else { cell = para->member.para.pCell; } assert(cell); /* Get the next cell. */ if (cell->member.cell.next_cell && cell->member.cell.next_cell->member.cell.next_cell) { cell = cell->member.cell.next_cell; } else { para = ME_GetTableRowEnd(ME_FindItemFwd(cell, diParagraph)); para = para->member.para.next_para; assert(para); if (para->member.para.nFlags & MEPF_ROWSTART) { cell = para->member.para.next_para->member.para.pCell; } else { /* Insert row */ para = para->member.para.prev_para; para = ME_AppendTableRow(editor, ME_GetTableRowStart(para)); /* Put cursor at the start of the new table row */ 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]; ME_WrapMarkedParagraphs(editor); return; } } /* Select cell */ editor->pCursors[1].pRun = ME_FindItemFwd(cell, diRun); editor->pCursors[1].nOffset = 0; assert(editor->pCursors[0].pRun); cell = cell->member.cell.next_cell; editor->pCursors[0].pRun = ME_FindItemBack(cell, diRun); editor->pCursors[0].nOffset = 0; assert(editor->pCursors[1].pRun); } else { /* v1.0 - 3.0 */ if (run->member.run.nFlags & MERF_ENDPARA && ME_IsInTable(ME_FindItemFwd(run, diParagraphOrEnd))) { run = ME_FindItemFwd(run, diRun); assert(run); } for (i = 0; i < 2; i++) { while (!(run->member.run.nFlags & MERF_TAB)) { run = ME_FindItemFwd(run, diRunOrParagraphOrEnd); if (run->type != diRun) { para = run; if (ME_IsInTable(para)) { run = ME_FindItemFwd(para, diRun); assert(run); editor->pCursors[0].pRun = run; editor->pCursors[0].nOffset = 0; i = 1; } else { /* Insert table row */ para = ME_AppendTableRow(editor, para->member.para.prev_para); /* Put cursor at the start of the new table row */ editor->pCursors[0].pRun = ME_FindItemFwd(para, diRun); editor->pCursors[0].nOffset = 0; editor->pCursors[1] = editor->pCursors[0]; ME_WrapMarkedParagraphs(editor); return; } } } if (i == 0) run = ME_FindItemFwd(run, diRun); editor->pCursors[i].pRun = run; editor->pCursors[i].nOffset = 0; } } }
/* 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; ME_DisplayItem *last = NULL; int rx = 0; BOOL isExact = TRUE; x -= editor->rcFormat.left; y -= editor->rcFormat.top; if (is_eol) *is_eol = 0; /* 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) { p = ME_FindItemFwd(p, diRun); break; } pp = ME_FindItemFwd(p, diStartRowOrParagraphOrEnd); if (pp->type != diStartRow) { p = ME_FindItemFwd(p, diRun); 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 != NULL){ p = ME_FindItemFwd(p, diRun); } else { p = editor->pBuffer->pLast; } } for (; p != editor->pBuffer->pLast; p = p->next) { switch (p->type) { case diRun: rx = x - p->member.run.pt.x; if (rx < p->member.run.nWidth) return ME_ReturnFoundPos(editor, p, result, rx, isExact); break; case diStartRow: isExact = FALSE; p = ME_FindItemFwd(p, diRun); if (is_eol) *is_eol = 1; rx = 0; /* FIXME not sure */ return ME_ReturnFoundPos(editor, p, result, rx, isExact); case diCell: case diParagraph: case diTextEnd: isExact = FALSE; rx = 0; /* FIXME not sure */ p = last; return ME_ReturnFoundPos(editor, p, result, rx, isExact); default: assert(0); } last = p; } 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 ME_DisplayItem *ME_WrapHandleRun(ME_WrapContext *wc, ME_DisplayItem *p) { ME_DisplayItem *pp; ME_Run *run; int len; assert(p->type == diRun); if (!wc->pRowStart) wc->pRowStart = p; run = &p->member.run; run->pt.x = wc->pt.x; run->pt.y = wc->pt.y; ME_WrapSizeRun(wc, p); len = ME_StrVLen(run->strText); if (wc->bOverflown) /* just skipping final whitespaces */ { if (run->nFlags & (MERF_WHITESPACE|MERF_TAB)) { p->member.run.nFlags |= MERF_SKIPPED; /* wc->pt.x += run->nWidth; */ /* skip runs consisting of only whitespaces */ return p->next; } if (run->nFlags & MERF_STARTWHITE) { /* try to split the run at the first non-white char */ int black; black = ME_FindNonWhitespaceV(run->strText, 0); if (black) { wc->bOverflown = FALSE; pp = ME_SplitRun(wc, p, black); p->member.run.nFlags |= MERF_SKIPPED; ME_InsertRowStart(wc, pp); return pp; } } /* black run: the row goes from pRowStart to the previous run */ ME_InsertRowStart(wc, p); return p; } /* simply end the current row and move on to next one */ if (run->nFlags & MERF_ENDROW) { p = p->next; ME_InsertRowStart(wc, p); return p; } /* we're not at the end of the row */ if (run->nFlags & MERF_TAB) { /* force recomputation of tabs' size as it depends on position */ ME_CalcRunExtent(wc->context, &ME_GetParagraph(p)->member.para, wc->nRow ? wc->nLeftMargin : wc->nFirstMargin, run); } /* will current run fit? */ if (wc->pt.x + run->nWidth > wc->nAvailWidth) { int loc = wc->nAvailWidth - wc->pt.x; /* total white run ? */ if (run->nFlags & MERF_WHITESPACE) { /* let the overflow logic handle it */ wc->bOverflown = TRUE; return p; } /* TAB: we can split before */ if (run->nFlags & MERF_TAB) { wc->bOverflown = TRUE; return p; } /* graphics: we can split before, if run's width is smaller than row's width */ if ((run->nFlags & MERF_GRAPHICS) && run->nWidth <= wc->nAvailWidth) { wc->bOverflown = TRUE; return p; } /* can we separate out the last spaces ? (to use overflow logic later) */ if (run->nFlags & MERF_ENDWHITE) { /* we aren't sure if it's *really* necessary, it's a good start however */ int black = ME_ReverseFindNonWhitespaceV(run->strText, len); ME_SplitRun(wc, p, black); /* handle both parts again */ return p; } /* determine the split point by backtracking */ pp = ME_SplitByBacktracking(wc, p, loc); if (pp == wc->pRowStart) { /* we had only spaces so far, entire content can be omitted */ wc->pt.x = 0; return p->next; } if (p != pp) /* found a suitable split point */ { wc->bOverflown = TRUE; return pp; } /* we detected that it's best to split on start of this run */ if (wc->bOverflown) return pp; ERR("failure!\n"); /* not found anything - writing over margins is the only option left */ } if ((run->nFlags & (MERF_SPLITTABLE | MERF_STARTWHITE)) || ((run->nFlags & (MERF_GRAPHICS|MERF_TAB)) && (p != wc->pRowStart))) { wc->pLastSplittableRun = p; wc->ptLastSplittableRun = wc->pt; } wc->pt.x += run->nWidth; return p->next; }
void ME_InternalDeleteText(ME_TextEditor *editor, int nOfs, int nChars) { ME_Cursor c; int shift = 0; while(nChars > 0) { ME_Run *run; ME_CursorFromCharOfs(editor, nOfs, &c); run = &c.pRun->member.run; if (run->nFlags & MERF_ENDPARA) { int eollen = run->nCR + run->nLF; if (!ME_FindItemFwd(c.pRun, diParagraph)) { return; } ME_JoinParagraphs(editor, ME_GetParagraph(c.pRun)); /* ME_SkipAndPropagateCharOffset(p->pRun, shift); */ ME_CheckCharOffsets(editor); nChars -= (eollen < nChars) ? eollen : nChars; continue; } else { ME_Cursor cursor; int nIntendedChars = nChars; int nCharsToDelete = nChars; int i; int loc = c.nOffset; ME_FindItemBack(c.pRun, diParagraph)->member.para.nFlags |= MEPF_REWRAP; cursor = c; ME_StrRelPos(run->strText, loc, &nChars); /* nChars is the number of characters that should be deleted from the FOLLOWING runs (these AFTER cursor.pRun) nCharsToDelete is a number of chars to delete from THIS run */ nCharsToDelete -= nChars; shift -= nCharsToDelete; TRACE("Deleting %d (intended %d-remaning %d) chars at %d in '%s' (%d)\n", nCharsToDelete, nIntendedChars, nChars, c.nOffset, debugstr_w(run->strText->szData), run->strText->nLen); if (!c.nOffset && ME_StrVLen(run->strText) == nCharsToDelete) { /* undo = reinsert whole run */ /* nOfs is a character offset (from the start of the document to the current (deleted) run */ ME_UndoItem *pUndo = ME_AddUndoItem(editor, diUndoInsertRun, c.pRun); if (pUndo) pUndo->di.member.run.nCharOfs = nOfs; } else { /* undo = reinsert partial run */ ME_UndoItem *pUndo = ME_AddUndoItem(editor, diUndoInsertRun, c.pRun); if (pUndo) { ME_DestroyString(pUndo->di.member.run.strText); pUndo->di.member.run.nCharOfs = nOfs; pUndo->di.member.run.strText = ME_MakeStringN(run->strText->szData+c.nOffset, nCharsToDelete); } } TRACE("Post deletion string: %s (%d)\n", debugstr_w(run->strText->szData), run->strText->nLen); TRACE("Shift value: %d\n", shift); ME_StrDeleteV(run->strText, c.nOffset, nCharsToDelete); /* update cursors (including c) */ for (i=-1; i<editor->nCursors; i++) { ME_Cursor *pThisCur = editor->pCursors + i; 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); assert(pThisCur->nOffset <= ME_StrVLen(run->strText)); } if (pThisCur->nOffset == ME_StrVLen(run->strText)) { pThisCur->pRun = ME_FindItemFwd(pThisCur->pRun, diRunOrParagraphOrEnd); assert(pThisCur->pRun->type == diRun); pThisCur->nOffset = 0; } } } /* c = updated data now */ if (c.pRun == cursor.pRun) ME_SkipAndPropagateCharOffset(c.pRun, shift); else ME_PropagateCharOffset(c.pRun, shift); if (!ME_StrVLen(cursor.pRun->member.run.strText)) { TRACE("Removing useless run\n"); ME_Remove(cursor.pRun); ME_DestroyDisplayItem(cursor.pRun); } shift = 0; /* ME_CheckCharOffsets(editor); */ continue; } } }
static void ME_PlayUndoItem(ME_TextEditor *editor, ME_DisplayItem *pItem) { ME_UndoItem *pUItem = (ME_UndoItem *)pItem; if (editor->nUndoMode == umIgnore) return; TRACE("Playing undo/redo item, id=%s\n", ME_GetDITypeName(pItem->type)); switch(pItem->type) { case diUndoEndTransaction: assert(0); case diUndoSetParagraphFormat: { ME_Cursor tmp; ME_CursorFromCharOfs(editor, pItem->member.para.nCharOfs, &tmp); ME_SetParaFormat(editor, ME_FindItemBack(tmp.pRun, diParagraph), pItem->member.para.pFmt); break; } case diUndoSetCharFormat: { ME_SetCharFormat(editor, pUItem->nStart, pUItem->nLen, &pItem->member.ustyle->fmt); break; } case diUndoSetDefaultCharFormat: { ME_SetDefaultCharFormat(editor, &pItem->member.ustyle->fmt); break; } case diUndoInsertRun: { ME_InsertRun(editor, pItem->member.run.nCharOfs, pItem); break; } case diUndoDeleteRun: { ME_InternalDeleteText(editor, pUItem->nStart, pUItem->nLen); break; } case diUndoJoinParagraphs: { ME_Cursor tmp; ME_CursorFromCharOfs(editor, pUItem->nStart, &tmp); /* the only thing that's needed is paragraph offset, so no need to split runs */ ME_JoinParagraphs(editor, ME_GetParagraph(tmp.pRun)); break; } case diUndoSplitParagraph: { ME_Cursor tmp; ME_DisplayItem *new_para; ME_CursorFromCharOfs(editor, pUItem->nStart, &tmp); if (tmp.nOffset) tmp.pRun = ME_SplitRunSimple(editor, tmp.pRun, tmp.nOffset); assert(pUItem->nCR >= 0); assert(pUItem->nLF >= 0); new_para = ME_SplitParagraph(editor, tmp.pRun, tmp.pRun->member.run.style, pUItem->nCR, pUItem->nLF); assert(pItem->member.para.pFmt->cbSize == sizeof(PARAFORMAT2)); *new_para->member.para.pFmt = *pItem->member.para.pFmt; break; } default: assert(0 == "PlayUndoItem, unexpected type"); } }