mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-15 14:16:07 -06:00
482 lines
9.7 KiB
C++
482 lines
9.7 KiB
C++
#include "RichEdit.h"
|
|
|
|
namespace Upp {
|
|
|
|
void RichEdit::FinishNF()
|
|
{
|
|
cursor = clamp(cursor, 0, text.GetLength());
|
|
anchor = clamp(anchor, 0, text.GetLength());
|
|
anchorp = text.GetRichPos(anchor);
|
|
cursorp = text.GetRichPos(cursor);
|
|
tablesel = 0;
|
|
begtabsel = false;
|
|
if(anchor != cursor) {
|
|
RichPos p = text.GetRichPos(cursor, anchorp.level);
|
|
if(anchorp.level == 0 || anchorp.level < cursorp.level) {
|
|
cursor = text.AdjustCursor(anchor, cursor);
|
|
cursorp = text.GetRichPos(cursor);
|
|
}
|
|
else
|
|
if(p.table != anchorp.table) {
|
|
if(anchor == 0 && anchorp.level == 1 && text.GetRichPos(anchor, 1).table == 1 && anchor < cursor) {
|
|
while(cursor > 0 && cursorp.level) // selection must be at plain text
|
|
cursorp = text.GetRichPos(--cursor);
|
|
begtabsel = true;
|
|
anchor = 0;
|
|
}
|
|
else {
|
|
tablesel = anchorp.table;
|
|
if(cursor < anchor) {
|
|
cells.left = 0;
|
|
cells.right = anchorp.cell.x;
|
|
cells.top = 0;
|
|
cells.bottom = anchorp.cell.y;
|
|
}
|
|
else {
|
|
cells.left = anchorp.cell.x;
|
|
cells.right = anchorp.tabsize.cx - 1;
|
|
cells.top = anchorp.cell.y;
|
|
cells.bottom = anchorp.tabsize.cy - 1;
|
|
}
|
|
text.AdjustTableSel(tablesel, cells);
|
|
}
|
|
}
|
|
else
|
|
if(p.cell != anchorp.cell) {
|
|
tablesel = anchorp.table;
|
|
cells.left = min(anchorp.cell.x, p.cell.x);
|
|
cells.right = max(anchorp.cell.x, p.cell.x);
|
|
cells.top = min(anchorp.cell.y, p.cell.y);
|
|
cells.bottom = max(anchorp.cell.y, p.cell.y);
|
|
text.AdjustTableSel(tablesel, cells);
|
|
}
|
|
}
|
|
cursorc = text.GetCaret(cursor, pagesz);
|
|
Size sz = GetSize();
|
|
SetSb();
|
|
Rect r = PlaceCaret();
|
|
if(r.top == GetPosY(text.GetCaret(0, pagesz)))
|
|
sb = 0;
|
|
else
|
|
sb.ScrollInto(r.top, r.Height());
|
|
sb.ScrollInto(r.bottom, 1); // if r.Height is bigger than view height, make sure we rather see the bottom
|
|
SetZsc();
|
|
PageY top, bottom;
|
|
int sell = min(cursor, anchor);
|
|
int selh = max(cursor, anchor);
|
|
if(tablesel)
|
|
Refresh();
|
|
else
|
|
if(text.GetInvalid(top, bottom, pagesz, sell, selh, osell, oselh)) {
|
|
int y = GetPosY(top);
|
|
Refresh(0, y - sb, sz.cx, GetPosY(bottom) - y);
|
|
y = GetPosY(text.GetHeight(pagesz)) - sb;
|
|
if(y < sz.cy)
|
|
Refresh(0, y, sz.cx, sz.cy - y);
|
|
}
|
|
osell = sell;
|
|
oselh = selh;
|
|
text.Validate();
|
|
FixObjectRect();
|
|
SetupRuler();
|
|
if(modified) {
|
|
if(useraction)
|
|
Action();
|
|
}
|
|
useraction = modified = false;
|
|
WhenSel();
|
|
}
|
|
|
|
void RichEdit::Finish()
|
|
{
|
|
FinishNF();
|
|
ReadFormat();
|
|
}
|
|
|
|
void RichEdit::MoveNG(int newpos, bool select)
|
|
{
|
|
if(newpos < 0) newpos = 0;
|
|
if(newpos >= text.GetLength() + select) newpos = text.GetLength() + select;
|
|
CloseFindReplace();
|
|
cursor = newpos;
|
|
if(!select) {
|
|
anchor = cursor;
|
|
begtabsel = false;
|
|
}
|
|
objectpos = -1;
|
|
Finish();
|
|
if(select)
|
|
SetSelectionSource(String().Cat() << "text/QTF;Rich Text Format;text/rtf;application/rtf;"
|
|
<< ClipFmtsText());
|
|
CancelMyPreedit();
|
|
}
|
|
|
|
void RichEdit::Move(int newpos, bool select)
|
|
{
|
|
MoveNG(newpos, select);
|
|
gx = cursorc.left;
|
|
}
|
|
|
|
void RichEdit::MoveUpDown(int dir, bool select, int pg)
|
|
{
|
|
Rect page = pagesz;
|
|
if(dir > 0 && cursor >= GetLength() && select) {
|
|
Move(GetLength() + 1, true);
|
|
return;
|
|
}
|
|
if(dir < 0 && cursor > GetLength()) {
|
|
Move(GetLength(), select);
|
|
return;
|
|
}
|
|
int c = text.GetVertMove(min(GetLength(), cursor), gx, page, dir);
|
|
if(c >= 0)
|
|
MoveNG(c, select);
|
|
else
|
|
Move(dir < 0 ? 0 : GetLength(), select);
|
|
if(pg) {
|
|
RichCaret pr = text.GetCaret(cursor, pagesz);
|
|
PageY py;
|
|
py.page = pr.page;
|
|
py.y = pr.top + dir * pg;
|
|
while(py.y > pagesz.cy) {
|
|
py.y -= pagesz.cy;
|
|
py.page++;
|
|
}
|
|
while(py.y < 0) {
|
|
py.y += pagesz.cy;
|
|
py.page--;
|
|
}
|
|
MoveNG(text.GetPos(pr.left, py, pagesz), select);
|
|
}
|
|
}
|
|
|
|
void RichEdit::MovePageUpDown(int dir, bool select)
|
|
{
|
|
PageRect p = text.GetCaret(cursor, pagesz);
|
|
int q = GetPosY(p) - sb;
|
|
MoveUpDown(dir, select, 4 * GetTextRect().Height() / GetZoom() / 5);
|
|
p = text.GetCaret(cursor, pagesz);
|
|
sb = GetPosY(p) - q;
|
|
}
|
|
|
|
void RichEdit::MoveHomeEnd(int dir, bool select)
|
|
{
|
|
int c = cursor;
|
|
while(c + dir >= 0 && c + dir <= text.GetLength()) {
|
|
PageRect p1 = text.GetCaret(c + dir, pagesz);
|
|
if(p1.page != cursorc.page || p1.top != cursorc.top)
|
|
break;
|
|
c += dir;
|
|
}
|
|
Move(c, select);
|
|
}
|
|
|
|
bool RichEdit::IsW(int c)
|
|
{
|
|
return IsLetter(c) || IsDigit(c) || c == '_';
|
|
}
|
|
|
|
void RichEdit::MoveWordRight(bool select)
|
|
{
|
|
Move((int)GetNextWord(cursor), select);
|
|
}
|
|
|
|
void RichEdit::MoveWordLeft(bool select)
|
|
{
|
|
Move((int)GetPrevWord(cursor), select);
|
|
}
|
|
|
|
bool RichEdit::SelBeg(bool select)
|
|
{
|
|
if(IsSelection() && !select) {
|
|
Move(min(cursor, anchor), false);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool RichEdit::SelEnd(bool select)
|
|
{
|
|
if(IsSelection() && !select) {
|
|
Move(max(cursor, anchor), false);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void RichEdit::SelCell(int dx, int dy)
|
|
{
|
|
Move(text.GetCellPos(tablesel, minmax(cursorp.cell.y + dy, 0, cursorp.tabsize.cy - 1),
|
|
minmax(cursorp.cell.x + dx, 0, cursorp.tabsize.cx - 1)).pos, true);
|
|
}
|
|
|
|
bool RichEdit::CursorKey(dword key, int count)
|
|
{
|
|
bool select = key & K_SHIFT;
|
|
if(key == K_CTRL_ADD) {
|
|
ZoomView(1);
|
|
return true;
|
|
}
|
|
if(key == K_CTRL_SUBTRACT) {
|
|
ZoomView(-1);
|
|
return true;
|
|
}
|
|
if(select && tablesel)
|
|
switch(key & ~K_SHIFT) {
|
|
case K_LEFT:
|
|
SelCell(-1, 0);
|
|
break;
|
|
case K_RIGHT:
|
|
SelCell(1, 0);
|
|
break;
|
|
case K_UP:
|
|
SelCell(0, -1);
|
|
break;
|
|
case K_DOWN:
|
|
SelCell(0, 1);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
else {
|
|
switch(key) {
|
|
case K_CTRL_UP:
|
|
sb.PrevLine();
|
|
break;
|
|
case K_CTRL_DOWN:
|
|
sb.NextLine();
|
|
break;
|
|
default:
|
|
switch(key & ~K_SHIFT) {
|
|
case K_LEFT:
|
|
if(!SelBeg(select))
|
|
Move(cursor - 1, select);
|
|
break;
|
|
case K_RIGHT:
|
|
if(!SelEnd(select))
|
|
Move(cursor + 1, select);
|
|
break;
|
|
case K_UP:
|
|
if(!SelBeg(select))
|
|
MoveUpDown(-1, select);
|
|
break;
|
|
case K_DOWN:
|
|
if(!SelEnd(select))
|
|
MoveUpDown(1, select);
|
|
break;
|
|
case K_PAGEUP:
|
|
if(!SelBeg(select))
|
|
MovePageUpDown(-1, select);
|
|
break;
|
|
case K_PAGEDOWN:
|
|
if(!SelEnd(select))
|
|
MovePageUpDown(1, select);
|
|
break;
|
|
case K_END:
|
|
MoveHomeEnd(1, select);
|
|
break;
|
|
case K_HOME:
|
|
MoveHomeEnd(-1, select);
|
|
break;
|
|
case K_CTRL_LEFT:
|
|
if(!SelBeg(select))
|
|
MoveWordLeft(select);
|
|
break;
|
|
case K_CTRL_RIGHT:
|
|
if(!SelEnd(select))
|
|
MoveWordRight(select);
|
|
break;
|
|
case K_CTRL_HOME:
|
|
case K_CTRL_PAGEUP:
|
|
Move(0, select);
|
|
break;
|
|
case K_CTRL_END:
|
|
case K_CTRL_PAGEDOWN:
|
|
Move(text.GetLength(), select);
|
|
break;
|
|
case K_CTRL_A:
|
|
Move(0, false);
|
|
Move(text.GetLength(), true);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
Sync();
|
|
return true;
|
|
}
|
|
|
|
bool RichEdit::IsSelection() const
|
|
{
|
|
return anchor != cursor;
|
|
}
|
|
|
|
bool RichEdit::GetSelection(int& l, int& h) const
|
|
{
|
|
if(IsSelection()) {
|
|
l = min(anchor, cursor);
|
|
h = max(anchor, cursor);
|
|
return true;
|
|
}
|
|
l = h = cursor;
|
|
return false;
|
|
}
|
|
|
|
bool RichEdit::InSelection(int& c) const
|
|
{
|
|
int sell, selh;
|
|
if(GetSelection(sell, selh) && c >= sell && c < selh) {
|
|
c = sell;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void RichEdit::CancelSelection()
|
|
{
|
|
if(IsSelection()) {
|
|
tablesel = 0;
|
|
anchor = cursor;
|
|
begtabsel = false;
|
|
found = notfoundfw = false;
|
|
CloseFindReplace();
|
|
Finish();
|
|
}
|
|
}
|
|
|
|
bool RichEdit::RemoveSelection(bool back)
|
|
{
|
|
if(IsSelection()) {
|
|
if(tablesel) {
|
|
NextUndo();
|
|
SaveTable(tablesel);
|
|
text.ClearTable(tablesel, cells);
|
|
Move(text.GetCellPos(tablesel, cells.top, cells.left).pos);
|
|
}
|
|
else {
|
|
BegSelTabFix();
|
|
int c = min(cursor, anchor);
|
|
Remove(c, abs(cursor - anchor), back);
|
|
found = notfoundfw = false;
|
|
CloseFindReplace();
|
|
Move(c);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void RichEdit::GetWordAtCursorPos(int& pos, int& count)
|
|
{
|
|
WString w;
|
|
int c = cursor;
|
|
pos = count = 0;
|
|
if(IsLetter(text[c])) {
|
|
while(c > 0 && IsLetter(text[c - 1]))
|
|
c--;
|
|
pos = c;
|
|
while(w.GetLength() < 64 && IsLetter(text[c]))
|
|
c++;
|
|
count = c - pos;
|
|
}
|
|
}
|
|
|
|
WString RichEdit::GetWordAtCursor()
|
|
{
|
|
int pos, count;
|
|
GetWordAtCursorPos(pos, count);
|
|
WString w;
|
|
for(int i = 0; i < count; i++)
|
|
w.Cat(text[i + pos]);
|
|
return w;
|
|
}
|
|
|
|
void RichEdit::AddUserDict()
|
|
{
|
|
if(IsSelection()) return;
|
|
WString w = GetWordAtCursor();
|
|
if(w.IsEmpty()) return;
|
|
SpellerAdd(w, fixedlang ? fixedlang : formatinfo.language);
|
|
text.ClearSpelling();
|
|
Refresh();
|
|
}
|
|
|
|
void RichEdit::Goto()
|
|
{
|
|
SetFocus();
|
|
if(gototable.IsCursor())
|
|
{
|
|
Move(gototable.Get(1), false);
|
|
Move(gototable.Get(2), true);
|
|
}
|
|
}
|
|
|
|
void RichEdit::GotoType(int type, Ctrl& l)
|
|
{
|
|
Vector<RichValPos> f = text.GetValPos(pagesz, type);
|
|
gototable.Clear();
|
|
for(int i = 0; i < f.GetCount(); i++) {
|
|
const RichValPos& q = f[i];
|
|
int endpos = q.pos;
|
|
if(type == RichText::INDEXENTRIES) {
|
|
WString ie = text.GetRichPos(endpos).format.indexentry;
|
|
int l = text.GetLength();
|
|
while(endpos < l) {
|
|
RichPos p = text.GetRichPos(++endpos);
|
|
if(p.format.indexentry != ie || p.chr == '\n')
|
|
break;
|
|
}
|
|
}
|
|
gototable.Add(q.data, q.pos, endpos);
|
|
}
|
|
if(gototable.GetCount())
|
|
gototable.PopUp(&l);
|
|
}
|
|
|
|
void RichEdit::GotoLbl()
|
|
{
|
|
GotoType(RichText::LABELS, label);
|
|
}
|
|
|
|
void RichEdit::GotoEntry()
|
|
{
|
|
GotoType(RichText::INDEXENTRIES, indexentry);
|
|
}
|
|
|
|
bool RichEdit::GotoLabel(Gate<const WString&> match)
|
|
{
|
|
Vector<RichValPos> f = text.GetValPos(pagesz, RichText::LABELS);
|
|
for(int i = 0; i < f.GetCount(); i++)
|
|
if(match(f[i].data)) {
|
|
Move(f[i].pos);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool RichEdit::GotoLabel(const String& lbl)
|
|
{
|
|
return GotoLabel([=](const WString& data) { return data == WString(lbl); });
|
|
}
|
|
|
|
void RichEdit::BeginPara()
|
|
{
|
|
RichPos pos = text.GetRichPos(anchor);
|
|
Move(cursor - pos.posinpara);
|
|
}
|
|
|
|
void RichEdit::NextPara()
|
|
{
|
|
RichPos pos = text.GetRichPos(anchor);
|
|
Move(cursor - pos.posinpara + pos.paralen + 1);
|
|
}
|
|
|
|
void RichEdit::PrevPara()
|
|
{
|
|
RichPos pos = text.GetRichPos(anchor);
|
|
Move(cursor - pos.posinpara - 1);
|
|
BeginPara();
|
|
}
|
|
|
|
}
|