mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-15 14:16:07 -06:00
This might bring some incompatibilities in the code that expects wchar to be 16 bit, which escpecially involves dealing with Win32 (and to lesser extend MacOS) APIs, so if your application is doing that, please check all instances of WCHAR (UniChar on MacOS) or even wchar especially type casts. To support host APIs, char16 is introduced (but there is no 16-bit String varian). Use ToSystemCharsetW, FromSystemCharsetW to convert texts to Win32 API. - Support of drawing non-BMP characters in GUI - Vastly improved character font replacement code (when drawing characters missing with requested font, replacement font is used) - Last instances of Win32 ANSI calls (those ending with A) are removed - UTF handling routines are refactored and their's naming is unified - RTF is now being able to handle non-BMP characters (RTF is used as clipboard format for RichText) Other minor changes: - fixed TryRealloc issue - improved MemoryCheck - Removed MemoryAlloc48/MemoryFree48 - In theide Background parsing should less often cause delays in the main thread
614 lines
13 KiB
C++
614 lines
13 KiB
C++
#include "CtrlLib.h"
|
|
|
|
namespace Upp {
|
|
|
|
void DocEdit::MouseWheel(Point p, int zdelta, dword keyflags)
|
|
{
|
|
sb.Wheel(zdelta);
|
|
}
|
|
|
|
void DocEdit::ClearLines()
|
|
{
|
|
para.Clear();
|
|
ASSERT(GetLineCount() == para.GetCount());
|
|
}
|
|
|
|
void DocEdit::InsertLines(int line, int count)
|
|
{
|
|
para.Insert(line, Para(), count);
|
|
ASSERT(GetLineCount() == para.GetCount());
|
|
}
|
|
|
|
void DocEdit::RemoveLines(int line, int count)
|
|
{
|
|
para.Remove(line, count);
|
|
ASSERT(GetLineCount() == para.GetCount());
|
|
}
|
|
|
|
DocEdit::Fmt DocEdit::Format(const WString& text) const
|
|
{
|
|
FontInfo fi = font.Info();
|
|
Fmt fmt;
|
|
int tcx = fi['x'] * 4;
|
|
fmt.len = text.GetLength();
|
|
fmt.text.Alloc(text.GetLength());
|
|
memcpy(fmt.text, text, text.GetLength() * sizeof(wchar));
|
|
fmt.width.Alloc(text.GetLength());
|
|
fmt.line.Add(0);
|
|
int *w = fmt.width;
|
|
int x = 0;
|
|
const wchar *space = NULL;
|
|
int spacex = 0;
|
|
for(wchar *s = fmt.text; s < fmt.text + fmt.len; s++) {
|
|
int cw;
|
|
if(*s == '\t')
|
|
cw = (x + tcx) / tcx * tcx - x;
|
|
else
|
|
cw = fi[*s];
|
|
*w++ = cw;
|
|
if(*s == ' ' || *s == '\t') {
|
|
space = s;
|
|
spacex = x + cw;
|
|
*s = ' ';
|
|
}
|
|
x += cw;
|
|
if(x > cx) {
|
|
if(space && space <= s) {
|
|
space++;
|
|
fmt.line.Add(int(space - fmt.text));
|
|
space = NULL;
|
|
x -= spacex;
|
|
}
|
|
else {
|
|
fmt.line.Add(int(s - fmt.text));
|
|
x = cw;
|
|
}
|
|
}
|
|
}
|
|
fmt.fi = fi;
|
|
return fmt;
|
|
}
|
|
|
|
int DocEdit::GetHeight(int i) {
|
|
Para& p = para[i];
|
|
if(p.cx == cx) return p.cy;
|
|
Fmt fmt = Format(GetWLine(i));
|
|
p.cx = cx;
|
|
p.cy = fmt.line.GetCount() * (fmt.fi.GetHeight()) + after;
|
|
return p.cy;
|
|
}
|
|
|
|
int DocEdit::GetY(int parai) {
|
|
int y = 1;
|
|
for(int i = 0; i < parai; i++)
|
|
y += GetHeight(i);
|
|
return y;
|
|
}
|
|
|
|
void DocEdit::InvalidateLine(int i)
|
|
{
|
|
para[i].cx = -1;
|
|
}
|
|
|
|
void DocEdit::RefreshLine(int i) {
|
|
int q = para[i].cx >= 0 ? para[i].cy : -1;
|
|
Refresh(1, GetY(i) - sb, cx, GetHeight(i));
|
|
if(q < 0 || q != para[i].cy)
|
|
Refresh();
|
|
}
|
|
|
|
int sSum(const int *w, int n)
|
|
{
|
|
int m = 0;
|
|
while(n--)
|
|
m += *w++;
|
|
return m;
|
|
}
|
|
|
|
void DocEdit::Paint(Draw& w) {
|
|
Size sz = GetSize();
|
|
Color bg = color[IsShowEnabled() && !IsReadOnly() ? PAPER_NORMAL : PAPER_READONLY];
|
|
if(nobg)
|
|
bg = Null;
|
|
int y = -sb + 1;
|
|
int pos = 0;
|
|
int sell, selh;
|
|
GetSelection32(sell, selh);
|
|
for(int i = 0; i < para.GetCount() && y < sz.cy; i++) {
|
|
int h = GetHeight(i);
|
|
if(y + h >= 0) {
|
|
WString text = GetWLine(i);
|
|
Fmt fmt = Format(text);
|
|
int p = pos;
|
|
for(int i = 0; i < fmt.line.GetCount(); i++) {
|
|
int n = fmt.LineEnd(i) - fmt.line[i];
|
|
int a = minmax(sell - p, 0, n);
|
|
int b = minmax(selh - p, 0, n) - a;
|
|
int c = n - a - b;
|
|
int *wa = fmt.width + fmt.line[i];
|
|
int *wb = fmt.width + fmt.line[i] + a;
|
|
int *wc = fmt.width + fmt.line[i] + a + b;
|
|
int acx = sSum(wa, a);
|
|
int bcx = sSum(wb, b);
|
|
int ccx = sSum(wc, c);
|
|
w.DrawRect(1, y, acx, fmt.fi.GetHeight(), bg);
|
|
w.DrawText(1, y, ~fmt.text + fmt.line[i], font,
|
|
IsShowEnabled() ? color[INK_NORMAL] : color[INK_DISABLED], a, wa);
|
|
w.DrawRect(1 + acx, y, bcx, fmt.fi.GetHeight(), color[PAPER_SELECTED]);
|
|
w.DrawText(1 + acx, y, ~fmt.text + fmt.line[i] + a, font, color[INK_SELECTED], b, wb);
|
|
w.DrawRect(1 + acx + bcx, y, ccx, fmt.fi.GetHeight(), bg);
|
|
w.DrawText(1 + acx + bcx, y, ~fmt.text + fmt.line[i] + a + b, font, color[INK_NORMAL], c, wc);
|
|
p += n;
|
|
w.DrawRect(1 + acx + bcx + ccx, y, cx - (acx + bcx + ccx), fmt.fi.GetHeight(),
|
|
p >= sell && p < selh ? color[PAPER_SELECTED] : bg);
|
|
y += fmt.fi.GetHeight();
|
|
}
|
|
w.DrawRect(1, y, cx, after, color[PAPER_NORMAL]);
|
|
y += after;
|
|
}
|
|
else
|
|
y += h;
|
|
pos += GetLineLength(i) + 1;
|
|
}
|
|
w.DrawRect(0, -sb, sz.cx, 1, bg);
|
|
w.DrawRect(0, 0, 1, sz.cy, bg);
|
|
w.DrawRect(sz.cx - 1, 0, 1, sz.cy, bg);
|
|
if(eofline)
|
|
w.DrawRect(1, y++, cx, 1, SColorShadow);
|
|
if(y < sz.cy)
|
|
w.DrawRect(1, y, cx, sz.cy - y, bg);
|
|
DrawTiles(w, DropCaret(), CtrlImg::checkers());
|
|
}
|
|
|
|
void DocEdit::SetSb()
|
|
{
|
|
Size sz = GetSize();
|
|
cx = max(Draw::GetStdFontCy(), sz.cx - 2);
|
|
sb.SetPage(GetSize().cy);
|
|
sb.SetTotal(GetY(para.GetCount()) + 2);
|
|
PlaceCaret(false);
|
|
}
|
|
|
|
void DocEdit::Layout()
|
|
{
|
|
SetSb();
|
|
Invalidate();
|
|
}
|
|
|
|
Point DocEdit::GetCaret(int pos) {
|
|
int i = GetLinePos32(pos);
|
|
Fmt fmt = Format(GetWLine(i));
|
|
int l;
|
|
for(l = 0; l < fmt.line.GetCount(); l++)
|
|
if(pos < fmt.line[l])
|
|
break;
|
|
l--;
|
|
const int *w = fmt.width + fmt.line[l];
|
|
pos -= fmt.line[l];
|
|
int x = 0;
|
|
while(pos-- > 0)
|
|
x += *w++;
|
|
return Point(x, GetY(i) + l * fmt.fi.GetHeight());
|
|
}
|
|
|
|
int DocEdit::GetCursorPos(Point p) {
|
|
int pos = 0;
|
|
for(int i = 0; i < para.GetCount(); i++) {
|
|
int h = GetHeight(i);
|
|
if(p.y < h) {
|
|
WString text = GetWLine(i);
|
|
Fmt fmt = Format(text);
|
|
int x = 0;
|
|
int l = p.y / fmt.fi.GetHeight();
|
|
if(l < 0)
|
|
return pos;
|
|
if(l >= fmt.line.GetCount())
|
|
return pos + text.GetLength();
|
|
const int *w = fmt.width + fmt.line[l];
|
|
const int *e = fmt.width + fmt.LineEnd(l);
|
|
while(w < e) {
|
|
if(p.x < x + *w / 2)
|
|
return int(w - fmt.width) + pos;
|
|
x += *w++;
|
|
}
|
|
int p = int(e - fmt.width);
|
|
if(p > 0 && text[p - 1] == ' ' && l < fmt.line.GetCount() - 1)
|
|
p--;
|
|
return p + pos;
|
|
}
|
|
p.y -= h;
|
|
pos += GetLineLength(i) + 1;
|
|
}
|
|
return GetLength32();
|
|
}
|
|
|
|
void DocEdit::PlaceCaret(bool scroll) {
|
|
Point cr = GetCaret((int)cursor);
|
|
int fy = font.Info().GetLineHeight();
|
|
if(scroll) {
|
|
if(cursor == GetLength32())
|
|
sb.End();
|
|
else
|
|
sb.ScrollInto(cr.y, fy + 2);
|
|
}
|
|
SetCaret(cr.x + 1, cr.y - sb, 1, fy);
|
|
WhenSel();
|
|
}
|
|
|
|
void DocEdit::PlaceCaret(int64 newpos, bool select) {
|
|
if(newpos > GetLength32())
|
|
newpos = GetLength32();
|
|
int z = GetLine(newpos);
|
|
if(select) {
|
|
if(anchor < 0)
|
|
anchor = cursor;
|
|
RefreshLines(z, GetLine(cursor));
|
|
}
|
|
else
|
|
if(anchor >= 0) {
|
|
RefreshLines(GetLine(cursor), GetLine(anchor));
|
|
anchor = -1;
|
|
}
|
|
cursor = newpos;
|
|
PlaceCaret(true);
|
|
SelectionChanged();
|
|
if(IsSelection())
|
|
SetSelectionSource(ClipFmtsText());
|
|
}
|
|
|
|
int DocEdit::GetMousePos(Point p)
|
|
{
|
|
return GetCursorPos(Point(p.x - 1, p.y + sb - 1));
|
|
}
|
|
|
|
void DocEdit::LeftDown(Point p, dword flags) {
|
|
SetFocus();
|
|
int c = GetMousePos(p);
|
|
int l, h;
|
|
if(GetSelection32(l, h) && c >= l && c < h) {
|
|
selclick = true;
|
|
return;
|
|
}
|
|
PlaceCaret(c, flags & K_SHIFT);
|
|
SetCapture();
|
|
}
|
|
|
|
void DocEdit::LeftUp(Point p, dword flags)
|
|
{
|
|
if(!HasCapture() && selclick) {
|
|
int c = GetMousePos(p);
|
|
PlaceCaret(c, flags & K_SHIFT);
|
|
SetFocus();
|
|
}
|
|
selclick = false;
|
|
ReleaseCapture();
|
|
}
|
|
|
|
void DocEdit::MouseMove(Point p, dword flags) {
|
|
if(!HasCapture()) return;
|
|
PlaceCaret(GetMousePos(p), true);
|
|
}
|
|
|
|
void DocEdit::LeftDouble(Point, dword)
|
|
{
|
|
int64 l, h;
|
|
if(GetWordSelection(cursor, l, h))
|
|
SetSelection(l, h);
|
|
}
|
|
|
|
void DocEdit::LeftTriple(Point, dword)
|
|
{
|
|
int q = (int)cursor;
|
|
int i = GetLinePos32(q);
|
|
q = (int)cursor - q;
|
|
SetSelection(q, q + GetLineLength(i) + 1);
|
|
}
|
|
|
|
Image DocEdit::CursorImage(Point, dword) {
|
|
return Image::IBeam();
|
|
}
|
|
|
|
void DocEdit::GotFocus() {
|
|
Refresh();
|
|
}
|
|
|
|
void DocEdit::LostFocus() {
|
|
Refresh();
|
|
}
|
|
|
|
void DocEdit::VertMove(int delta, bool select, bool scs) {
|
|
int hy = GetY(para.GetCount());
|
|
Point p = GetCaret((int)cursor);
|
|
int yy = p.y;
|
|
for(;;) {
|
|
p.y += delta;
|
|
if(p.y > hy) p.y = hy - 1;
|
|
if(p.y < 0) p.y = 0;
|
|
int q = GetCursorPos(p);
|
|
if(q >= 0 && q != cursor && (delta < 0) == (q < cursor) && GetCaret(q).y != yy) {
|
|
PlaceCaret(q, select);
|
|
break;
|
|
}
|
|
if(p.y == 0 || p.y >= hy - 1) {
|
|
PlaceCaret(delta > 0 ? GetLength32() : 0, select);
|
|
break;
|
|
}
|
|
delta = sgn(delta) * 4;
|
|
}
|
|
if(scs)
|
|
sb = GetCaret((int)cursor).y - (yy - sb);
|
|
PlaceCaret(true);
|
|
}
|
|
|
|
void DocEdit::HomeEnd(int x, bool select) {
|
|
Point p = GetCaret((int)cursor);
|
|
p.x = x;
|
|
PlaceCaret(GetCursorPos(p), select);
|
|
}
|
|
|
|
bool DocEdit::Key(dword key, int cnt)
|
|
{
|
|
NextUndo();
|
|
bool h;
|
|
int q;
|
|
bool select = key & K_SHIFT;
|
|
int pgsk = max(8, 6 * GetSize().cy / 8);
|
|
switch(key & ~K_SHIFT) {
|
|
case K_CTRL_LEFT:
|
|
PlaceCaret(GetPrevWord(cursor), select);
|
|
break;
|
|
case K_CTRL_RIGHT:
|
|
PlaceCaret(GetNextWord(cursor), select);
|
|
break;
|
|
case K_HOME:
|
|
HomeEnd(0, select);
|
|
break;
|
|
case K_END:
|
|
HomeEnd(cx, select);
|
|
break;
|
|
case K_CTRL_HOME:
|
|
case K_CTRL_PAGEUP:
|
|
PlaceCaret(0, select);
|
|
break;
|
|
case K_CTRL_END:
|
|
case K_CTRL_PAGEDOWN:
|
|
PlaceCaret(GetLength32(), select);
|
|
break;
|
|
case K_UP:
|
|
if(GetCursor() == 0)
|
|
return !updownleave;
|
|
VertMove(-8, select, false);
|
|
return true;
|
|
case K_DOWN:
|
|
if(GetCursor32() == GetLength32())
|
|
return !updownleave;
|
|
VertMove(8, select, false);
|
|
return true;
|
|
case K_PAGEUP:
|
|
VertMove(-pgsk, select, true);
|
|
return true;
|
|
case K_PAGEDOWN:
|
|
VertMove(pgsk, select, true);
|
|
return true;
|
|
case K_LEFT:
|
|
if(cursor)
|
|
PlaceCaret(cursor - 1, select);
|
|
break;
|
|
case K_RIGHT:
|
|
if(cursor < GetLength32())
|
|
PlaceCaret(cursor + 1, select);
|
|
break;
|
|
default:
|
|
if(IsReadOnly()) return MenuBar::Scan(WhenBar, key);
|
|
switch(key) {
|
|
case K_BACKSPACE:
|
|
case K_SHIFT|K_BACKSPACE:
|
|
if(RemoveSelection()) break;
|
|
if(cursor == 0) return true;
|
|
cursor--;
|
|
Remove((int)cursor, 1);
|
|
break;
|
|
case K_CTRL_BACKSPACE:
|
|
if(RemoveSelection()) break;
|
|
if(cursor <= 0) return true;
|
|
q = (int)cursor - 1;
|
|
h = IsLetter(GetChar(q));
|
|
while(q > 0 && IsLetter(GetChar(q - 1)) == h) q--;
|
|
Remove(q, (int)cursor - q);
|
|
SetCursor(q);
|
|
break;
|
|
case K_DELETE:
|
|
if(RemoveSelection()) break;
|
|
if(cursor >= GetLength32()) return true;
|
|
if(cursor < GetLength32())
|
|
Remove((int)cursor, 1);
|
|
break;
|
|
case K_CTRL_DELETE:
|
|
if(RemoveSelection()) break;
|
|
if(cursor >= GetLength32()) return true;
|
|
q = (int)cursor;
|
|
h = IsLetter(GetChar(q));
|
|
while(IsLetter(GetChar(q)) == h && q < GetLength32()) q++;
|
|
Remove((int)cursor, q - (int)cursor);
|
|
break;
|
|
case K_ENTER:
|
|
if(!processenter)
|
|
return true;
|
|
key = '\n';
|
|
default:
|
|
if(filter && key >= 32 && key < 65535)
|
|
key = (*filter)(key);
|
|
if(key >= ' ' && key < K_CHAR_LIM || key == '\n' || key == '\t' || key == K_SHIFT_SPACE) {
|
|
if(key == K_TAB && !processtab)
|
|
return false;
|
|
if(key >= 128 && key < K_CHAR_LIM && (charset != CHARSET_UNICODE && charset != CHARSET_UTF8_BOM)
|
|
&& FromUnicode((wchar)key, charset) == DEFAULTCHAR)
|
|
return true;
|
|
RemoveSelection();
|
|
Insert((int)cursor, WString(key == K_SHIFT_SPACE ? ' ' : key, cnt), true);
|
|
cursor += cnt;
|
|
break;
|
|
}
|
|
return MenuBar::Scan(WhenBar, key);
|
|
}
|
|
UpdateAction();
|
|
}
|
|
PlaceCaret(true);
|
|
return true;
|
|
}
|
|
|
|
void DocEdit::Scroll()
|
|
{
|
|
PlaceCaret(false);
|
|
Refresh();
|
|
}
|
|
|
|
void DocEdit::Invalidate()
|
|
{
|
|
for(int i = 0; i < para.GetCount(); i++)
|
|
para[i].cx = -1;
|
|
PlaceCaret(false);
|
|
}
|
|
|
|
void DocEdit::RefreshStyle()
|
|
{
|
|
cursor = 0;
|
|
sb = 0;
|
|
ClearSelection();
|
|
Invalidate();
|
|
Layout();
|
|
Refresh();
|
|
}
|
|
|
|
void DocEdit::RightDown(Point p, dword w)
|
|
{
|
|
SetFocus();
|
|
int c = GetMousePos(p);
|
|
int l, h;
|
|
if(!GetSelection32(l, h) || c < l || c >= h)
|
|
PlaceCaret(c, false);
|
|
MenuBar::Execute(WhenBar);
|
|
}
|
|
|
|
DocEdit::DocEdit()
|
|
{
|
|
updownleave = false;
|
|
cx = 0;
|
|
filter = NULL;
|
|
after = 0;
|
|
font = StdFont();
|
|
AutoHideSb();
|
|
SetFrame(ViewFrame());
|
|
AddFrame(sb);
|
|
sb.SetLine(8);
|
|
sb.WhenScroll = THISBACK(Scroll);
|
|
InsertLines(0, 1);
|
|
eofline = true;
|
|
PlaceCaret(false);
|
|
}
|
|
|
|
DocEdit::~DocEdit() {}
|
|
|
|
void DocEdit::DragAndDrop(Point p, PasteClip& d)
|
|
{
|
|
if(IsReadOnly()) return;
|
|
int c = GetMousePos(p);
|
|
if(AcceptText(d)) {
|
|
NextUndo();
|
|
int a = sb;
|
|
int sell, selh;
|
|
WString txt = GetWString(d);
|
|
if(GetSelection32(sell, selh)) {
|
|
if(c >= sell && c < selh) {
|
|
if(!IsReadOnly())
|
|
RemoveSelection();
|
|
if(IsDragAndDropSource())
|
|
d.SetAction(DND_COPY);
|
|
c = sell;
|
|
}
|
|
else
|
|
if(d.GetAction() == DND_MOVE && IsDragAndDropSource()) {
|
|
if(c > sell)
|
|
c -= selh - sell;
|
|
if(!IsReadOnly())
|
|
RemoveSelection();
|
|
d.SetAction(DND_COPY);
|
|
}
|
|
}
|
|
int count = Insert(c, txt);
|
|
sb = a;
|
|
SetFocus();
|
|
SetSelection(c, c + count);
|
|
Action();
|
|
return;
|
|
}
|
|
if(!d.IsAccepted()) return;
|
|
Point dc = Null;
|
|
if(c >= 0) {
|
|
Point cr = GetCaret(c);
|
|
dc = Point(cr.x + 1, cr.y);
|
|
}
|
|
if(dc != dropcaret) {
|
|
RefreshDropCaret();
|
|
dropcaret = dc;
|
|
RefreshDropCaret();
|
|
}
|
|
}
|
|
|
|
Rect DocEdit::DropCaret()
|
|
{
|
|
if(IsNull(dropcaret))
|
|
return Rect(0, 0, 0, 0);
|
|
return RectC(dropcaret.x, dropcaret.y - sb, 1, font.Info().GetLineHeight());
|
|
}
|
|
|
|
void DocEdit::RefreshDropCaret()
|
|
{
|
|
Refresh(DropCaret());
|
|
}
|
|
|
|
void DocEdit::DragRepeat(Point p)
|
|
{
|
|
sb = (int)sb + GetDragScroll(this, p, 16).y;
|
|
}
|
|
|
|
void DocEdit::DragLeave()
|
|
{
|
|
RefreshDropCaret();
|
|
dropcaret = Null;
|
|
isdrag = false;
|
|
Layout();
|
|
}
|
|
|
|
void DocEdit::LeftDrag(Point p, dword flags)
|
|
{
|
|
int c = GetMousePos(p);
|
|
int l, h;
|
|
if(!HasCapture() && GetSelection32(l, h) && c >= l && c < h) {
|
|
WString sample = GetW(l, min(h - l, 3000));
|
|
Size ssz = StdSampleSize();
|
|
ImageDraw iw(ssz);
|
|
iw.DrawRect(ssz, Black());
|
|
iw.Alpha().DrawRect(ssz, Black());
|
|
DrawTLText(iw.Alpha(), 0, 0, ssz.cx, sample, StdFont(), White());
|
|
NextUndo();
|
|
if(DoDragAndDrop(ClipFmtsText(), iw) == DND_MOVE && !IsReadOnly()) {
|
|
RemoveSelection();
|
|
Action();
|
|
}
|
|
}
|
|
}
|
|
|
|
TextCtrl::EditPos DocEdit::GetEditPos() const {
|
|
EditPos pos;
|
|
pos.sby = sb.Get();
|
|
pos.cursor = cursor;
|
|
return pos;
|
|
}
|
|
|
|
void DocEdit::SetEditPos(const TextCtrl::EditPos& pos) {
|
|
sb.Set(minmax(pos.sby, 0, GetY(para.GetCount()) + 2));
|
|
SetCursor(pos.cursor);
|
|
}
|
|
|
|
}
|